From 8746d4c13dde6a77136233661b03298d27ba4c8a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 9 Mar 2022 14:59:55 +0100 Subject: [PATCH 01/42] Small typos fixed.. --- .../references/umbtrackedreferences.component.js | 10 +++++----- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js index 000e87146c..bf1891bf0f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js @@ -16,17 +16,17 @@ function onInit() { - vm.referencesTitle = this.hideNoneDependencies ? "The following items depends on this" : "Referenced by the following items"; - vm.referencedDescendantsTitle = this.hideNoneDependencies ? "The following descending items has dependencies" : "The following descending items are referenced"; - + vm.referencesTitle = this.hideNoneDependencies ? "The following items depend on this" : "Referenced by the following items"; + vm.referencedDescendantsTitle = this.hideNoneDependencies ? "The following descending items has dependencies" : "The following descendant items have dependencies"; + localizationService.localize(this.hideNoneDependencies ? "references_labelDependsOnThis" : "references_labelUsedByItems").then(function (value) { vm.referencesTitle = value; }); - + localizationService.localize(this.hideNoneDependencies ? "references_labelDependentDescendants" : "references_labelUsedDescendants").then(function (value) { vm.referencedDescendantsTitle = value; }); - + vm.descendantsOptions = {}; vm.descendantsOptions.filterMustBeIsDependency = this.hideNoneDependencies; vm.hasReferencesInDescendants = false; diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index e056647ebc..bed9f227dd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2559,12 +2559,12 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Referenced by the following Member Types Referenced by Referenced by the following items - The following items depends on this + The following items depend on this Referenced by the following Documents Referenced by the following Members Referenced by the following Media The following items are referenced - The following descending items are referenced + The following descendant items have dependencies The following descending items has dependencies One or more of this item's descendants is being referenced in a media item. One or more of this item's descendants is being referenced in a content item. From cc05428640404b1ac33736083f2399251c5a0093 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 10 Mar 2022 13:59:19 +0000 Subject: [PATCH 02/42] fix additional legacy password formats (#12120) * Added failing test to demo issue. * Handle old machine key default. --- .../Security/LegacyPasswordSecurity.cs | 10 +++ .../Security/UmbracoPasswordHasherTests.cs | 81 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index ca3045a4de..35528a48ca 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -209,11 +209,21 @@ namespace Umbraco.Cms.Core.Security { // This is for the v6-v8 hashing algorithm if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) + { return true; + } + + // Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes) + if (algorithm.InvariantEquals("SHA1")) + { + return true; + } // This is for the <= v4 hashing algorithm if (IsLegacySHA1Algorithm(algorithm)) + { return true; + } return false; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs new file mode 100644 index 0000000000..aa6bb4156b --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs @@ -0,0 +1,81 @@ +using AutoFixture.NUnit3; +using Microsoft.AspNetCore.Identity; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Tests.UnitTests.AutoFixture; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security +{ + [TestFixture] + public class UmbracoPasswordHasherTests + { + // Technically MD5, HMACSHA384 & HMACSHA512 were also possible but opt in as opposed to historic defaults. + [Test] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "uB/pLEhhe1W7EtWMv/pSgg==1y8+aso9+h3AKRtJXlVYeg2TZKJUr64hccj82ZZ7Ksk=")] // Actually HMACSHA256 + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] // v4 site legacy password, with incorrect algorithm specified in database actually HMACSHA1 with password used as key. + [InlineAutoMoqData("SHA1", "Umbraco9Rocks!", "6tZGfG9NTxJJYp19Fac9og==zzRggqANxhb+CbD/VabEt8cIde8=")] // When SHA1 is set on machine key. + public void VerifyHashedPassword_WithValidLegacyPasswordHash_ReturnsSuccessRehashNeeded( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings{ HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + + [Test] + [InlineAutoMoqData("PBKDF2.ASPNETCORE.V3", "Umbraco9Rocks!", "AQAAAAEAACcQAAAAEDCrYcnIhHKr38yuchsDu6AFqqmLNvRooKObV25GC1LC1tLY+gWGU4xNug0lc17PHA==")] + public void VerifyHashedPassword_WithValidModernPasswordHash_ReturnsSuccess( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings { HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.Success, result); + } + + [Test] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "aB/cDeFaBcDefAbcD/EfaB==1y8+aso9+h3AKRtJXlVYeg2TZKJUr64hccj82ZZ7Ksk=")] + public void VerifyHashedPassword_WithIncorrectPassword_ReturnsFailed( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings { HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.Failed, result); + } + + public class TestUserStub : UmbracoIdentityUser + { + public TestUserStub() => PasswordConfig = "not null or empty"; + } + } +} From 1ab0bb9254c2edfd32238532e7fecbc04542056c Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Thu, 10 Mar 2022 13:59:19 +0000 Subject: [PATCH 03/42] fix additional legacy password formats (#12120) * Added failing test to demo issue. * Handle old machine key default. --- .../Security/LegacyPasswordSecurity.cs | 10 +++ .../Security/UmbracoPasswordHasherTests.cs | 81 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index ca3045a4de..35528a48ca 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -209,11 +209,21 @@ namespace Umbraco.Cms.Core.Security { // This is for the v6-v8 hashing algorithm if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) + { return true; + } + + // Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes) + if (algorithm.InvariantEquals("SHA1")) + { + return true; + } // This is for the <= v4 hashing algorithm if (IsLegacySHA1Algorithm(algorithm)) + { return true; + } return false; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs new file mode 100644 index 0000000000..aa6bb4156b --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs @@ -0,0 +1,81 @@ +using AutoFixture.NUnit3; +using Microsoft.AspNetCore.Identity; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Tests.UnitTests.AutoFixture; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security +{ + [TestFixture] + public class UmbracoPasswordHasherTests + { + // Technically MD5, HMACSHA384 & HMACSHA512 were also possible but opt in as opposed to historic defaults. + [Test] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "uB/pLEhhe1W7EtWMv/pSgg==1y8+aso9+h3AKRtJXlVYeg2TZKJUr64hccj82ZZ7Ksk=")] // Actually HMACSHA256 + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] // v4 site legacy password, with incorrect algorithm specified in database actually HMACSHA1 with password used as key. + [InlineAutoMoqData("SHA1", "Umbraco9Rocks!", "6tZGfG9NTxJJYp19Fac9og==zzRggqANxhb+CbD/VabEt8cIde8=")] // When SHA1 is set on machine key. + public void VerifyHashedPassword_WithValidLegacyPasswordHash_ReturnsSuccessRehashNeeded( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings{ HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + + [Test] + [InlineAutoMoqData("PBKDF2.ASPNETCORE.V3", "Umbraco9Rocks!", "AQAAAAEAACcQAAAAEDCrYcnIhHKr38yuchsDu6AFqqmLNvRooKObV25GC1LC1tLY+gWGU4xNug0lc17PHA==")] + public void VerifyHashedPassword_WithValidModernPasswordHash_ReturnsSuccess( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings { HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.Success, result); + } + + [Test] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "aB/cDeFaBcDefAbcD/EfaB==1y8+aso9+h3AKRtJXlVYeg2TZKJUr64hccj82ZZ7Ksk=")] + public void VerifyHashedPassword_WithIncorrectPassword_ReturnsFailed( + string algorithm, + string providedPassword, + string hashedPassword, + [Frozen] IJsonSerializer jsonSerializer, + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + Mock.Get(jsonSerializer) + .Setup(x => x.Deserialize(It.IsAny())) + .Returns(new PersistedPasswordSettings { HashAlgorithm = algorithm }); + + var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); + + Assert.AreEqual(PasswordVerificationResult.Failed, result); + } + + public class TestUserStub : UmbracoIdentityUser + { + public TestUserStub() => PasswordConfig = "not null or empty"; + } + } +} From 17266dacb1faf41c588e8ea24900d17874d81bc6 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Thu, 10 Mar 2022 16:25:47 +0100 Subject: [PATCH 04/42] New service and repo to get macros by alias --- .../Repositories/IMacroWithAliasRepository.cs | 13 +++++++++++++ .../Services/IMacroWithAliasService.cs | 17 +++++++++++++++++ .../UmbracoBuilder.Repositories.cs | 4 +++- .../UmbracoBuilder.Services.cs | 14 ++++++++++++-- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs create mode 100644 src/Umbraco.Core/Services/IMacroWithAliasService.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs new file mode 100644 index 0000000000..86fe5b49e0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + [Obsolete("This interface will be merged with IMacroRepository in Umbraco 11")] + public interface IMacroWithAliasRepository : IMacroRepository + { + IMacro GetByAlias(string alias); + IEnumerable GetAllByAlias(string[] aliases); + } +} diff --git a/src/Umbraco.Core/Services/IMacroWithAliasService.cs b/src/Umbraco.Core/Services/IMacroWithAliasService.cs new file mode 100644 index 0000000000..b34bada27d --- /dev/null +++ b/src/Umbraco.Core/Services/IMacroWithAliasService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + [Obsolete("This interface will be merged with IMacroService in Umbraco 11")] + public interface IMacroWithAliasService : IMacroService + { + /// + /// Gets a list of available objects by alias. + /// + /// + /// + IEnumerable GetAll(params string[] aliases); + } +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 966b54633b..7dc227f05f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -36,7 +36,9 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 915b815033..fa7453f24d 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -18,9 +18,9 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; -using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Services.Implement; +using Umbraco.Cms.Infrastructure.Templates; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -64,7 +64,16 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); + builder.Services.AddUnique(factory => new MacroService( + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService() + )); + builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); @@ -92,6 +101,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddSingleton(); builder.Services.AddUnique(); + builder.Services.AddUnique(); return builder; } From 2aa79c7494b7354575588b99436a1b4081408d7a Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Thu, 10 Mar 2022 16:26:16 +0100 Subject: [PATCH 05/42] Adding repository caching of macro definition by alias --- src/Umbraco.Core/Cache/MacroCacheRefresher.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index 77550b81d1..f5b0b21e7a 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -55,6 +55,7 @@ namespace Umbraco.Cms.Core.Cache if (macroRepoCache) { macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Alias)); // Repository caching of macro definition by alias } } From cbb19b1d930dc2d1927eea739180da5a48b36d29 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Thu, 10 Mar 2022 16:27:31 +0100 Subject: [PATCH 06/42] Implementations of the new interfaces --- .../Repositories/Implement/MacroRepository.cs | 30 +++++++++++++++++- .../Services/Implement/MacroService.cs | 31 ++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 535895e8ed..3da1991728 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -18,14 +18,16 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { - internal class MacroRepository : EntityRepositoryBase, IMacroRepository + internal class MacroRepository : EntityRepositoryBase, IMacroWithAliasRepository { private readonly IShortStringHelper _shortStringHelper; + private readonly IRepositoryCachePolicy _macroByAliasCachePolicy; public MacroRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IShortStringHelper shortStringHelper) : base(scopeAccessor, cache, logger) { _shortStringHelper = shortStringHelper; + _macroByAliasCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } protected override IMacro PerformGet(int id) @@ -68,6 +70,32 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return Get(id) != null; } + public IMacro GetByAlias(string alias) + { + return _macroByAliasCachePolicy.Get(alias, PerformGetByAlias, PerformGetAllByAlias); + } + + public IEnumerable GetAllByAlias(string[] aliases) + { + if (aliases.Any() == false) return base.GetMany(); + + return _macroByAliasCachePolicy.GetAll(aliases, PerformGetAllByAlias); + } + + private IMacro PerformGetByAlias(string alias) + { + var query = Query().Where(x => x.Alias.Equals(alias)); + return PerformGetByQuery(query).FirstOrDefault(); + } + + private IEnumerable PerformGetAllByAlias(params string[] aliases) + { + if (aliases.Any() == false) return base.GetMany(); + + var query = Query().WhereIn(x => x.Alias, aliases); + return PerformGetByQuery(query); + } + protected override IEnumerable PerformGetAll(params int[] ids) { return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs index a79d9fddce..5693db4403 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs @@ -1,31 +1,45 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.Services.Implement { /// /// Represents the Macro Service, which is an easy access to operations involving /// - internal class MacroService : RepositoryService, IMacroService + internal class MacroService : RepositoryService, IMacroWithAliasService { - private readonly IMacroRepository _macroRepository; + private readonly IMacroWithAliasRepository _macroRepository; private readonly IAuditRepository _auditRepository; public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IMacroRepository macroRepository, IAuditRepository auditRepository) + IMacroWithAliasRepository macroRepository, IAuditRepository auditRepository) : base(provider, loggerFactory, eventMessagesFactory) { _macroRepository = macroRepository; _auditRepository = auditRepository; } + [Obsolete("Use ctor injecting IMacroWithAliasRepository instead")] + public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, + IMacroRepository macroRepository, IAuditRepository auditRepository) + : this ( + provider, + loggerFactory, + eventMessagesFactory, + StaticServiceProvider.Instance.GetRequiredService(), + auditRepository) + { + } + /// /// Gets an object by its alias /// @@ -35,8 +49,7 @@ namespace Umbraco.Cms.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var q = Query().Where(x => x.Alias == alias); - return _macroRepository.Get(q).FirstOrDefault(); + return _macroRepository.GetByAlias(alias); } } @@ -61,6 +74,14 @@ namespace Umbraco.Cms.Core.Services.Implement } } + public IEnumerable GetAll(params string[] aliases) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return _macroRepository.GetAllByAlias(aliases); + } + } + public IMacro GetById(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) From c22b9dbb76a306e6c27e2178bd0cdc2dec3f15e6 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Thu, 10 Mar 2022 16:28:33 +0100 Subject: [PATCH 07/42] Adding a new parser to get the media references in a macro --- .../Templates/HtmlMacroParameterParser.cs | 145 ++++++++++++++++++ .../Templates/IHtmlMacroParameterParser.cs | 13 ++ 2 files changed, 158 insertions(+) create mode 100644 src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs create mode 100644 src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs new file mode 100644 index 0000000000..46fce0437e --- /dev/null +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Macros; + +namespace Umbraco.Cms.Infrastructure.Templates +{ + public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser + { + private readonly IMacroWithAliasService _macroService; + private readonly ILogger _logger; + private readonly ParameterEditorCollection _parameterEditors; + + public HtmlMacroParameterParser(IMacroWithAliasService macroService, ILogger logger, ParameterEditorCollection parameterEditors) + { + _macroService = macroService; + _logger = logger; + _parameterEditors = parameterEditors; + } + + /// + /// Parses out media UDIs from an HTML string based on embedded macro parameter values. + /// + /// + /// + public IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text) + { + // There may be more than one macro with the same alias on the page so using a tuple + var foundMacros = new List>>(); + + // This legacy ParseMacros() already finds the macros within a Rich Text Editor using regexes + // It seems to lowercase the macro parameter alias - so making the dictionary case insensitive + MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => foundMacros.Add(new Tuple>(macroAlias, new Dictionary(macroAttributes, StringComparer.OrdinalIgnoreCase)))); + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) + { + yield return umbracoEntityReference; + } + } + + /// + /// Parses out media UDIs from Macro Grid Control Parameters. + /// + /// + /// + public IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls) + { + var foundMacros = new List>>(); + + foreach (var macroGridControl in macroGridControls) + { + // Deserialise JSON of Macro Grid Control to a class + var gridMacro = macroGridControl.Value.ToObject(); + // Collect any macro parameters that contain the media udi format + if (gridMacro != null && gridMacro.MacroParameters != null && gridMacro.MacroParameters.Any()) + { + foundMacros.Add(new Tuple>(gridMacro.MacroAlias, gridMacro.MacroParameters)); + } + } + + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) + { + yield return umbracoEntityReference; + } + } + + private IEnumerable GetUmbracoEntityReferencesFromMacros(List>> macros) + { + var uniqueMacroAliases = macros.Select(f => f.Item1).Distinct(); + // TODO: Tracking Macro references + // Here we are finding the used macros' Udis (there should be a Related Macro relation type - but Relations don't accept 'Macro' as an option) + var foundMacroUmbracoEntityReferences = new List(); + // Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy + var macroConfigs = _macroService.GetAll(uniqueMacroAliases.ToArray()); + + foreach (var macro in macros) + { + var macroConfig = macroConfigs.FirstOrDefault(f => f.Alias == macro.Item1); + foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key))); + // Only do this if the macros actually have parameters + if (macroConfig.Properties != null && macroConfig.Properties.Keys.Any(f => f != "macroAlias")) + { + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors)) + { + yield return umbracoEntityReference; + } + } + } + } + + /// + /// Finds media UDIs in Macro Parameter Values by calling the GetReference method for all the Macro Parameter Editors for a particular macro. + /// + /// The parameters for the macro a dictionary of key/value strings + /// The macro configuration for this particular macro - contains the types of editors used for each parameter + /// A list of all the registered parameter editors used in the Umbraco implmentation - to look up the corresponding property editor for a macro parameter + /// + private IEnumerable GetUmbracoEntityReferencesFromMacroParameters(Dictionary macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors) + { + var foundUmbracoEntityReferences = new List(); + foreach (var parameter in macroConfig.Properties) + { + if (macroParameters.TryGetValue(parameter.Alias, out string parameterValue)) + { + var parameterEditorAlias = parameter.EditorAlias; + // Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter + var parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase)); + if (parameterEditor != null) + { + // Get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast as IDataValueReference to determine if 'it is' implemented for the editor + if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor) + { + foreach (var entityReference in parameterValueEditor.GetReferences(parameterValue)) + { + foundUmbracoEntityReferences.Add(entityReference); + } + } + else + { + _logger.LogInformation("{0} doesn't have a ValueEditor that implements IDataValueReference", parameterEditor.Alias); + } + } + } + } + + return foundUmbracoEntityReferences; + } + + // Poco class to deserialise the Json for a Macro Control + private class GridMacro + { + [JsonProperty("macroAlias")] + public string MacroAlias { get; set; } + + [JsonProperty("macroParamsDictionary")] + public Dictionary MacroParameters { get; set; } + } + } +} diff --git a/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs new file mode 100644 index 0000000000..df7aadb3f9 --- /dev/null +++ b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; + +namespace Umbraco.Cms.Infrastructure.Templates +{ + public interface IHtmlMacroParameterParser + { + IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text); + + IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroValues); + } +} From 92480b8446e498ffefe36fbaf83a42a65ed2d180 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Thu, 10 Mar 2022 16:29:56 +0100 Subject: [PATCH 08/42] Changes in the editors so we can track the items --- .../MultipleMediaPickerParameterEditor.cs | 57 +++++++++++++++++- .../PropertyEditors/GridPropertyEditor.cs | 58 +++++++++++++++++-- .../PropertyEditors/RichTextPropertyEditor.cs | 53 +++++++++++++++-- 3 files changed, 155 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs index d8f74b1b28..f4be58b753 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs @@ -1,5 +1,8 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -26,5 +29,55 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors { DefaultConfiguration.Add("multiPicker", "1"); } + + protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); + + internal class MultipleMediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference + { + private readonly IEntityService _entityService; + + public MultipleMediaPickerPropertyValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + { + _entityService = entityService; + } + + public IEnumerable GetReferences(object value) + { + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) + { + yield break; + } + + foreach (var udiStr in asString.Split(',')) + { + if (UdiParser.TryParse(udiStr, out Udi udi)) + { + yield return new UmbracoEntityReference(udi); + } + + // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis + if (int.TryParse(udiStr, out var id)) + { + Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectTypes.Media); + Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; + + if (guid != Guid.Empty) + { + yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); + } + + } + } + } + } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs index f149757919..c3d8be8f50 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; @@ -14,6 +15,8 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; +using Umbraco.Cms.Infrastructure.Templates; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -37,6 +40,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly RichTextEditorPastedImages _pastedImages; private readonly HtmlLocalLinkParser _localLinkParser; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlMacroParameterParser _macroParameterParser; public GridPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -45,7 +49,8 @@ namespace Umbraco.Cms.Core.PropertyEditors RichTextEditorPastedImages pastedImages, HtmlLocalLinkParser localLinkParser, IIOHelper ioHelper, - IImageUrlGenerator imageUrlGenerator) + IImageUrlGenerator imageUrlGenerator, + IHtmlMacroParameterParser macroParameterParser) : base(dataValueEditorFactory) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -54,6 +59,20 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _localLinkParser = localLinkParser; _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public GridPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IIOHelper ioHelper, + IImageUrlGenerator imageUrlGenerator) + : this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, pastedImages, localLinkParser, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService()) + { } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -74,6 +93,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor; private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlMacroParameterParser _macroParameterParser; public GridPropertyValueEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -85,7 +105,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IShortStringHelper shortStringHelper, IImageUrlGenerator imageUrlGenerator, IJsonSerializer jsonSerializer, - IIOHelper ioHelper) + IIOHelper ioHelper, + IHtmlMacroParameterParser macroParameterParser) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -96,6 +117,25 @@ namespace Umbraco.Cms.Core.PropertyEditors _mediaPickerPropertyValueEditor = dataValueEditorFactory.Create(attribute); _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public GridPropertyValueEditor( + IDataValueEditorFactory dataValueEditorFactory, + DataEditorAttribute attribute, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + IShortStringHelper shortStringHelper, + IImageUrlGenerator imageUrlGenerator, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + : this (dataValueEditorFactory, attribute, backOfficeSecurityAccessor, localizedTextService, + imageSourceParser, pastedImages, shortStringHelper, imageUrlGenerator, jsonSerializer, ioHelper, + StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -120,7 +160,7 @@ namespace Umbraco.Cms.Core.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var grid = DeserializeGridValue(rawJson, out var rtes, out _); + var grid = DeserializeGridValue(rawJson, out var rtes, out _, out _); var userId = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser?.Id ?? Constants.Security.SuperUserId; @@ -154,7 +194,7 @@ namespace Umbraco.Cms.Core.PropertyEditors if (val.IsNullOrWhiteSpace()) return string.Empty; - var grid = DeserializeGridValue(val, out var rtes, out _); + var grid = DeserializeGridValue(val, out var rtes, out _, out _); //process the rte values foreach (var rte in rtes.ToList()) @@ -168,7 +208,7 @@ namespace Umbraco.Cms.Core.PropertyEditors return grid; } - private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues) + private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues, out IEnumerable macroValues) { var grid = JsonConvert.DeserializeObject(rawJson); @@ -177,6 +217,9 @@ namespace Umbraco.Cms.Core.PropertyEditors richTextValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte"); mediaValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "media"); + // Find all the macros + macroValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "macro"); + return grid; } @@ -192,7 +235,7 @@ namespace Umbraco.Cms.Core.PropertyEditors if (rawJson.IsNullOrWhiteSpace()) yield break; - DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues); + DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues, out var macroValues); foreach (var umbracoEntityReference in richTextEditorValues.SelectMany(x => _richTextPropertyValueEditor.GetReferences(x.Value))) @@ -201,6 +244,9 @@ namespace Umbraco.Cms.Core.PropertyEditors foreach (var umbracoEntityReference in mediaValues.Where(x => x.Value.HasValues) .SelectMany(x => _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"]))) yield return umbracoEntityReference; + + foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromGridControlMacros(macroValues)) + yield return umbracoEntityReference; } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 1cfbc3449e..18c3fe0902 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -3,8 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; @@ -16,6 +15,8 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Infrastructure.Macros; +using Umbraco.Cms.Infrastructure.Templates; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -36,12 +37,13 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IHtmlMacroParameterParser _macroParameterParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IIOHelper _ioHelper; private readonly IImageUrlGenerator _imageUrlGenerator; /// - /// The constructor will setup the property editor based on the attribute if one is found + /// The constructor will setup the property editor based on the attribute if one is found. /// public RichTextPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -50,7 +52,8 @@ namespace Umbraco.Cms.Core.PropertyEditors HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IIOHelper ioHelper, - IImageUrlGenerator imageUrlGenerator) + IImageUrlGenerator imageUrlGenerator, + IHtmlMacroParameterParser macroParameterParser) : base(dataValueEditorFactory) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -59,6 +62,20 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _ioHelper = ioHelper; _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public RichTextPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IIOHelper ioHelper, + IImageUrlGenerator imageUrlGenerator) + : this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, localLinkParser, pastedImages, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -79,6 +96,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IHtmlMacroParameterParser _macroParameterParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly IHtmlSanitizer _htmlSanitizer; @@ -94,7 +112,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IImageUrlGenerator imageUrlGenerator, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - IHtmlSanitizer htmlSanitizer) + IHtmlSanitizer htmlSanitizer, + IHtmlMacroParameterParser macroParameterParser) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -103,6 +122,26 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _imageUrlGenerator = imageUrlGenerator; _htmlSanitizer = htmlSanitizer; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an HtmlMacroParameterParser instead")] + public RichTextPropertyValueEditor( + DataEditorAttribute attribute, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IImageUrlGenerator imageUrlGenerator, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + IHtmlSanitizer htmlSanitizer) + : this(attribute, backOfficeSecurityAccessor, localizedTextService, shortStringHelper, imageSourceParser, + localLinkParser, pastedImages, imageUrlGenerator, jsonSerializer, ioHelper, htmlSanitizer, + StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -182,6 +221,10 @@ namespace Umbraco.Cms.Core.PropertyEditors yield return new UmbracoEntityReference(udi); //TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs + //UPDATE: We are getting the Macros in 'FindUmbracoEntityReferencesFromEmbeddedMacros' - perhaps we just return the macro Udis here too or do they need their own relationAlias? + + foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromEmbeddedMacros(asString)) + yield return umbracoEntityReference; } } From ff2865c1dc8984290203780316683c243214a9ef Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Fri, 11 Mar 2022 10:41:04 +0000 Subject: [PATCH 09/42] Further enhancements for legacy password support. (#12124) * Further enhancements for legacy password support. For users - try new style passwords first and fallback on failure seeing as a valid modern password is the norm, rehash is only one time. For both users and members also deals with the fact that for useLegacyEncoding we could store any old thing in passwordConfig e.g. it's possible to get Umbraco8 to store "HMACSHA384" alongside the hash even though it's really HMACSHA1 with password used as key (try it out by tweaking machine key settings and setting useLegacyEncoding=true). Has behavioral breaking changes in LegacyPasswordSecurity as the code now expects consumers to to respect IsSupportedHashAlgorithm rather than ignoring it. * Less rushed removals --- .../Security/LegacyPasswordSecurity.cs | 81 ++++++---------- .../Security/MemberPasswordHasher.cs | 3 +- .../Security/UmbracoPasswordHasher.cs | 97 ++++++++++--------- .../Security/LegacyPasswordSecurityTests.cs | 18 ++-- .../Security/UmbracoPasswordHasherTests.cs | 58 +++++++++-- 5 files changed, 146 insertions(+), 111 deletions(-) diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index 35528a48ca..b8c7596b2d 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -8,12 +8,17 @@ namespace Umbraco.Cms.Core.Security { /// - /// Handles password hashing and formatting for legacy hashing algorithms + /// Handles password hashing and formatting for legacy hashing algorithms. /// + /// + /// Should probably be internal. + /// public class LegacyPasswordSecurity { + // TODO: Remove v11 // Used for tests [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] public string HashPasswordForStorage(string algorithmType, string password) { if (string.IsNullOrWhiteSpace(password)) @@ -24,13 +29,15 @@ namespace Umbraco.Cms.Core.Security return FormatPasswordForStorage(algorithmType, hashed, salt); } + // TODO: Remove v11 // Used for tests [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] public string FormatPasswordForStorage(string algorithmType, string hashedPassword, string salt) { - if (IsLegacySHA1Algorithm(algorithmType)) + if (!SupportHashAlgorithm(algorithmType)) { - return hashedPassword; + throw new InvalidOperationException($"{algorithmType} is not supported"); } return salt + hashedPassword; @@ -45,10 +52,15 @@ namespace Umbraco.Cms.Core.Security /// public bool VerifyPassword(string algorithm, string password, string dbPassword) { - if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); + if (string.IsNullOrWhiteSpace(dbPassword)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); + } if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix)) + { return false; + } try { @@ -61,7 +73,6 @@ namespace Umbraco.Cms.Core.Security //This can happen if the length of the password is wrong and a salt cannot be extracted. return false; } - } /// @@ -69,12 +80,13 @@ namespace Umbraco.Cms.Core.Security /// public bool VerifyLegacyHashedPassword(string password, string dbPassword) { - var hashAlgorith = new HMACSHA1 + var hashAlgorithm = new HMACSHA1 { //the legacy salt was actually the password :( Key = Encoding.Unicode.GetBytes(password) }; - var hashed = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); + + var hashed = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); return dbPassword == hashed; } @@ -87,6 +99,8 @@ namespace Umbraco.Cms.Core.Security /// /// // TODO: Do we need this method? We shouldn't be using this class to create new password hashes for storage + // TODO: Remove v11 + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] public string HashNewPassword(string algorithm, string newPassword, out string salt) { salt = GenerateSalt(); @@ -102,15 +116,15 @@ namespace Umbraco.Cms.Core.Security /// public string ParseStoredHashPassword(string algorithm, string storedString, out string salt) { - if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); - - // This is for the <= v4 hashing algorithm for which there was no salt - if (IsLegacySHA1Algorithm(algorithm)) + if (string.IsNullOrWhiteSpace(storedString)) { - salt = string.Empty; - return storedString; + throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); } + if (!SupportHashAlgorithm(algorithm)) + { + throw new InvalidOperationException($"{algorithm} is not supported"); + } var saltLen = GenerateSalt(); salt = storedString.Substring(0, saltLen.Length); @@ -133,12 +147,12 @@ namespace Umbraco.Cms.Core.Security /// private string HashPassword(string algorithmType, string pass, string salt) { - if (IsLegacySHA1Algorithm(algorithmType)) + if (!SupportHashAlgorithm(algorithmType)) { - return HashLegacySHA1Password(pass); + throw new InvalidOperationException($"{algorithmType} is not supported"); } - //This is the correct way to implement this (as per the sql membership provider) + // This is the correct way to implement this (as per the sql membership provider) var bytes = Encoding.Unicode.GetBytes(pass); var saltBytes = Convert.FromBase64String(salt); @@ -219,42 +233,7 @@ namespace Umbraco.Cms.Core.Security return true; } - // This is for the <= v4 hashing algorithm - if (IsLegacySHA1Algorithm(algorithm)) - { - return true; - } - return false; } - - private bool IsLegacySHA1Algorithm(string algorithm) => algorithm.InvariantEquals(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); - - /// - /// Hashes the password with the old v4 algorithm - /// - /// The password. - /// The encoded password. - private string HashLegacySHA1Password(string password) - { - using var hashAlgorithm = GetLegacySHA1Algorithm(password); - var hash = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); - return hash; - } - - /// - /// Returns the old v4 algorithm and settings - /// - /// - /// - private HashAlgorithm GetLegacySHA1Algorithm(string password) - { - return new HMACSHA1 - { - //the legacy salt was actually the password :( - Key = Encoding.Unicode.GetBytes(password) - }; - } - } } diff --git a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs index e470bf0a6c..02aef30217 100644 --- a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs @@ -71,6 +71,7 @@ namespace Umbraco.Cms.Core.Security return result; } } + // We need to check for clear text passwords from members as the first thing. This was possible in v8 :( else if (IsSuccessfulLegacyPassword(hashedPassword, providedPassword)) { @@ -138,7 +139,7 @@ namespace Umbraco.Cms.Core.Security } var result = LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName, providedPassword, hashedPassword); - return result || LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName, providedPassword, hashedPassword); + return result || LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword); } private static string DecryptLegacyPassword(string encryptedPassword, string algorithmName, string decryptionKey) diff --git a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs index da08bc8713..2847f13dc4 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Models.Membership; @@ -10,7 +11,6 @@ namespace Umbraco.Cms.Core.Security where TUser: UmbracoIdentityUser { private readonly IJsonSerializer _jsonSerializer; - private readonly PasswordHasher _aspnetV2PasswordHasher = new PasswordHasher(new V2PasswordHasherOptions()); public UmbracoPasswordHasher(LegacyPasswordSecurity legacyPasswordSecurity, IJsonSerializer jsonSerializer) { @@ -43,57 +43,64 @@ namespace Umbraco.Cms.Core.Security { if (user is null) { - throw new System.ArgumentNullException(nameof(user)); + throw new ArgumentNullException(nameof(user)); } - if (!user.PasswordConfig.IsNullOrWhiteSpace()) + try { - // check if the (legacy) password security supports this hash algorith and if so then use it - var deserialized = _jsonSerializer.Deserialize(user.PasswordConfig); - if (LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) + // Best case and most likely scenario, a modern hash supported by ASP.Net identity. + PasswordVerificationResult upstreamResult = base.VerifyHashedPassword(user, hashedPassword, providedPassword); + if (upstreamResult != PasswordVerificationResult.Failed) { - var result = LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword); - - //We need to special handle this case, apparently v8 still saves the user algorithm as {"hashAlgorithm":"HMACSHA256"}, when using legacy encoding and hasinging. - if (result == false) - { - result = LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword); - } - - return result - ? PasswordVerificationResult.SuccessRehashNeeded - : PasswordVerificationResult.Failed; - } - - // We will explicitly detect names here - // The default is PBKDF2.ASPNETCORE.V3: - // PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - // The underlying class only lets us change 2 things which is the version: options.CompatibilityMode and the iteration count - // The PBKDF2.ASPNETCORE.V2 settings are: - // PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. - - switch (deserialized.HashAlgorithm) - { - case Constants.Security.AspNetCoreV3PasswordHashAlgorithmName: - return base.VerifyHashedPassword(user, hashedPassword, providedPassword); - case Constants.Security.AspNetCoreV2PasswordHashAlgorithmName: - var legacyResult = _aspnetV2PasswordHasher.VerifyHashedPassword(user, hashedPassword, providedPassword); - if (legacyResult == PasswordVerificationResult.Success) - return PasswordVerificationResult.SuccessRehashNeeded; - return legacyResult; + return upstreamResult; } } - - // else go the default (v3) - return base.VerifyHashedPassword(user, hashedPassword, providedPassword); - } - - private class V2PasswordHasherOptions : IOptions - { - public PasswordHasherOptions Value => new PasswordHasherOptions + catch (FormatException) { - CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2 - }; + // hash wasn't a valid base64 encoded string, MS concat the salt bytes and hash bytes and base 64 encode both together. + // We however historically base 64 encoded the salt bytes and hash bytes separately then concat the strings so we got 2 sets of padding. + // both salt bytes and hash bytes lengths were not evenly divisible by 3 hence 2 sets of padding. + + // We could check upfront with TryFromBase64String, but not whilst we target netstandard 2.0 + // so might as well just deal with the exception. + } + + // At this point we either have a legacy password or a bad attempt. + + // Check the supported worst case scenario, a "useLegacyEncoding" password - HMACSHA1 but with password used as key so not unique for users sharing same password + // This was the standard for v4. + // Do this first because with useLegacyEncoding the algorithm stored in the database is irrelevant. + if (LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword)) + { + return PasswordVerificationResult.SuccessRehashNeeded; + } + + // For users we expect to know the historic algorithm. + // NOTE: MemberPasswordHasher subclasses this class to deal with the fact that PasswordConfig wasn't stored. + if (user.PasswordConfig.IsNullOrWhiteSpace()) + { + return PasswordVerificationResult.Failed; + } + + PersistedPasswordSettings deserialized; + try + { + deserialized = _jsonSerializer.Deserialize(user.PasswordConfig); + } + catch + { + return PasswordVerificationResult.Failed; + } + + if (!LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) + { + return PasswordVerificationResult.Failed; + } + + // Last chance must be HMACSHA256 or SHA1 + return LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword) + ? PasswordVerificationResult.SuccessRehashNeeded + : PasswordVerificationResult.Failed; } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs index 273823eec3..12c5f50d30 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs @@ -1,10 +1,14 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; +using System.Security.Cryptography; +using System.Text; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Security; +using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security @@ -15,7 +19,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security [Test] public void Check_Password_Hashed_Non_KeyedHashAlgorithm() { - IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == "SHA256"); + IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == "SHA1"); var passwordSecurity = new LegacyPasswordSecurity(); var pass = "ThisIsAHashedPassword"; @@ -45,14 +49,12 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Security [Test] public void Check_Password_Legacy_v4_SHA1() { - IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); - var passwordSecurity = new LegacyPasswordSecurity(); + const string clearText = "ThisIsAHashedPassword"; + var clearTextUnicodeBytes = Encoding.Unicode.GetBytes(clearText); + using var algorithm = new HMACSHA1(clearTextUnicodeBytes); + var dbPassword = Convert.ToBase64String(algorithm.ComputeHash(clearTextUnicodeBytes)); - var pass = "ThisIsAHashedPassword"; - var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out string salt); - var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); - - var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); + var result = new LegacyPasswordSecurity().VerifyLegacyHashedPassword(clearText, dbPassword); Assert.IsTrue(result); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs index aa6bb4156b..db4ec3392f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/UmbracoPasswordHasherTests.cs @@ -1,5 +1,9 @@ +using System; +using System.Security.Cryptography; +using System.Text; using AutoFixture.NUnit3; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Models.Membership; @@ -15,9 +19,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security // Technically MD5, HMACSHA384 & HMACSHA512 were also possible but opt in as opposed to historic defaults. [Test] [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "uB/pLEhhe1W7EtWMv/pSgg==1y8+aso9+h3AKRtJXlVYeg2TZKJUr64hccj82ZZ7Ksk=")] // Actually HMACSHA256 - [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] // v4 site legacy password, with incorrect algorithm specified in database actually HMACSHA1 with password used as key. [InlineAutoMoqData("SHA1", "Umbraco9Rocks!", "6tZGfG9NTxJJYp19Fac9og==zzRggqANxhb+CbD/VabEt8cIde8=")] // When SHA1 is set on machine key. - public void VerifyHashedPassword_WithValidLegacyPasswordHash_ReturnsSuccessRehashNeeded( + public void VerifyHashedPassword_ValidHashWithoutLegacyEncoding_ReturnsSuccessRehashNeeded( string algorithm, string providedPassword, string hashedPassword, @@ -34,10 +37,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); } - [Test] - [InlineAutoMoqData("PBKDF2.ASPNETCORE.V3", "Umbraco9Rocks!", "AQAAAAEAACcQAAAAEDCrYcnIhHKr38yuchsDu6AFqqmLNvRooKObV25GC1LC1tLY+gWGU4xNug0lc17PHA==")] - public void VerifyHashedPassword_WithValidModernPasswordHash_ReturnsSuccess( + [InlineAutoMoqData("HMACSHA1", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData("HMACSHA256", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData("FOOBARBAZQUX", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData("", "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + [InlineAutoMoqData(null, "Umbraco9Rocks!", "t0U8atXTX/efNCtTafukwZeIpr8=")] + public void VerifyHashedPassword_ValidHashWithLegacyEncoding_ReturnsSuccessRehashNeeded( string algorithm, string providedPassword, string hashedPassword, @@ -51,7 +57,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security var result = sut.VerifyHashedPassword(aUser, hashedPassword, providedPassword); - Assert.AreEqual(PasswordVerificationResult.Success, result); + Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); } [Test] @@ -73,6 +79,46 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security Assert.AreEqual(PasswordVerificationResult.Failed, result); } + [Test] + [AutoMoqData] + public void VerifyHashedPassword_WithIdentityV1OrV2StyleHash_ReturnsSuccessRehashNeeded( + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + var options = Options.Create(new PasswordHasherOptions + { + CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2 + }); + + var upstreamHasher = new PasswordHasher(options); + + const string password = "Umbraco9Rocks!"; + var identityV1Or2StyleHash = upstreamHasher.HashPassword(aUser, password); + var result = sut.VerifyHashedPassword(aUser, identityV1Or2StyleHash, password); + + Assert.AreEqual(PasswordVerificationResult.SuccessRehashNeeded, result); + } + + [Test] + [AutoMoqData] + public void VerifyHashedPassword_WithIdentityV3StyleHash_ReturnsSuccess( + TestUserStub aUser, + UmbracoPasswordHasher sut) + { + var options = Options.Create(new PasswordHasherOptions + { + CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV3 + }); + + var upstreamHasher = new PasswordHasher(options); + + const string password = "Umbraco9Rocks!"; + var identityV1Or2StyleHash = upstreamHasher.HashPassword(aUser, password); + var result = sut.VerifyHashedPassword(aUser, identityV1Or2StyleHash, password); + + Assert.AreEqual(PasswordVerificationResult.Success, result); + } + public class TestUserStub : UmbracoIdentityUser { public TestUserStub() => PasswordConfig = "not null or empty"; From cf9f4f39e4548db4eccb8d7178c7d97ee4fdbe1e Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 16 Mar 2022 10:30:02 +0100 Subject: [PATCH 10/42] Adds/Fixes xdoc comments --- .../Templates/HtmlMacroParameterParser.cs | 4 ++-- .../Templates/IHtmlMacroParameterParser.cs | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs index 46fce0437e..a4b177209e 100644 --- a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -28,7 +28,7 @@ namespace Umbraco.Cms.Infrastructure.Templates /// /// Parses out media UDIs from an HTML string based on embedded macro parameter values. /// - /// + /// HTML string /// public IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text) { @@ -45,7 +45,7 @@ namespace Umbraco.Cms.Infrastructure.Templates } /// - /// Parses out media UDIs from Macro Grid Control Parameters. + /// Parses out media UDIs from Macro Grid Control parameters. /// /// /// diff --git a/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs index df7aadb3f9..c0abb5d2b3 100644 --- a/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs +++ b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs @@ -4,10 +4,24 @@ using Umbraco.Cms.Core.Models.Editors; namespace Umbraco.Cms.Infrastructure.Templates { + /// + /// Provides methods to parse referenced entities as Macro parameters. + /// + public interface IHtmlMacroParameterParser { + /// + /// Parses out media UDIs from an HTML string based on embedded macro parameter values. + /// + /// HTML string + /// IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text); - IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroValues); + /// + /// Parses out media UDIs from Macro Grid Control parameters. + /// + /// + /// + IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls); } } From 22b77713051cab45e2b83832bf1ba17d61f78447 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 16 Mar 2022 14:15:46 +0100 Subject: [PATCH 11/42] Changing the way we implement the new Macro service and repository --- .../UmbracoBuilder.Repositories.cs | 5 +-- .../UmbracoBuilder.Services.cs | 11 +----- .../Services/Implement/MacroService.cs | 35 ++++++++----------- .../Templates/HtmlMacroParameterParser.cs | 12 +++++-- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 7dc227f05f..13196c1879 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; @@ -36,9 +35,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(factory => factory.GetRequiredService()); builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(factory => factory.GetRequiredService()); - builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index fa7453f24d..157d49fd39 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -64,16 +64,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(factory => new MacroService( - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService() - )); - builder.Services.AddUnique(factory => factory.GetRequiredService()); - builder.Services.AddUnique(factory => factory.GetRequiredService()); - + builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs index 5693db4403..1175c08971 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs @@ -1,14 +1,12 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Core.Services.Implement { @@ -17,29 +15,16 @@ namespace Umbraco.Cms.Core.Services.Implement /// internal class MacroService : RepositoryService, IMacroWithAliasService { - private readonly IMacroWithAliasRepository _macroRepository; + private readonly IMacroRepository _macroRepository; private readonly IAuditRepository _auditRepository; - public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IMacroWithAliasRepository macroRepository, IAuditRepository auditRepository) + public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMacroRepository macroRepository, IAuditRepository auditRepository) : base(provider, loggerFactory, eventMessagesFactory) { _macroRepository = macroRepository; _auditRepository = auditRepository; } - [Obsolete("Use ctor injecting IMacroWithAliasRepository instead")] - public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IMacroRepository macroRepository, IAuditRepository auditRepository) - : this ( - provider, - loggerFactory, - eventMessagesFactory, - StaticServiceProvider.Instance.GetRequiredService(), - auditRepository) - { - } - /// /// Gets an object by its alias /// @@ -47,9 +32,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// An object public IMacro GetByAlias(string alias) { + if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) + { + return GetAll().First(); + } + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _macroRepository.GetByAlias(alias); + return macroWithAliasRepository.GetByAlias(alias); } } @@ -76,9 +66,14 @@ namespace Umbraco.Cms.Core.Services.Implement public IEnumerable GetAll(params string[] aliases) { + if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) + { + return GetAll(); + } + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _macroRepository.GetAllByAlias(aliases); + return macroWithAliasRepository.GetAllByAlias(aliases); } } diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs index a4b177209e..2e1f86fc1b 100644 --- a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -14,11 +14,11 @@ namespace Umbraco.Cms.Infrastructure.Templates { public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser { - private readonly IMacroWithAliasService _macroService; + private readonly IMacroService _macroService; private readonly ILogger _logger; private readonly ParameterEditorCollection _parameterEditors; - public HtmlMacroParameterParser(IMacroWithAliasService macroService, ILogger logger, ParameterEditorCollection parameterEditors) + public HtmlMacroParameterParser(IMacroService macroService, ILogger logger, ParameterEditorCollection parameterEditors) { _macroService = macroService; _logger = logger; @@ -72,12 +72,18 @@ namespace Umbraco.Cms.Infrastructure.Templates private IEnumerable GetUmbracoEntityReferencesFromMacros(List>> macros) { + + if (_macroService is not IMacroWithAliasService macroWithAliasService) + { + yield break; + } + var uniqueMacroAliases = macros.Select(f => f.Item1).Distinct(); // TODO: Tracking Macro references // Here we are finding the used macros' Udis (there should be a Related Macro relation type - but Relations don't accept 'Macro' as an option) var foundMacroUmbracoEntityReferences = new List(); // Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy - var macroConfigs = _macroService.GetAll(uniqueMacroAliases.ToArray()); + var macroConfigs = macroWithAliasService.GetAll(uniqueMacroAliases.ToArray()); foreach (var macro in macros) { From 5d08caae8c699faf865d609ac40e9f559ea4a6c3 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 16 Mar 2022 14:18:24 +0100 Subject: [PATCH 12/42] Cleanup --- .../Repositories/IMacroWithAliasRepository.cs | 1 + src/Umbraco.Core/Services/IMacroService.cs | 9 +-------- src/Umbraco.Core/Services/IMacroWithAliasService.cs | 4 ++-- .../Templates/IHtmlMacroParameterParser.cs | 1 - 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs index 86fe5b49e0..f6cd27ad60 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs @@ -8,6 +8,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories public interface IMacroWithAliasRepository : IMacroRepository { IMacro GetByAlias(string alias); + IEnumerable GetAllByAlias(string[] aliases); } } diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs index c4bc34997f..e1eb97ac00 100644 --- a/src/Umbraco.Core/Services/IMacroService.cs +++ b/src/Umbraco.Core/Services/IMacroService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models; @@ -17,13 +17,6 @@ namespace Umbraco.Cms.Core.Services /// An object IMacro GetByAlias(string alias); - ///// - ///// Gets a list all available objects - ///// - ///// Optional array of aliases to limit the results - ///// An enumerable list of objects - //IEnumerable GetAll(params string[] aliases); - IEnumerable GetAll(); IEnumerable GetAll(params int[] ids); diff --git a/src/Umbraco.Core/Services/IMacroWithAliasService.cs b/src/Umbraco.Core/Services/IMacroWithAliasService.cs index b34bada27d..6e72777bfa 100644 --- a/src/Umbraco.Core/Services/IMacroWithAliasService.cs +++ b/src/Umbraco.Core/Services/IMacroWithAliasService.cs @@ -10,8 +10,8 @@ namespace Umbraco.Cms.Core.Services /// /// Gets a list of available objects by alias. /// - /// - /// + /// Optional array of aliases to limit the results + /// An enumerable list of objects IEnumerable GetAll(params string[] aliases); } } diff --git a/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs index c0abb5d2b3..6e484cc30a 100644 --- a/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs +++ b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs @@ -7,7 +7,6 @@ namespace Umbraco.Cms.Infrastructure.Templates /// /// Provides methods to parse referenced entities as Macro parameters. /// - public interface IHtmlMacroParameterParser { /// From 5bebf98339e73e559cc01c9ddb1cb9e253b536bf Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 16 Mar 2022 14:19:25 +0100 Subject: [PATCH 13/42] Test + fix --- .../Repositories/Implement/MacroRepository.cs | 12 +++++++++--- .../Services/MacroServiceTests.cs | 17 +++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 3da1991728..21461effe9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -77,7 +77,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public IEnumerable GetAllByAlias(string[] aliases) { - if (aliases.Any() == false) return base.GetMany(); + if (aliases.Any() == false) + { + return base.GetMany(); + } return _macroByAliasCachePolicy.GetAll(aliases, PerformGetAllByAlias); } @@ -90,9 +93,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement private IEnumerable PerformGetAllByAlias(params string[] aliases) { - if (aliases.Any() == false) return base.GetMany(); + if (aliases.Any() == false) + { + return base.GetMany(); + } - var query = Query().WhereIn(x => x.Alias, aliases); + var query = Query().Where(x => aliases.Contains(x.Alias)); return PerformGetByQuery(query); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs index 75dae7515b..621762917d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -24,7 +23,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class MacroServiceTests : UmbracoIntegrationTest { - private IMacroService MacroService => GetRequiredService(); + [Obsolete("After merging IMacroWithAliasService interface with IMacroService in Umbraco 11, this should go back to just being GetRequiredService()")] + private IMacroWithAliasService MacroService => GetRequiredService() as IMacroWithAliasService; [SetUp] public void SetupTest() @@ -52,6 +52,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual("Test1", macro.Name); } + [Test] + public void Can_Get_By_Aliases() + { + // Act + IEnumerable macros = MacroService.GetAll("test1", "test2"); + + // Assert + Assert.IsNotNull(macros); + Assert.AreEqual(2, macros.Count()); + Assert.AreEqual("Test1", macros.ToArray()[0].Name); + Assert.AreEqual("Test2", macros.ToArray()[1].Name); + } + [Test] public void Can_Get_All() { From 30a30731ec9fafb277bb65447a6ce545df617462 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 17 Mar 2022 14:52:15 +0100 Subject: [PATCH 14/42] Encode path (#12132) Co-authored-by: Nikolaj Geisle --- .../Controllers/ImagesController.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index a10d524c03..327884689e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -1,9 +1,11 @@ using System; using System.IO; +using System.Web; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -53,20 +55,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public IActionResult GetResized(string imagePath, int width) { - var ext = Path.GetExtension(imagePath); - + // We have to use HttpUtility to encode the path here, for non-ASCII characters + // We cannot use the WebUtility, as we only want to encode the path, and not the entire string + var encodedImagePath = HttpUtility.UrlPathEncode(imagePath); + + + var ext = Path.GetExtension(encodedImagePath); + // check if imagePath is local to prevent open redirect - if (!Uri.IsWellFormedUriString(imagePath, UriKind.Relative)) + if (!Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) { return Unauthorized(); } - + // we need to check if it is an image by extension if (_imageUrlGenerator.IsSupportedImageFormat(ext) == false) { return NotFound(); } - + // redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file DateTimeOffset? imageLastModified = null; try @@ -82,7 +89,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; - var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) + var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(encodedImagePath) { Width = width, ImageCropMode = ImageCropMode.Max, From c7e45ae13a84422b04f789851a8b657c3b34f7dc Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 17 Mar 2022 14:52:15 +0100 Subject: [PATCH 15/42] Encode path (#12132) Co-authored-by: Nikolaj Geisle --- .../Controllers/ImagesController.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index a10d524c03..327884689e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -1,9 +1,11 @@ using System; using System.IO; +using System.Web; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -53,20 +55,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// public IActionResult GetResized(string imagePath, int width) { - var ext = Path.GetExtension(imagePath); - + // We have to use HttpUtility to encode the path here, for non-ASCII characters + // We cannot use the WebUtility, as we only want to encode the path, and not the entire string + var encodedImagePath = HttpUtility.UrlPathEncode(imagePath); + + + var ext = Path.GetExtension(encodedImagePath); + // check if imagePath is local to prevent open redirect - if (!Uri.IsWellFormedUriString(imagePath, UriKind.Relative)) + if (!Uri.IsWellFormedUriString(encodedImagePath, UriKind.Relative)) { return Unauthorized(); } - + // we need to check if it is an image by extension if (_imageUrlGenerator.IsSupportedImageFormat(ext) == false) { return NotFound(); } - + // redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file DateTimeOffset? imageLastModified = null; try @@ -82,7 +89,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; - var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) + var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(encodedImagePath) { Width = width, ImageCropMode = ImageCropMode.Max, From 608a65147fbfacbb6f4235a45fa6e4f36306c84f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 18 Mar 2022 08:02:44 +0100 Subject: [PATCH 16/42] Fixes --- .../Services/Implement/MacroService.cs | 7 ++++--- .../Templates/HtmlMacroParameterParser.cs | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs index 1175c08971..e7770e018f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Core.Services.Implement { if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) { - return GetAll().First(); + return GetAll().FirstOrDefault(x=>x.Alias == alias); } using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -68,7 +68,8 @@ namespace Umbraco.Cms.Core.Services.Implement { if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) { - return GetAll(); + var hashset = new HashSet(aliases); + return GetAll().Where(x=> hashset.Contains(x.Alias)); } using (var scope = ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs index 2e1f86fc1b..2b4786bd0a 100644 --- a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -88,6 +88,10 @@ namespace Umbraco.Cms.Infrastructure.Templates foreach (var macro in macros) { var macroConfig = macroConfigs.FirstOrDefault(f => f.Alias == macro.Item1); + if (macroConfig is null) + { + continue; + } foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key))); // Only do this if the macros actually have parameters if (macroConfig.Properties != null && macroConfig.Properties.Keys.Any(f => f != "macroAlias")) From 3044f8df0420cea585f3e8a8b327c517a62d4e30 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Fri, 18 Mar 2022 11:21:49 +0100 Subject: [PATCH 17/42] Item tracking fixes (#12146) * Cleanup; Fix lang keys * Documentation * Typos * Distinct the results * Changed GetPagedRelationsForItems to GetPagedRelationsForItem as we would only expect a single id to be passed when calling this + fix more docs * Changed to the correct reference * Unused code * Only load references when info tab is clicked Co-authored-by: Bjarke Berg --- .../ITrackedReferencesRepository.cs | 38 +++++++-- .../Services/ITrackedReferencesService.cs | 29 ++++++- .../Persistence/NPocoSqlExtensions.cs | 6 ++ .../Implement/RelationRepository.cs | 33 -------- .../Implement/TrackedReferencesRepository.cs | 83 ++++++++++--------- .../Implement/TrackedReferencesService.cs | 23 +++-- .../Controllers/MediaController.cs | 2 +- .../TrackedReferencesController.cs | 43 +++++----- .../content/umbcontentnodeinfo.directive.js | 7 ++ .../umbtrackedreferences.component.js | 4 +- .../resources/trackedreferences.resource.js | 2 +- .../content/umb-content-node-info.html | 2 +- .../umb-tracked-references-table.html | 4 +- .../references/umb-tracked-references.html | 10 +-- .../src/views/users/user.controller.js | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 10 +-- .../umbraco/config/lang/en_us.xml | 14 +--- 17 files changed, 175 insertions(+), 137 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs index 42746a9565..e6ca8eaa50 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -1,14 +1,42 @@ -using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Persistence.Repositories { public interface ITrackedReferencesRepository { - IEnumerable GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords); - IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords); - IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords); + /// + /// 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 page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of the items with reference to the current item. + /// An enumerable list of objects. + IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + + /// + /// 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 page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of the items in any kind of relation. + /// An enumerable list of objects. + IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + + /// + /// Gets a page of the descending items that have any references, given a parent id. + /// + /// The unique identifier of the parent to retrieve descendants for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of descending items. + /// An enumerable list of objects. + IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); } } diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs index eee8a324df..dea99c0f6d 100644 --- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -4,12 +4,35 @@ namespace Umbraco.Cms.Core.Services { public interface ITrackedReferencesService { - PagedResult GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency); - + /// + /// 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 page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. + PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency); + /// + /// Gets a paged result of the descending items that have any references, given a parent id. + /// + /// The unique identifier of the parent to retrieve descendants for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency); - + /// + /// Gets a paged result of items used in any kind of relation from selected integer ids. + /// + /// The identifiers of the entities to check for relations. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency); } } diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs index 6b7c34dc15..47cca58ce2 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs @@ -659,6 +659,12 @@ namespace Umbraco.Extensions return sql; } + public static Sql SelectDistinct(this Sql sql, params object[] columns) + { + sql.Append("SELECT DISTINCT " + string.Join(", ", columns)); + return sql; + } + //this.Append("SELECT " + string.Join(", ", columns), new object[0]); /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index a18782ca82..7ba20d1db5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -198,22 +198,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement }); } - public IEnumerable GetPagedParentEntitiesByChildIds(int[] childIds, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes) - { - return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => - { - SqlJoinRelations(sql); - - sql.WhereIn(rel => rel.ChildId, childIds); - sql.WhereAny(s => s.WhereIn(rel => rel.ParentId, childIds), s => s.WhereNotIn(node => node.NodeId, childIds)); - - if (relationTypes != null && relationTypes.Any()) - { - sql.WhereIn(rel => rel.RelationType, relationTypes); - } - }); - } - public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) { return GetPagedChildEntitiesByParentId(parentId, pageIndex, pageSize, out totalRecords, new int[0], entityTypes); @@ -241,21 +225,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement }); } - public IEnumerable GetPagedEntitiesForItemsInRelation(int[] itemIds, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) - { - return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => - { - SqlJoinRelations(sql); - - sql.WhereIn(rel => rel.ChildId, itemIds); - sql.Where((rel, node) => rel.ChildId == node.NodeId); - sql.Where(type => type.IsDependency); - }); - } - - - - public void Save(IEnumerable relations) { foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity)) @@ -475,8 +444,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement [Column(Name = "contentTypeName")] public string ChildContentTypeName { get; set; } - - [Column(Name = "relationTypeName")] public string RelationTypeName { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs index f5fb945464..0e70d47cbf 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; @@ -20,10 +19,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement _scopeAccessor = scopeAccessor; } - public IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, - bool filterMustBeIsDependency, out long totalRecords) + /// + /// Gets a page of items used in any kind of relation from selected integer ids. + /// + public IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { - var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[pn].[id] as nodeId", "[pn].[uniqueId] as nodeKey", "[pn].[text] as nodeName", @@ -36,12 +37,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") - .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType") - .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" ) - .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" ) - .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c") - .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct") - .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn"); + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn"); if (ids.Any()) { @@ -57,13 +58,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } - public IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, - out long totalRecords) + /// + /// Gets a page of the descending items that have any references, given a parent id. + /// + public IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { var syntax = _scopeAccessor.AmbientScope.Database.SqlContext.SqlSyntax; @@ -73,13 +76,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .From("node") .Where(x => x.NodeId == parentId, "node"); - // Gets the descendants of the parent node Sql subQuery; if (_scopeAccessor.AmbientScope.Database.DatabaseType.IsSqlCe()) { - // SqlCE do not support nested selects that returns a scalar. So we need to do this in multiple queries + // SqlCE does not support nested selects that returns a scalar. So we need to do this in multiple queries var pathForLike = _scopeAccessor.AmbientScope.Database.ExecuteScalar(subsubQuery); @@ -96,10 +98,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .WhereLike(x => x.Path, subsubQuery); } - - // Get all relations where parent is in the sub query - var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[pn].[id] as nodeId", "[pn].[uniqueId] as nodeKey", "[pn].[text] as nodeName", @@ -112,29 +112,35 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") - .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType") - .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" ) - .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" ) - .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c") - .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct") - .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn") + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn") .WhereIn((System.Linq.Expressions.Expression>)(x => x.NodeId), subQuery, "pn"); + if (filterMustBeIsDependency) { sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); } + // Ordering is required for paging sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } - public IEnumerable GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) + /// + /// Gets a page of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + public IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { - var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[cn].[id] as nodeId", "[cn].[uniqueId] as nodeKey", "[cn].[text] as nodeName", @@ -147,17 +153,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") - .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType") - .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" ) - .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" ) - .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"cn", aliasRight:"c") - .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct") - .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn"); - - if (ids.Any()) - { - sql = sql.Where(x => ids.Contains(x.NodeId), "pn"); - } + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "cn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn") + .Where(x => x.NodeId == id, "pn"); if (filterMustBeIsDependency) { @@ -168,14 +170,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } private RelationItem MapDtoToEntity(RelationItemDto dto) { - var type = ObjectTypes.GetUdiType(dto.ChildNodeObjectType); return new RelationItem() { NodeId = dto.ChildNodeId, diff --git a/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs b/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs index 9a4cc8860e..ec22e1095c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs @@ -1,4 +1,3 @@ -using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; @@ -18,22 +17,32 @@ namespace Umbraco.Cms.Core.Services.Implement _entityService = entityService; } - public PagedResult GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) + /// + /// Gets a paged result of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); - var items = _trackedReferencesRepository.GetPagedRelationsForItems(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); + var items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex+1, pageSize) { Items = items }; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } + /// + /// Gets a paged result of items used in any kind of relation from selected integer ids. + /// public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); - var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); + var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex+1, pageSize) { Items = items }; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } + /// + /// Gets a paged result of the descending items that have any references, given a parent id. + /// public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); @@ -44,7 +53,7 @@ namespace Umbraco.Cms.Core.Services.Implement pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex+1, pageSize) { Items = items }; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index d8fa641891..c4328da2d4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -1082,7 +1082,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return new ActionResult(toMove); } - [Obsolete("Please use TrackedReferencesController.GetPagedReferences() instead. Scheduled for removal in V11.")] + [Obsolete("Please use TrackedReferencesController.GetPagedRelationsForItem() instead. Scheduled for removal in V11.")] public PagedResult GetPagedReferences(int id, string entityType, int pageNumber = 1, int pageSize = 100) { if (pageNumber <= 0 || pageSize <= 0) diff --git a/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs b/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs index 2cef8d61af..aa1a0ee86e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs @@ -1,12 +1,7 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.BackOffice.ModelBinders; using Umbraco.Cms.Web.Common.Attributes; @@ -19,28 +14,36 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public class TrackedReferencesController : BackOfficeNotificationsController { private readonly ITrackedReferencesService _relationService; - private readonly IEntityService _entityService; - public TrackedReferencesController(ITrackedReferencesService relationService, - IEntityService entityService) + public TrackedReferencesController(ITrackedReferencesService relationService) { _relationService = relationService; - _entityService = entityService; } - // Used by info tabs on content, media etc. So this is basically finding childs of relations. - public ActionResult> GetPagedReferences(int id, int pageNumber = 1, - int pageSize = 100, bool filterMustBeIsDependency = false) + /// + /// Gets a page list of tracked references for the current item, so you can see where an item is being used. + /// + /// + /// Used by info tabs on content, media etc. and for the delete and unpublish of single items. + /// This is basically finding parents of relations. + /// + public ActionResult> GetPagedReferences(int id, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = false) { if (pageNumber <= 0 || pageSize <= 0) { return BadRequest("Both pageNumber and pageSize must be greater than zero"); } - return _relationService.GetPagedRelationsForItems(new []{id}, pageNumber - 1, pageSize, filterMustBeIsDependency); + return _relationService.GetPagedRelationsForItem(id, pageNumber - 1, pageSize, filterMustBeIsDependency); } - // Used on delete, finds + /// + /// Gets a page list of the child nodes of the current item used in any kind of relation. + /// + /// + /// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any kind of relation. + /// This is basically finding the descending items which are children in relations. + /// public ActionResult> GetPagedDescendantsInReferences(int parentId, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = true) { if (pageNumber <= 0 || pageSize <= 0) @@ -48,12 +51,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return BadRequest("Both pageNumber and pageSize must be greater than zero"); } - return _relationService.GetPagedDescendantsInReferences(parentId, pageNumber - 1, pageSize, filterMustBeIsDependency); - } - // Used by unpublish content. So this is basically finding parents of relations. + /// + /// Gets a page list of the items used in any kind of relation from selected integer ids. + /// + /// + /// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view). + /// This is basically finding children of relations. + /// [HttpGet] [HttpPost] public ActionResult> GetPagedReferencedItems([FromJsonPath] int[] ids, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = true) @@ -64,8 +71,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } return _relationService.GetPagedItemsWithRelations(ids, pageNumber - 1, pageSize, filterMustBeIsDependency); - } } - } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 83292251da..501ea9f81a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -12,6 +12,7 @@ scope.publishStatus = []; scope.currentVariant = null; scope.currentUrls = []; + scope.loadingReferences = false; scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; scope.allowChangeDocumentType = false; @@ -229,6 +230,10 @@ }); } + + function loadReferences(){ + scope.loadingReferences = true; + } function loadRedirectUrls() { scope.loadingRedirectUrls = true; //check if Redirect URL Management is enabled @@ -335,6 +340,7 @@ loadRedirectUrls(); setNodePublishStatus(); formatDatesToLocal(); + loadReferences(); } else { isInfoTab = false; } @@ -352,6 +358,7 @@ loadRedirectUrls(); setNodePublishStatus(); formatDatesToLocal(); + loadReferences(); } updateCurrentUrls(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js index 000e87146c..d4287be9a8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/references/umbtrackedreferences.component.js @@ -16,8 +16,8 @@ function onInit() { - vm.referencesTitle = this.hideNoneDependencies ? "The following items depends on this" : "Referenced by the following items"; - vm.referencedDescendantsTitle = this.hideNoneDependencies ? "The following descending items has dependencies" : "The following descending items are referenced"; + vm.referencesTitle = this.hideNoneDependencies ? "The following items depend on this" : "Referenced by the following items"; + vm.referencedDescendantsTitle = this.hideNoneDependencies ? "The following descending items have dependencies" : "The following descending items are referenced"; localizationService.localize(this.hideNoneDependencies ? "references_labelDependsOnThis" : "references_labelUsedByItems").then(function (value) { vm.referencesTitle = value; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js index cd64c89589..d64951a6d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js @@ -162,7 +162,7 @@ function trackedReferencesResource($q, $http, umbRequestHelper) { $http.post( umbRequestHelper.getApiUrl( "trackedReferencesApiBaseUrl", - "getPagedReferencedItems", + "GetPagedReferencedItems", query), { ids: ids, diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 6429e39db6..1c7545f9ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -21,7 +21,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html index afc8f9a3e6..d09bc23318 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html @@ -7,8 +7,8 @@
-
Node Name
-
Type Name
+
Node Name
+
Type Name
Type
Relation
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html index fd788cc598..9e08c5fbae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html @@ -26,11 +26,11 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 79164a2457..684ce6d2f0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -374,7 +374,7 @@ } function enableUser() { - vm.enableUserButtonState = "busfy"; + vm.enableUserButtonState = "busy"; usersResource.enableUsers([vm.user.id]).then(function (data) { vm.user.userState = "Active"; setUserDisplayState(); diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 6b5d301c5f..819b7f8f70 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -788,6 +788,7 @@ New Next No + Node Name of Off OK @@ -829,6 +830,7 @@ Submit Success Type + Type Name Type to search... under Up @@ -2388,6 +2390,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Parent Child Count + Relation Relations Created Comment @@ -2468,18 +2471,13 @@ To manage your website, simply open the Umbraco backoffice and start adding cont References This Data Type has no references. + This item has no references. Used in Document Types Used in Media Types Used in Member Types Used by - Used in Documents - Used in Members - Used in Media Items in use Descendants in use - One or more of this item's descendants is being used in a media item. - One or more of this item's descendants is being used in a content item. - One or more of this item's descendants is being used in a member. This item or its descendants is being used. Deletion can lead to broken links on your website. This item or its descendants is being used. Unpublishing can lead to broken links on your website. Please take the appropriate actions. This item or its descendants is being used. Therefore, deletion has been disabled. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index e056647ebc..3c416bfb02 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -809,7 +809,7 @@ New Next No - Node Name + Node Name of Off OK @@ -850,7 +850,7 @@ Submit Success Type - Type Name + Type Name Type to search... under Up @@ -2559,16 +2559,10 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Referenced by the following Member Types Referenced by Referenced by the following items - The following items depends on this - Referenced by the following Documents - Referenced by the following Members - Referenced by the following Media + The following items depend on this The following items are referenced The following descending items are referenced - The following descending items has dependencies - One or more of this item's descendants is being referenced in a media item. - One or more of this item's descendants is being referenced in a content item. - One or more of this item's descendants is being referenced in a member. + The following descending items have dependencies This item or its descendants is being referenced. Deletion can lead to broken links on your website. This item or its descendants is being referenced. Unpublishing can lead to broken links on your website. Please take the appropriate actions. This item or its descendants is being referenced. Therefore, deletion has been disabled. From 0f3aa320be1b1b434b91bc9062c2e0ab8f8afd39 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Fri, 18 Mar 2022 12:54:19 +0100 Subject: [PATCH 18/42] Fix caching & contentPicker in parameter --- src/Umbraco.Core/Cache/MacroCacheRefresher.cs | 8 ++- .../MultipleContentPickerParameterEditor.cs | 57 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index f5b0b21e7a..e1b65e2a32 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -46,6 +46,11 @@ namespace Umbraco.Cms.Core.Cache { var payloads = Deserialize(json); + Refresh(payloads); + } + + public override void Refresh(JsonPayload[] payloads) + { foreach (var payload in payloads) { foreach (var alias in GetCacheKeysForAlias(payload.Alias)) @@ -59,8 +64,9 @@ namespace Umbraco.Cms.Core.Cache } } - base.Refresh(json); + base.Refresh(payloads); } + #endregion #region Json diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs index 048ad40ac0..a7ae997eeb 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs @@ -1,5 +1,10 @@ -using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -28,5 +33,55 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors DefaultConfiguration.Add("minNumber",0 ); DefaultConfiguration.Add("maxNumber", 0); } + + protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); + + internal class MultipleContentPickerParamateterValueEditor : DataValueEditor, IDataValueReference + { + private readonly IEntityService _entityService; + + public MultipleContentPickerParamateterValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + { + _entityService = entityService; + } + + public IEnumerable GetReferences(object value) + { + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) + { + yield break; + } + + foreach (var udiStr in asString.Split(',')) + { + if (UdiParser.TryParse(udiStr, out Udi udi)) + { + yield return new UmbracoEntityReference(udi); + } + + // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis + if (int.TryParse(udiStr, out var id)) + { + Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectTypes.Document); + Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; + + if (guid != Guid.Empty) + { + yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); + } + + } + } + } + } } } From 8e78de4dc5726f4a5343e2eee05c1a99d66e61ce Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Fri, 18 Mar 2022 14:24:04 +0100 Subject: [PATCH 19/42] Abstract ParameterValueEditors --- .../MultipleContentPickerParameterEditor.cs | 53 ++-------------- .../MultipleMediaPickerParameterEditor.cs | 47 ++------------ ...ultiplePickerParamateterValueEditorBase.cs | 61 +++++++++++++++++++ 3 files changed, 71 insertions(+), 90 deletions(-) create mode 100644 src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs index a7ae997eeb..4d88431e7c 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -36,52 +31,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); - internal class MultipleContentPickerParamateterValueEditor : DataValueEditor, IDataValueReference + internal class MultipleContentPickerParamateterValueEditor : MultiplePickerParamateterValueEditorBase { - private readonly IEntityService _entityService; - - public MultipleContentPickerParamateterValueEditor( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper, - DataEditorAttribute attribute, - IEntityService entityService) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + public MultipleContentPickerParamateterValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) { - _entityService = entityService; } - public IEnumerable GetReferences(object value) - { - var asString = value is string str ? str : value?.ToString(); - - if (string.IsNullOrEmpty(asString)) - { - yield break; - } - - foreach (var udiStr in asString.Split(',')) - { - if (UdiParser.TryParse(udiStr, out Udi udi)) - { - yield return new UmbracoEntityReference(udi); - } - - // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis - if (int.TryParse(udiStr, out var id)) - { - Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectTypes.Document); - Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; - - if (guid != Guid.Empty) - { - yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); - } - - } - } - } + public override string UdiEntityType { get; } = Constants.UdiEntityType.Document; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Document; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs index f4be58b753..dfdd6f9b9c 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection.Metadata; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; @@ -32,52 +33,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); - internal class MultipleMediaPickerPropertyValueEditor : DataValueEditor, IDataValueReference + internal class MultipleMediaPickerPropertyValueEditor : MultiplePickerParamateterValueEditorBase { - private readonly IEntityService _entityService; - - public MultipleMediaPickerPropertyValueEditor( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper, - DataEditorAttribute attribute, - IEntityService entityService) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + public MultipleMediaPickerPropertyValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) { - _entityService = entityService; } - public IEnumerable GetReferences(object value) - { - var asString = value is string str ? str : value?.ToString(); - - if (string.IsNullOrEmpty(asString)) - { - yield break; - } - - foreach (var udiStr in asString.Split(',')) - { - if (UdiParser.TryParse(udiStr, out Udi udi)) - { - yield return new UmbracoEntityReference(udi); - } - - // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis - if (int.TryParse(udiStr, out var id)) - { - Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectTypes.Media); - Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; - - if (guid != Guid.Empty) - { - yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); - } - - } - } - } + public override string UdiEntityType { get; } = Constants.UdiEntityType.Media; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Media; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs new file mode 100644 index 0000000000..2c4f27b560 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; + +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +{ + internal abstract class MultiplePickerParamateterValueEditorBase : DataValueEditor, IDataValueReference + { + private readonly IEntityService _entityService; + + public MultiplePickerParamateterValueEditorBase( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + { + _entityService = entityService; + } + + public abstract string UdiEntityType { get; } + public abstract UmbracoObjectTypes UmbracoObjectType { get; } + public IEnumerable GetReferences(object value) + { + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) + { + yield break; + } + + foreach (var udiStr in asString.Split(',')) + { + if (UdiParser.TryParse(udiStr, out Udi udi)) + { + yield return new UmbracoEntityReference(udi); + } + + // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis + if (int.TryParse(udiStr, out var id)) + { + Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectType); + Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; + + if (guid != Guid.Empty) + { + yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); + } + + } + } + } + } +} From 93432a2658491a4ec36f4291fd12d19e62c2d282 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Fri, 18 Mar 2022 14:26:25 +0100 Subject: [PATCH 20/42] Apply suggestions from code review --- .../Persistence/Repositories/Implement/MacroRepository.cs | 4 ++-- .../Services/Implement/MacroService.cs | 4 ++-- .../Templates/HtmlMacroParameterParser.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 21461effe9..21638027ea 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -77,7 +77,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public IEnumerable GetAllByAlias(string[] aliases) { - if (aliases.Any() == false) + if (aliases.Any() is false) { return base.GetMany(); } @@ -93,7 +93,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement private IEnumerable PerformGetAllByAlias(params string[] aliases) { - if (aliases.Any() == false) + if (aliases.Any() is false) { return base.GetMany(); } diff --git a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs index e7770e018f..a1d556d805 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Core.Services.Implement { if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) { - return GetAll().FirstOrDefault(x=>x.Alias == alias); + return GetAll().FirstOrDefault(x => x.Alias == alias); } using (var scope = ScopeProvider.CreateScope(autoComplete: true)) @@ -69,7 +69,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) { var hashset = new HashSet(aliases); - return GetAll().Where(x=> hashset.Contains(x.Alias)); + return GetAll().Where(x => hashset.Contains(x.Alias)); } using (var scope = ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs index 2b4786bd0a..6323139137 100644 --- a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -58,7 +58,7 @@ namespace Umbraco.Cms.Infrastructure.Templates // Deserialise JSON of Macro Grid Control to a class var gridMacro = macroGridControl.Value.ToObject(); // Collect any macro parameters that contain the media udi format - if (gridMacro != null && gridMacro.MacroParameters != null && gridMacro.MacroParameters.Any()) + if (gridMacro is not null && gridMacro.MacroParameters is not null && gridMacro.MacroParameters.Any()) { foundMacros.Add(new Tuple>(gridMacro.MacroAlias, gridMacro.MacroParameters)); } @@ -94,7 +94,7 @@ namespace Umbraco.Cms.Infrastructure.Templates } foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key))); // Only do this if the macros actually have parameters - if (macroConfig.Properties != null && macroConfig.Properties.Keys.Any(f => f != "macroAlias")) + if (macroConfig.Properties is not null && macroConfig.Properties.Keys.Any(f => f != "macroAlias")) { foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors)) { @@ -121,7 +121,7 @@ namespace Umbraco.Cms.Infrastructure.Templates var parameterEditorAlias = parameter.EditorAlias; // Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter var parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase)); - if (parameterEditor != null) + if (parameterEditor is not null) { // Get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast as IDataValueReference to determine if 'it is' implemented for the editor if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor) From a02ef3c101ff2ab8eedb2dc087035c140bdf1f52 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Fri, 18 Mar 2022 14:52:57 +0100 Subject: [PATCH 21/42] Try to make test non-flaky --- .../Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs index c25b2fde1e..fdb29f88e6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs @@ -323,6 +323,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.PublishedCache.NuCache } [Test] + [Retry(5)] // TODO make this test non-flaky. public async Task EventuallyCollectNulls() { var d = new SnapDictionary(); From a9daab5a155b85534421847f397a47dd956959df Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 21 Mar 2022 08:24:19 +0100 Subject: [PATCH 22/42] Merge pull request #12139 from umbraco/v9/bugfix/track-media-items-picked-as-macro-params Fix media tracking of items added via macro parameters in RTE and Grid --- src/Umbraco.Core/Cache/MacroCacheRefresher.cs | 9 +- .../Repositories/IMacroWithAliasRepository.cs | 14 ++ .../MultipleContentPickerParameterEditor.cs | 16 +- .../MultipleMediaPickerParameterEditor.cs | 20 ++- ...ultiplePickerParamateterValueEditorBase.cs | 61 +++++++ src/Umbraco.Core/Services/IMacroService.cs | 9 +- .../Services/IMacroWithAliasService.cs | 17 ++ .../UmbracoBuilder.Repositories.cs | 1 - .../UmbracoBuilder.Services.cs | 3 +- .../Repositories/Implement/MacroRepository.cs | 36 +++- .../PropertyEditors/GridPropertyEditor.cs | 58 ++++++- .../PropertyEditors/RichTextPropertyEditor.cs | 53 +++++- .../Services/Implement/MacroService.cs | 27 ++- .../Templates/HtmlMacroParameterParser.cs | 155 ++++++++++++++++++ .../Templates/IHtmlMacroParameterParser.cs | 26 +++ .../Services/MacroServiceTests.cs | 17 +- .../SnapDictionaryTests.cs | 1 + 17 files changed, 489 insertions(+), 34 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs create mode 100644 src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs create mode 100644 src/Umbraco.Core/Services/IMacroWithAliasService.cs create mode 100644 src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs create mode 100644 src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index 77550b81d1..e1b65e2a32 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -46,6 +46,11 @@ namespace Umbraco.Cms.Core.Cache { var payloads = Deserialize(json); + Refresh(payloads); + } + + public override void Refresh(JsonPayload[] payloads) + { foreach (var payload in payloads) { foreach (var alias in GetCacheKeysForAlias(payload.Alias)) @@ -55,11 +60,13 @@ namespace Umbraco.Cms.Core.Cache if (macroRepoCache) { macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Alias)); // Repository caching of macro definition by alias } } - base.Refresh(json); + base.Refresh(payloads); } + #endregion #region Json diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs new file mode 100644 index 0000000000..f6cd27ad60 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories +{ + [Obsolete("This interface will be merged with IMacroRepository in Umbraco 11")] + public interface IMacroWithAliasRepository : IMacroRepository + { + IMacro GetByAlias(string alias); + + IEnumerable GetAllByAlias(string[] aliases); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs index 048ad40ac0..4d88431e7c 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs @@ -1,5 +1,5 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -28,5 +28,17 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors DefaultConfiguration.Add("minNumber",0 ); DefaultConfiguration.Add("maxNumber", 0); } + + protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); + + internal class MultipleContentPickerParamateterValueEditor : MultiplePickerParamateterValueEditorBase + { + public MultipleContentPickerParamateterValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) + { + } + + public override string UdiEntityType { get; } = Constants.UdiEntityType.Document; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Document; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs index d8f74b1b28..dfdd6f9b9c 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs @@ -1,5 +1,9 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using System; +using System.Collections.Generic; +using System.Reflection.Metadata; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -26,5 +30,17 @@ namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors { DefaultConfiguration.Add("multiPicker", "1"); } + + protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute); + + internal class MultipleMediaPickerPropertyValueEditor : MultiplePickerParamateterValueEditorBase + { + public MultipleMediaPickerPropertyValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) + { + } + + public override string UdiEntityType { get; } = Constants.UdiEntityType.Media; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Media; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs new file mode 100644 index 0000000000..2c4f27b560 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; + +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +{ + internal abstract class MultiplePickerParamateterValueEditorBase : DataValueEditor, IDataValueReference + { + private readonly IEntityService _entityService; + + public MultiplePickerParamateterValueEditorBase( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + { + _entityService = entityService; + } + + public abstract string UdiEntityType { get; } + public abstract UmbracoObjectTypes UmbracoObjectType { get; } + public IEnumerable GetReferences(object value) + { + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) + { + yield break; + } + + foreach (var udiStr in asString.Split(',')) + { + if (UdiParser.TryParse(udiStr, out Udi udi)) + { + yield return new UmbracoEntityReference(udi); + } + + // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis + if (int.TryParse(udiStr, out var id)) + { + Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectType); + Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; + + if (guid != Guid.Empty) + { + yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); + } + + } + } + } + } +} diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs index c4bc34997f..e1eb97ac00 100644 --- a/src/Umbraco.Core/Services/IMacroService.cs +++ b/src/Umbraco.Core/Services/IMacroService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models; @@ -17,13 +17,6 @@ namespace Umbraco.Cms.Core.Services /// An object IMacro GetByAlias(string alias); - ///// - ///// Gets a list all available objects - ///// - ///// Optional array of aliases to limit the results - ///// An enumerable list of objects - //IEnumerable GetAll(params string[] aliases); - IEnumerable GetAll(); IEnumerable GetAll(params int[] ids); diff --git a/src/Umbraco.Core/Services/IMacroWithAliasService.cs b/src/Umbraco.Core/Services/IMacroWithAliasService.cs new file mode 100644 index 0000000000..6e72777bfa --- /dev/null +++ b/src/Umbraco.Core/Services/IMacroWithAliasService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services +{ + [Obsolete("This interface will be merged with IMacroService in Umbraco 11")] + public interface IMacroWithAliasService : IMacroService + { + /// + /// Gets a list of available objects by alias. + /// + /// Optional array of aliases to limit the results + /// An enumerable list of objects + IEnumerable GetAll(params string[] aliases); + } +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 966b54633b..13196c1879 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Extensions; diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 915b815033..157d49fd39 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -18,9 +18,9 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Packaging; -using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Services.Implement; +using Umbraco.Cms.Infrastructure.Templates; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -92,6 +92,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddSingleton(); builder.Services.AddUnique(); + builder.Services.AddUnique(); return builder; } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 535895e8ed..21638027ea 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -18,14 +18,16 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { - internal class MacroRepository : EntityRepositoryBase, IMacroRepository + internal class MacroRepository : EntityRepositoryBase, IMacroWithAliasRepository { private readonly IShortStringHelper _shortStringHelper; + private readonly IRepositoryCachePolicy _macroByAliasCachePolicy; public MacroRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IShortStringHelper shortStringHelper) : base(scopeAccessor, cache, logger) { _shortStringHelper = shortStringHelper; + _macroByAliasCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } protected override IMacro PerformGet(int id) @@ -68,6 +70,38 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return Get(id) != null; } + public IMacro GetByAlias(string alias) + { + return _macroByAliasCachePolicy.Get(alias, PerformGetByAlias, PerformGetAllByAlias); + } + + public IEnumerable GetAllByAlias(string[] aliases) + { + if (aliases.Any() is false) + { + return base.GetMany(); + } + + return _macroByAliasCachePolicy.GetAll(aliases, PerformGetAllByAlias); + } + + private IMacro PerformGetByAlias(string alias) + { + var query = Query().Where(x => x.Alias.Equals(alias)); + return PerformGetByQuery(query).FirstOrDefault(); + } + + private IEnumerable PerformGetAllByAlias(params string[] aliases) + { + if (aliases.Any() is false) + { + return base.GetMany(); + } + + var query = Query().Where(x => aliases.Contains(x.Alias)); + return PerformGetByQuery(query); + } + protected override IEnumerable PerformGetAll(params int[] ids) { return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds(); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs index f149757919..c3d8be8f50 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyEditor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; @@ -14,6 +15,8 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; +using Umbraco.Cms.Infrastructure.Templates; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -37,6 +40,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly RichTextEditorPastedImages _pastedImages; private readonly HtmlLocalLinkParser _localLinkParser; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlMacroParameterParser _macroParameterParser; public GridPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -45,7 +49,8 @@ namespace Umbraco.Cms.Core.PropertyEditors RichTextEditorPastedImages pastedImages, HtmlLocalLinkParser localLinkParser, IIOHelper ioHelper, - IImageUrlGenerator imageUrlGenerator) + IImageUrlGenerator imageUrlGenerator, + IHtmlMacroParameterParser macroParameterParser) : base(dataValueEditorFactory) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -54,6 +59,20 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _localLinkParser = localLinkParser; _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public GridPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + HtmlLocalLinkParser localLinkParser, + IIOHelper ioHelper, + IImageUrlGenerator imageUrlGenerator) + : this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, pastedImages, localLinkParser, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService()) + { } public override IPropertyIndexValueFactory PropertyIndexValueFactory => new GridPropertyIndexValueFactory(); @@ -74,6 +93,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly RichTextPropertyEditor.RichTextPropertyValueEditor _richTextPropertyValueEditor; private readonly MediaPickerPropertyEditor.MediaPickerPropertyValueEditor _mediaPickerPropertyValueEditor; private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IHtmlMacroParameterParser _macroParameterParser; public GridPropertyValueEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -85,7 +105,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IShortStringHelper shortStringHelper, IImageUrlGenerator imageUrlGenerator, IJsonSerializer jsonSerializer, - IIOHelper ioHelper) + IIOHelper ioHelper, + IHtmlMacroParameterParser macroParameterParser) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -96,6 +117,25 @@ namespace Umbraco.Cms.Core.PropertyEditors _mediaPickerPropertyValueEditor = dataValueEditorFactory.Create(attribute); _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public GridPropertyValueEditor( + IDataValueEditorFactory dataValueEditorFactory, + DataEditorAttribute attribute, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + HtmlImageSourceParser imageSourceParser, + RichTextEditorPastedImages pastedImages, + IShortStringHelper shortStringHelper, + IImageUrlGenerator imageUrlGenerator, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + : this (dataValueEditorFactory, attribute, backOfficeSecurityAccessor, localizedTextService, + imageSourceParser, pastedImages, shortStringHelper, imageUrlGenerator, jsonSerializer, ioHelper, + StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -120,7 +160,7 @@ namespace Umbraco.Cms.Core.PropertyEditors var mediaParent = config?.MediaParentId; var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid; - var grid = DeserializeGridValue(rawJson, out var rtes, out _); + var grid = DeserializeGridValue(rawJson, out var rtes, out _, out _); var userId = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser?.Id ?? Constants.Security.SuperUserId; @@ -154,7 +194,7 @@ namespace Umbraco.Cms.Core.PropertyEditors if (val.IsNullOrWhiteSpace()) return string.Empty; - var grid = DeserializeGridValue(val, out var rtes, out _); + var grid = DeserializeGridValue(val, out var rtes, out _, out _); //process the rte values foreach (var rte in rtes.ToList()) @@ -168,7 +208,7 @@ namespace Umbraco.Cms.Core.PropertyEditors return grid; } - private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues) + private GridValue DeserializeGridValue(string rawJson, out IEnumerable richTextValues, out IEnumerable mediaValues, out IEnumerable macroValues) { var grid = JsonConvert.DeserializeObject(rawJson); @@ -177,6 +217,9 @@ namespace Umbraco.Cms.Core.PropertyEditors richTextValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "rte"); mediaValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "media"); + // Find all the macros + macroValues = controls.Where(x => x.Editor.Alias.ToLowerInvariant() == "macro"); + return grid; } @@ -192,7 +235,7 @@ namespace Umbraco.Cms.Core.PropertyEditors if (rawJson.IsNullOrWhiteSpace()) yield break; - DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues); + DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues, out var macroValues); foreach (var umbracoEntityReference in richTextEditorValues.SelectMany(x => _richTextPropertyValueEditor.GetReferences(x.Value))) @@ -201,6 +244,9 @@ namespace Umbraco.Cms.Core.PropertyEditors foreach (var umbracoEntityReference in mediaValues.Where(x => x.Value.HasValues) .SelectMany(x => _mediaPickerPropertyValueEditor.GetReferences(x.Value["udi"]))) yield return umbracoEntityReference; + + foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromGridControlMacros(macroValues)) + yield return umbracoEntityReference; } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 1cfbc3449e..18c3fe0902 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -3,8 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; @@ -16,6 +15,8 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Infrastructure.Macros; +using Umbraco.Cms.Infrastructure.Templates; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -36,12 +37,13 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IHtmlMacroParameterParser _macroParameterParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IIOHelper _ioHelper; private readonly IImageUrlGenerator _imageUrlGenerator; /// - /// The constructor will setup the property editor based on the attribute if one is found + /// The constructor will setup the property editor based on the attribute if one is found. /// public RichTextPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, @@ -50,7 +52,8 @@ namespace Umbraco.Cms.Core.PropertyEditors HtmlLocalLinkParser localLinkParser, RichTextEditorPastedImages pastedImages, IIOHelper ioHelper, - IImageUrlGenerator imageUrlGenerator) + IImageUrlGenerator imageUrlGenerator, + IHtmlMacroParameterParser macroParameterParser) : base(dataValueEditorFactory) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -59,6 +62,20 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _ioHelper = ioHelper; _imageUrlGenerator = imageUrlGenerator; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an IHtmlMacroParameterParser instead")] + public RichTextPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IIOHelper ioHelper, + IImageUrlGenerator imageUrlGenerator) + : this (dataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, localLinkParser, pastedImages, ioHelper, imageUrlGenerator, StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -79,6 +96,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly HtmlImageSourceParser _imageSourceParser; private readonly HtmlLocalLinkParser _localLinkParser; + private readonly IHtmlMacroParameterParser _macroParameterParser; private readonly RichTextEditorPastedImages _pastedImages; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly IHtmlSanitizer _htmlSanitizer; @@ -94,7 +112,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IImageUrlGenerator imageUrlGenerator, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - IHtmlSanitizer htmlSanitizer) + IHtmlSanitizer htmlSanitizer, + IHtmlMacroParameterParser macroParameterParser) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -103,6 +122,26 @@ namespace Umbraco.Cms.Core.PropertyEditors _pastedImages = pastedImages; _imageUrlGenerator = imageUrlGenerator; _htmlSanitizer = htmlSanitizer; + _macroParameterParser = macroParameterParser; + } + + [Obsolete("Use the constructor which takes an HtmlMacroParameterParser instead")] + public RichTextPropertyValueEditor( + DataEditorAttribute attribute, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + HtmlImageSourceParser imageSourceParser, + HtmlLocalLinkParser localLinkParser, + RichTextEditorPastedImages pastedImages, + IImageUrlGenerator imageUrlGenerator, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + IHtmlSanitizer htmlSanitizer) + : this(attribute, backOfficeSecurityAccessor, localizedTextService, shortStringHelper, imageSourceParser, + localLinkParser, pastedImages, imageUrlGenerator, jsonSerializer, ioHelper, htmlSanitizer, + StaticServiceProvider.Instance.GetRequiredService()) + { } /// @@ -182,6 +221,10 @@ namespace Umbraco.Cms.Core.PropertyEditors yield return new UmbracoEntityReference(udi); //TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs + //UPDATE: We are getting the Macros in 'FindUmbracoEntityReferencesFromEmbeddedMacros' - perhaps we just return the macro Udis here too or do they need their own relationAlias? + + foreach (var umbracoEntityReference in _macroParameterParser.FindUmbracoEntityReferencesFromEmbeddedMacros(asString)) + yield return umbracoEntityReference; } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs index a79d9fddce..a1d556d805 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MacroService.cs @@ -13,13 +13,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// Represents the Macro Service, which is an easy access to operations involving /// - internal class MacroService : RepositoryService, IMacroService + internal class MacroService : RepositoryService, IMacroWithAliasService { private readonly IMacroRepository _macroRepository; private readonly IAuditRepository _auditRepository; - public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IMacroRepository macroRepository, IAuditRepository auditRepository) + public MacroService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMacroRepository macroRepository, IAuditRepository auditRepository) : base(provider, loggerFactory, eventMessagesFactory) { _macroRepository = macroRepository; @@ -33,10 +32,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// An object public IMacro GetByAlias(string alias) { + if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) + { + return GetAll().FirstOrDefault(x => x.Alias == alias); + } + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - var q = Query().Where(x => x.Alias == alias); - return _macroRepository.Get(q).FirstOrDefault(); + return macroWithAliasRepository.GetByAlias(alias); } } @@ -61,6 +64,20 @@ namespace Umbraco.Cms.Core.Services.Implement } } + public IEnumerable GetAll(params string[] aliases) + { + if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository) + { + var hashset = new HashSet(aliases); + return GetAll().Where(x => hashset.Contains(x.Alias)); + } + + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return macroWithAliasRepository.GetAllByAlias(aliases); + } + } + public IMacro GetById(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs new file mode 100644 index 0000000000..6323139137 --- /dev/null +++ b/src/Umbraco.Infrastructure/Templates/HtmlMacroParameterParser.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Macros; + +namespace Umbraco.Cms.Infrastructure.Templates +{ + public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser + { + private readonly IMacroService _macroService; + private readonly ILogger _logger; + private readonly ParameterEditorCollection _parameterEditors; + + public HtmlMacroParameterParser(IMacroService macroService, ILogger logger, ParameterEditorCollection parameterEditors) + { + _macroService = macroService; + _logger = logger; + _parameterEditors = parameterEditors; + } + + /// + /// Parses out media UDIs from an HTML string based on embedded macro parameter values. + /// + /// HTML string + /// + public IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text) + { + // There may be more than one macro with the same alias on the page so using a tuple + var foundMacros = new List>>(); + + // This legacy ParseMacros() already finds the macros within a Rich Text Editor using regexes + // It seems to lowercase the macro parameter alias - so making the dictionary case insensitive + MacroTagParser.ParseMacros(text, textblock => { }, (macroAlias, macroAttributes) => foundMacros.Add(new Tuple>(macroAlias, new Dictionary(macroAttributes, StringComparer.OrdinalIgnoreCase)))); + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) + { + yield return umbracoEntityReference; + } + } + + /// + /// Parses out media UDIs from Macro Grid Control parameters. + /// + /// + /// + public IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls) + { + var foundMacros = new List>>(); + + foreach (var macroGridControl in macroGridControls) + { + // Deserialise JSON of Macro Grid Control to a class + var gridMacro = macroGridControl.Value.ToObject(); + // Collect any macro parameters that contain the media udi format + if (gridMacro is not null && gridMacro.MacroParameters is not null && gridMacro.MacroParameters.Any()) + { + foundMacros.Add(new Tuple>(gridMacro.MacroAlias, gridMacro.MacroParameters)); + } + } + + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros)) + { + yield return umbracoEntityReference; + } + } + + private IEnumerable GetUmbracoEntityReferencesFromMacros(List>> macros) + { + + if (_macroService is not IMacroWithAliasService macroWithAliasService) + { + yield break; + } + + var uniqueMacroAliases = macros.Select(f => f.Item1).Distinct(); + // TODO: Tracking Macro references + // Here we are finding the used macros' Udis (there should be a Related Macro relation type - but Relations don't accept 'Macro' as an option) + var foundMacroUmbracoEntityReferences = new List(); + // Get all the macro configs in one hit for these unique macro aliases - this is now cached with a custom cache policy + var macroConfigs = macroWithAliasService.GetAll(uniqueMacroAliases.ToArray()); + + foreach (var macro in macros) + { + var macroConfig = macroConfigs.FirstOrDefault(f => f.Alias == macro.Item1); + if (macroConfig is null) + { + continue; + } + foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key))); + // Only do this if the macros actually have parameters + if (macroConfig.Properties is not null && macroConfig.Properties.Keys.Any(f => f != "macroAlias")) + { + foreach (var umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors)) + { + yield return umbracoEntityReference; + } + } + } + } + + /// + /// Finds media UDIs in Macro Parameter Values by calling the GetReference method for all the Macro Parameter Editors for a particular macro. + /// + /// The parameters for the macro a dictionary of key/value strings + /// The macro configuration for this particular macro - contains the types of editors used for each parameter + /// A list of all the registered parameter editors used in the Umbraco implmentation - to look up the corresponding property editor for a macro parameter + /// + private IEnumerable GetUmbracoEntityReferencesFromMacroParameters(Dictionary macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors) + { + var foundUmbracoEntityReferences = new List(); + foreach (var parameter in macroConfig.Properties) + { + if (macroParameters.TryGetValue(parameter.Alias, out string parameterValue)) + { + var parameterEditorAlias = parameter.EditorAlias; + // Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter + var parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase)); + if (parameterEditor is not null) + { + // Get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast as IDataValueReference to determine if 'it is' implemented for the editor + if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor) + { + foreach (var entityReference in parameterValueEditor.GetReferences(parameterValue)) + { + foundUmbracoEntityReferences.Add(entityReference); + } + } + else + { + _logger.LogInformation("{0} doesn't have a ValueEditor that implements IDataValueReference", parameterEditor.Alias); + } + } + } + } + + return foundUmbracoEntityReferences; + } + + // Poco class to deserialise the Json for a Macro Control + private class GridMacro + { + [JsonProperty("macroAlias")] + public string MacroAlias { get; set; } + + [JsonProperty("macroParamsDictionary")] + public Dictionary MacroParameters { get; set; } + } + } +} diff --git a/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs new file mode 100644 index 0000000000..6e484cc30a --- /dev/null +++ b/src/Umbraco.Infrastructure/Templates/IHtmlMacroParameterParser.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; + +namespace Umbraco.Cms.Infrastructure.Templates +{ + /// + /// Provides methods to parse referenced entities as Macro parameters. + /// + public interface IHtmlMacroParameterParser + { + /// + /// Parses out media UDIs from an HTML string based on embedded macro parameter values. + /// + /// HTML string + /// + IEnumerable FindUmbracoEntityReferencesFromEmbeddedMacros(string text); + + /// + /// Parses out media UDIs from Macro Grid Control parameters. + /// + /// + /// + IEnumerable FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable macroGridControls); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs index 75dae7515b..621762917d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MacroServiceTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -24,7 +23,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class MacroServiceTests : UmbracoIntegrationTest { - private IMacroService MacroService => GetRequiredService(); + [Obsolete("After merging IMacroWithAliasService interface with IMacroService in Umbraco 11, this should go back to just being GetRequiredService()")] + private IMacroWithAliasService MacroService => GetRequiredService() as IMacroWithAliasService; [SetUp] public void SetupTest() @@ -52,6 +52,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual("Test1", macro.Name); } + [Test] + public void Can_Get_By_Aliases() + { + // Act + IEnumerable macros = MacroService.GetAll("test1", "test2"); + + // Assert + Assert.IsNotNull(macros); + Assert.AreEqual(2, macros.Count()); + Assert.AreEqual("Test1", macros.ToArray()[0].Name); + Assert.AreEqual("Test2", macros.ToArray()[1].Name); + } + [Test] public void Can_Get_All() { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs index c25b2fde1e..fdb29f88e6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs @@ -323,6 +323,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.PublishedCache.NuCache } [Test] + [Retry(5)] // TODO make this test non-flaky. public async Task EventuallyCollectNulls() { var d = new SnapDictionary(); From f3c5c0976dff49aaa49459d89e6267bf4dfdd130 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 21 Mar 2022 14:59:23 +0100 Subject: [PATCH 23/42] Same fix as #12154 - fixes #10066 --- .../Repositories/Implement/RedirectUrlRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 6ab29aa47e..5e9a8413b4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -60,14 +60,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var urlHash = url.GenerateHash(); Sql sql = GetBaseQuery(false) .Where(x => x.Url == url && x.UrlHash == urlHash && - (x.Culture == culture.ToLower() || x.Culture == string.Empty)) + (x.Culture == culture.ToLower() || x.Culture == null || x.Culture == string.Empty)) .OrderByDescending(x => x.CreateDateUtc); List dtos = Database.Fetch(sql); RedirectUrlDto dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); if (dto == null) { - dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); + dto = dtos.FirstOrDefault(f => string.IsNullOrWhiteSpace(f.Culture)); } return dto == null ? null : Map(dto); From cde312b6d40e0a00f50c575bb1a99c65fb142b67 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 21 Mar 2022 15:47:51 +0100 Subject: [PATCH 24/42] Use an umbra.co link for the TV replacement channel so we can change it in the future if we need to --- .../src/views/dashboard/settings/settingsdashboardintro.html | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index 436155de72..b33444177a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -17,7 +17,7 @@ Ask a question in the Community Forum
  • - Watch our free tutorial videos on the Umbraco Learning Base + Watch our free tutorial videos on the Umbraco Learning Base
  • Find out about our productivity boosting tools and commercial support diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 6b5d301c5f..e9b63f92a1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -2639,7 +2639,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont tutorial videos on the Umbraco Learning Base + Watch our free tutorial videos on the Umbraco Learning Base ]]> diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index bed9f227dd..f01b878d19 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2728,7 +2728,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont tutorial videos on the Umbraco Learning Base + Watch our free tutorial videos on the Umbraco Learning Base ]]> From 0aa4d1956aa1e075dc1e8a98368a4caba7692c4c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 21 Mar 2022 16:14:48 +0100 Subject: [PATCH 25/42] Also update Umbraco TV link + text in the help panel --- .../src/views/common/drawers/help/help.html | 6 +++--- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index 6f32e89988..c14110437d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -128,13 +128,13 @@
    - +
    - Visit umbraco.tv + Watch our free tutorial videos
    - The best Umbraco video tutorials + on the Umbraco Learning Base
    diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index e9b63f92a1..6d78c7db5b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1469,6 +1469,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont The best Umbraco video tutorials Visit our.umbraco.com Visit umbraco.tv + Watch our free tutorial videos + on the Umbraco Learning Base Default template diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index f01b878d19..fab7392ad8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1494,6 +1494,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont The best Umbraco video tutorials Visit our.umbraco.com Visit umbraco.tv + Watch our free tutorial videos + on the Umbraco Learning Base Default template From 04c292f1675073ef2be800eeeb3332028ab61f16 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 22 Mar 2022 10:09:36 +0100 Subject: [PATCH 26/42] Merge pull request #12155 from umbraco/v9/bugfix/10066 Same fix as #12154 - fixes #10066 (cherry picked from commit a302b10f66119c8253e1e3630d0456cb805bdc8a) --- .../Repositories/Implement/RedirectUrlRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 6ab29aa47e..5e9a8413b4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -60,14 +60,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var urlHash = url.GenerateHash(); Sql sql = GetBaseQuery(false) .Where(x => x.Url == url && x.UrlHash == urlHash && - (x.Culture == culture.ToLower() || x.Culture == string.Empty)) + (x.Culture == culture.ToLower() || x.Culture == null || x.Culture == string.Empty)) .OrderByDescending(x => x.CreateDateUtc); List dtos = Database.Fetch(sql); RedirectUrlDto dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); if (dto == null) { - dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); + dto = dtos.FirstOrDefault(f => string.IsNullOrWhiteSpace(f.Culture)); } return dto == null ? null : Map(dto); From 0d836875c77f67a08f428c00ce882f8a1e1b78f9 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 22 Mar 2022 12:58:38 +0100 Subject: [PATCH 27/42] Merge pull request #12153 from vsilvar/v9/bugfix/12022_recurring_hosted_service_scope_leak Fixes RecurringHostServices leaking the execution context / ambient scope --- .../HostedServices/ContentVersionCleanup.cs | 2 +- .../HostedServices/HealthCheckNotifier.cs | 1 + .../HostedServices/KeepAlive.cs | 2 +- .../HostedServices/LogScrubber.cs | 2 +- .../RecurringHostedServiceBase.cs | 20 +++++++++++++++---- .../HostedServices/ReportSiteTask.cs | 2 +- .../HostedServices/ScheduledPublishing.cs | 2 +- .../InstructionProcessTask.cs | 2 +- .../ServerRegistration/TouchServerTask.cs | 2 +- .../HostedServices/TempFileCleanup.cs | 2 +- .../Implement/CacheInstructionService.cs | 7 +++++++ 11 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs index 5f3aba5f3f..8c9f3223f0 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IContentVersionService service, IMainDom mainDom, IServerRoleAccessor serverRoleAccessor) - : base(TimeSpan.FromHours(1), TimeSpan.FromMinutes(3)) + : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(1)) { _runtimeState = runtimeState; _logger = logger; diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index 6a0828fad3..e6d8e75304 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -61,6 +61,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IProfilingLogger profilingLogger, ICronTabParser cronTabParser) : base( + logger, healthChecksSettings.Value.Notification.Period, healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) { diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 22160b8f6e..3233cfa8f2 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -49,7 +49,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IProfilingLogger profilingLogger, IServerRoleAccessor serverRegistrar, IHttpClientFactory httpClientFactory) - : base(TimeSpan.FromMinutes(5), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(5), DefaultDelay) { _hostingEnvironment = hostingEnvironment; _mainDom = mainDom; diff --git a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs index 27d9c29e8d..79c1c4b8ea 100644 --- a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger) - : base(TimeSpan.FromHours(4), DefaultDelay) + : base(logger, TimeSpan.FromHours(4), DefaultDelay) { _mainDom = mainDom; _serverRegistrar = serverRegistrar; diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index d97737a8f8..18fe9fc47f 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -21,6 +22,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices ///
  • protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3); + private readonly ILogger _logger; private TimeSpan _period; private readonly TimeSpan _delay; private Timer _timer; @@ -29,10 +31,12 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// /// Initializes a new instance of the class. /// - /// Timepsan representing how often the task should recur. - /// Timespan represeting the initial delay after application start-up before the first run of the task occurs. - protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) + /// Logger. + /// Timespan representing how often the task should recur. + /// Timespan representing the initial delay after application start-up before the first run of the task occurs. + protected RecurringHostedServiceBase(ILogger logger, TimeSpan period, TimeSpan delay) { + _logger = logger; _period = period; _delay = delay; } @@ -40,7 +44,11 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// public Task StartAsync(CancellationToken cancellationToken) { - _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + using (!ExecutionContext.IsFlowSuppressed() ? (IDisposable)ExecutionContext.SuppressFlow() : null) + { + _timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds); + } + return Task.CompletedTask; } @@ -61,6 +69,10 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Hat-tip: https://stackoverflow.com/a/14207615/489433 await PerformExecuteAsync(state); } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception in recurring hosted service {serviceName}.", GetType().Name); + } finally { // Resume now that the task is complete - Note we use period in both because we don't want to execute again after the delay. diff --git a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs index cfce96281c..6e5d412e71 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices public ReportSiteTask( ILogger logger, ITelemetryService telemetryService) - : base(TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) + : base(logger, TimeSpan.FromDays(1), TimeSpan.FromMinutes(1)) { _logger = logger; _telemetryService = telemetryService; diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index 429389273f..fd70c05fc1 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices ILogger logger, IServerMessenger serverMessenger, IScopeProvider scopeProvider) - : base(TimeSpan.FromMinutes(1), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(1), DefaultDelay) { _runtimeState = runtimeState; _mainDom = mainDom; diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index 43e2522efd..3aa49f3f71 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration /// The typed logger. /// The configuration for global settings. public InstructionProcessTask(IRuntimeState runtimeState, IServerMessenger messenger, ILogger logger, IOptions globalSettings) - : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) + : base(logger, globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) { _runtimeState = runtimeState; _messenger = messenger; diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index d54d67338e..5f20a3654e 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -44,7 +44,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration ILogger logger, IOptions globalSettings, IServerRoleAccessor serverRoleAccessor) - : base(globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) + : base(logger, globalSettings.Value.DatabaseServerRegistrar.WaitTimeBetweenCalls, TimeSpan.FromSeconds(15)) { _runtimeState = runtimeState; _serverRegistrationService = serverRegistrationService ?? throw new ArgumentNullException(nameof(serverRegistrationService)); diff --git a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs index e59cca5fbd..8a2a312455 100644 --- a/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/TempFileCleanup.cs @@ -33,7 +33,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices /// Representation of the main application domain. /// The typed logger. public TempFileCleanup(IIOHelper ioHelper, IMainDom mainDom, ILogger logger) - : base(TimeSpan.FromMinutes(60), DefaultDelay) + : base(logger, TimeSpan.FromMinutes(60), DefaultDelay) { _ioHelper = ioHelper; _mainDom = mainDom; diff --git a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs index a037cd1095..b4af98ad0a 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/CacheInstructionService.cs @@ -247,6 +247,13 @@ namespace Umbraco.Cms.Core.Services.Implement ///
    private bool TryDeserializeInstructions(CacheInstruction instruction, out JArray jsonInstructions) { + if (instruction.Instructions is null) + { + _logger.LogError("Failed to deserialize instructions ({DtoId}: 'null').", instruction.Id); + jsonInstructions = null; + return false; + } + try { jsonInstructions = JsonConvert.DeserializeObject(instruction.Instructions); From 20f0ceeda6923194f64c39f150999e3dfbe35db8 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 23 Mar 2022 09:05:30 +0100 Subject: [PATCH 28/42] Merge pull request #12161 from umbraco/v9/bugfix/amend_breaking_change_in_RecurringHostedServiceBase Amend breaking change in RecurringHostedServiceBase --- src/Umbraco.Core/StaticApplicationLogging.cs | 1 + .../HostedServices/ContentVersionCleanup.cs | 2 +- .../HostedServices/RecurringHostedServiceBase.cs | 13 ++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index e216011014..73078b0f42 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs index 8c9f3223f0..d037c91d86 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices IContentVersionService service, IMainDom mainDom, IServerRoleAccessor serverRoleAccessor) - : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(1)) + : base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(3)) { _runtimeState = runtimeState; _logger = logger; diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index 18fe9fc47f..c1c7cdf3cf 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -4,8 +4,10 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -41,6 +43,15 @@ namespace Umbraco.Cms.Infrastructure.HostedServices _delay = delay; } + // Scheduled for removal in V11 + [Obsolete("Please use constructor that takes an ILogger instead")] + protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) + { + _period = period; + _delay = delay; + _logger = StaticServiceProvider.Instance.GetRequiredService().CreateLogger(GetType()); + } + /// public Task StartAsync(CancellationToken cancellationToken) { @@ -71,7 +82,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices } catch (Exception ex) { - _logger.LogError(ex, "Unhandled exception in recurring hosted service {serviceName}.", GetType().Name); + _logger.LogError(ex, "Unhandled exception in recurring hosted service."); } finally { From 78cfb29908835ac1db40c01079bd8f0a53b87e3b Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Date: Fri, 18 Mar 2022 11:21:49 +0100 Subject: [PATCH 29/42] Item tracking fixes (#12146) * Cleanup; Fix lang keys * Documentation * Typos * Distinct the results * Changed GetPagedRelationsForItems to GetPagedRelationsForItem as we would only expect a single id to be passed when calling this + fix more docs * Changed to the correct reference * Unused code * Only load references when info tab is clicked Co-authored-by: Bjarke Berg --- .../ITrackedReferencesRepository.cs | 38 +++++++-- .../Services/ITrackedReferencesService.cs | 29 ++++++- .../Persistence/NPocoSqlExtensions.cs | 6 ++ .../Implement/RelationRepository.cs | 33 -------- .../Implement/TrackedReferencesRepository.cs | 83 ++++++++++--------- .../Implement/TrackedReferencesService.cs | 23 +++-- .../Controllers/MediaController.cs | 2 +- .../TrackedReferencesController.cs | 43 +++++----- .../content/umbcontentnodeinfo.directive.js | 7 ++ .../resources/trackedreferences.resource.js | 2 +- .../content/umb-content-node-info.html | 2 +- .../umb-tracked-references-table.html | 4 +- .../references/umb-tracked-references.html | 10 +-- .../src/views/users/user.controller.js | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 10 +-- .../umbraco/config/lang/en_us.xml | 14 +--- 16 files changed, 173 insertions(+), 135 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs index 42746a9565..e6ca8eaa50 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -1,14 +1,42 @@ -using System; using System.Collections.Generic; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Persistence.Repositories { public interface ITrackedReferencesRepository { - IEnumerable GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords); - IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords); - IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency,out long totalRecords); + /// + /// 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 page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of the items with reference to the current item. + /// An enumerable list of objects. + IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + + /// + /// 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 page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of the items in any kind of relation. + /// An enumerable list of objects. + IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + + /// + /// Gets a page of the descending items that have any references, given a parent id. + /// + /// The unique identifier of the parent to retrieve descendants for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// The total count of descending items. + /// An enumerable list of objects. + IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); } } diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs index eee8a324df..dea99c0f6d 100644 --- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -4,12 +4,35 @@ namespace Umbraco.Cms.Core.Services { public interface ITrackedReferencesService { - PagedResult GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency); - + /// + /// 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 page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. + PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency); + /// + /// Gets a paged result of the descending items that have any references, given a parent id. + /// + /// The unique identifier of the parent to retrieve descendants for. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency); - + /// + /// Gets a paged result of items used in any kind of relation from selected integer ids. + /// + /// The identifiers of the entities to check for relations. + /// The page index. + /// The page size. + /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). + /// A paged result of objects. PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency); } } diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs index 6b7c34dc15..47cca58ce2 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoSqlExtensions.cs @@ -659,6 +659,12 @@ namespace Umbraco.Extensions return sql; } + public static Sql SelectDistinct(this Sql sql, params object[] columns) + { + sql.Append("SELECT DISTINCT " + string.Join(", ", columns)); + return sql; + } + //this.Append("SELECT " + string.Join(", ", columns), new object[0]); /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index a18782ca82..7ba20d1db5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -198,22 +198,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement }); } - public IEnumerable GetPagedParentEntitiesByChildIds(int[] childIds, long pageIndex, int pageSize, out long totalRecords, int[] relationTypes, params Guid[] entityTypes) - { - return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => - { - SqlJoinRelations(sql); - - sql.WhereIn(rel => rel.ChildId, childIds); - sql.WhereAny(s => s.WhereIn(rel => rel.ParentId, childIds), s => s.WhereNotIn(node => node.NodeId, childIds)); - - if (relationTypes != null && relationTypes.Any()) - { - sql.WhereIn(rel => rel.RelationType, relationTypes); - } - }); - } - public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) { return GetPagedChildEntitiesByParentId(parentId, pageIndex, pageSize, out totalRecords, new int[0], entityTypes); @@ -241,21 +225,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement }); } - public IEnumerable GetPagedEntitiesForItemsInRelation(int[] itemIds, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes) - { - return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql => - { - SqlJoinRelations(sql); - - sql.WhereIn(rel => rel.ChildId, itemIds); - sql.Where((rel, node) => rel.ChildId == node.NodeId); - sql.Where(type => type.IsDependency); - }); - } - - - - public void Save(IEnumerable relations) { foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity)) @@ -475,8 +444,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement [Column(Name = "contentTypeName")] public string ChildContentTypeName { get; set; } - - [Column(Name = "relationTypeName")] public string RelationTypeName { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs index f5fb945464..0e70d47cbf 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; @@ -20,10 +19,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement _scopeAccessor = scopeAccessor; } - public IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, - bool filterMustBeIsDependency, out long totalRecords) + /// + /// Gets a page of items used in any kind of relation from selected integer ids. + /// + public IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { - var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[pn].[id] as nodeId", "[pn].[uniqueId] as nodeKey", "[pn].[text] as nodeName", @@ -36,12 +37,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") - .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType") - .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" ) - .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" ) - .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c") - .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct") - .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn"); + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn"); if (ids.Any()) { @@ -57,13 +58,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } - public IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, - out long totalRecords) + /// + /// Gets a page of the descending items that have any references, given a parent id. + /// + public IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { var syntax = _scopeAccessor.AmbientScope.Database.SqlContext.SqlSyntax; @@ -73,13 +76,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .From("node") .Where(x => x.NodeId == parentId, "node"); - // Gets the descendants of the parent node Sql subQuery; if (_scopeAccessor.AmbientScope.Database.DatabaseType.IsSqlCe()) { - // SqlCE do not support nested selects that returns a scalar. So we need to do this in multiple queries + // SqlCE does not support nested selects that returns a scalar. So we need to do this in multiple queries var pathForLike = _scopeAccessor.AmbientScope.Database.ExecuteScalar(subsubQuery); @@ -96,10 +98,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .WhereLike(x => x.Path, subsubQuery); } - - // Get all relations where parent is in the sub query - var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[pn].[id] as nodeId", "[pn].[uniqueId] as nodeKey", "[pn].[text] as nodeName", @@ -112,29 +112,35 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") - .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType") - .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" ) - .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" ) - .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"pn", aliasRight:"c") - .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct") - .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn") + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn") .WhereIn((System.Linq.Expressions.Expression>)(x => x.NodeId), subQuery, "pn"); + if (filterMustBeIsDependency) { sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); } + // Ordering is required for paging sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } - public IEnumerable GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) + /// + /// Gets a page of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + public IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { - var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( + var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[cn].[id] as nodeId", "[cn].[uniqueId] as nodeKey", "[cn].[text] as nodeName", @@ -147,17 +153,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") - .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight:"umbracoRelationType") - .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId )), aliasLeft: "r", aliasRight:"cn", aliasOther: "umbracoRelationType" ) - .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight:"pn", aliasOther:"cn" ) - .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"cn", aliasRight:"c") - .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft:"c", aliasRight:"ct") - .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft:"ct", aliasRight:"ctn"); - - if (ids.Any()) - { - sql = sql.Where(x => ids.Contains(x.NodeId), "pn"); - } + .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") + .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") + .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "cn", aliasRight: "c") + .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") + .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn") + .Where(x => x.NodeId == id, "pn"); if (filterMustBeIsDependency) { @@ -168,14 +170,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } private RelationItem MapDtoToEntity(RelationItemDto dto) { - var type = ObjectTypes.GetUdiType(dto.ChildNodeObjectType); return new RelationItem() { NodeId = dto.ChildNodeId, diff --git a/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs b/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs index 9a4cc8860e..ec22e1095c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/TrackedReferencesService.cs @@ -1,4 +1,3 @@ -using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; @@ -18,22 +17,32 @@ namespace Umbraco.Cms.Core.Services.Implement _entityService = entityService; } - public PagedResult GetPagedRelationsForItems(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) + /// + /// Gets a paged result of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); - var items = _trackedReferencesRepository.GetPagedRelationsForItems(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); + var items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex+1, pageSize) { Items = items }; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } + /// + /// Gets a paged result of items used in any kind of relation from selected integer ids. + /// public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); - var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); + var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex+1, pageSize) { Items = items }; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } + /// + /// Gets a paged result of the descending items that have any references, given a parent id. + /// public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); @@ -44,7 +53,7 @@ namespace Umbraco.Cms.Core.Services.Implement pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex+1, pageSize) { Items = items }; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index d8fa641891..c4328da2d4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -1082,7 +1082,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return new ActionResult(toMove); } - [Obsolete("Please use TrackedReferencesController.GetPagedReferences() instead. Scheduled for removal in V11.")] + [Obsolete("Please use TrackedReferencesController.GetPagedRelationsForItem() instead. Scheduled for removal in V11.")] public PagedResult GetPagedReferences(int id, string entityType, int pageNumber = 1, int pageSize = 100) { if (pageNumber <= 0 || pageSize <= 0) diff --git a/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs b/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs index 2cef8d61af..aa1a0ee86e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TrackedReferencesController.cs @@ -1,12 +1,7 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.BackOffice.ModelBinders; using Umbraco.Cms.Web.Common.Attributes; @@ -19,28 +14,36 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers public class TrackedReferencesController : BackOfficeNotificationsController { private readonly ITrackedReferencesService _relationService; - private readonly IEntityService _entityService; - public TrackedReferencesController(ITrackedReferencesService relationService, - IEntityService entityService) + public TrackedReferencesController(ITrackedReferencesService relationService) { _relationService = relationService; - _entityService = entityService; } - // Used by info tabs on content, media etc. So this is basically finding childs of relations. - public ActionResult> GetPagedReferences(int id, int pageNumber = 1, - int pageSize = 100, bool filterMustBeIsDependency = false) + /// + /// Gets a page list of tracked references for the current item, so you can see where an item is being used. + /// + /// + /// Used by info tabs on content, media etc. and for the delete and unpublish of single items. + /// This is basically finding parents of relations. + /// + public ActionResult> GetPagedReferences(int id, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = false) { if (pageNumber <= 0 || pageSize <= 0) { return BadRequest("Both pageNumber and pageSize must be greater than zero"); } - return _relationService.GetPagedRelationsForItems(new []{id}, pageNumber - 1, pageSize, filterMustBeIsDependency); + return _relationService.GetPagedRelationsForItem(id, pageNumber - 1, pageSize, filterMustBeIsDependency); } - // Used on delete, finds + /// + /// Gets a page list of the child nodes of the current item used in any kind of relation. + /// + /// + /// Used when deleting and unpublishing a single item to check if this item has any descending items that are in any kind of relation. + /// This is basically finding the descending items which are children in relations. + /// public ActionResult> GetPagedDescendantsInReferences(int parentId, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = true) { if (pageNumber <= 0 || pageSize <= 0) @@ -48,12 +51,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return BadRequest("Both pageNumber and pageSize must be greater than zero"); } - return _relationService.GetPagedDescendantsInReferences(parentId, pageNumber - 1, pageSize, filterMustBeIsDependency); - } - // Used by unpublish content. So this is basically finding parents of relations. + /// + /// Gets a page list of the items used in any kind of relation from selected integer ids. + /// + /// + /// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view). + /// This is basically finding children of relations. + /// [HttpGet] [HttpPost] public ActionResult> GetPagedReferencedItems([FromJsonPath] int[] ids, int pageNumber = 1, int pageSize = 100, bool filterMustBeIsDependency = true) @@ -64,8 +71,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } return _relationService.GetPagedItemsWithRelations(ids, pageNumber - 1, pageSize, filterMustBeIsDependency); - } } - } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 83292251da..501ea9f81a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -12,6 +12,7 @@ scope.publishStatus = []; scope.currentVariant = null; scope.currentUrls = []; + scope.loadingReferences = false; scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; scope.allowChangeDocumentType = false; @@ -229,6 +230,10 @@ }); } + + function loadReferences(){ + scope.loadingReferences = true; + } function loadRedirectUrls() { scope.loadingRedirectUrls = true; //check if Redirect URL Management is enabled @@ -335,6 +340,7 @@ loadRedirectUrls(); setNodePublishStatus(); formatDatesToLocal(); + loadReferences(); } else { isInfoTab = false; } @@ -352,6 +358,7 @@ loadRedirectUrls(); setNodePublishStatus(); formatDatesToLocal(); + loadReferences(); } updateCurrentUrls(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js index cd64c89589..d64951a6d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/trackedreferences.resource.js @@ -162,7 +162,7 @@ function trackedReferencesResource($q, $http, umbRequestHelper) { $http.post( umbRequestHelper.getApiUrl( "trackedReferencesApiBaseUrl", - "getPagedReferencedItems", + "GetPagedReferencedItems", query), { ids: ids, diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 6429e39db6..1c7545f9ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -21,7 +21,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html index afc8f9a3e6..d09bc23318 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references-table.html @@ -7,8 +7,8 @@
    -
    Node Name
    -
    Type Name
    +
    Node Name
    +
    Type Name
    Type
    Relation
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html index fd788cc598..9e08c5fbae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/references/umb-tracked-references.html @@ -26,11 +26,11 @@
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 79164a2457..684ce6d2f0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -374,7 +374,7 @@ } function enableUser() { - vm.enableUserButtonState = "busfy"; + vm.enableUserButtonState = "busy"; usersResource.enableUsers([vm.user.id]).then(function (data) { vm.user.userState = "Active"; setUserDisplayState(); diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 6d78c7db5b..bf2de30f2e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -788,6 +788,7 @@ New Next No + Node Name of Off OK @@ -829,6 +830,7 @@ Submit Success Type + Type Name Type to search... under Up @@ -2390,6 +2392,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Parent Child Count + Relation Relations Created Comment @@ -2470,18 +2473,13 @@ To manage your website, simply open the Umbraco backoffice and start adding cont References This Data Type has no references. + This item has no references. Used in Document Types Used in Media Types Used in Member Types Used by - Used in Documents - Used in Members - Used in Media Items in use Descendants in use - One or more of this item's descendants is being used in a media item. - One or more of this item's descendants is being used in a content item. - One or more of this item's descendants is being used in a member. This item or its descendants is being used. Deletion can lead to broken links on your website. This item or its descendants is being used. Unpublishing can lead to broken links on your website. Please take the appropriate actions. This item or its descendants is being used. Therefore, deletion has been disabled. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index fab7392ad8..873361169a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -809,7 +809,7 @@ New Next No - Node Name + Node Name of Off OK @@ -850,7 +850,7 @@ Submit Success Type - Type Name + Type Name Type to search... under Up @@ -1495,7 +1495,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Visit our.umbraco.com Visit umbraco.tv Watch our free tutorial videos - on the Umbraco Learning Base + on the Umbraco Learning Base Default template @@ -2562,15 +2562,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Referenced by Referenced by the following items The following items depend on this - Referenced by the following Documents - Referenced by the following Members - Referenced by the following Media The following items are referenced The following descendant items have dependencies - The following descending items has dependencies - One or more of this item's descendants is being referenced in a media item. - One or more of this item's descendants is being referenced in a content item. - One or more of this item's descendants is being referenced in a member. + The following descending items have dependencies This item or its descendants is being referenced. Deletion can lead to broken links on your website. This item or its descendants is being referenced. Unpublishing can lead to broken links on your website. Please take the appropriate actions. This item or its descendants is being referenced. Therefore, deletion has been disabled. From 4fffb9f7a79624493f93486333dd78ccec8a98fa Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 23 Mar 2022 09:48:21 +0100 Subject: [PATCH 30/42] Bump versions to non-rc --- build/templates/UmbracoPackage/.template.config/template.json | 2 +- build/templates/UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 082f9301bf..1889fa706e 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.0-rc", + "defaultValue": "9.4.0", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index 810940c4eb..780d560154 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.0-rc", + "defaultValue": "9.4.0", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 68962caef4..19773adb37 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ 9.4.0 9.4.0 - 9.4.0-rc + 9.4.0 9.4.0 9.0 en-US From 535008ebc100199104f591d0941015b2fcf35a6a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 23 Mar 2022 10:42:24 +0100 Subject: [PATCH 31/42] Fix custom block view (#12104) Co-authored-by: Elitsa Marinovska --- .../blocklist.blockconfiguration.overlay.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js index 9331e4227b..0fdc251949 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js @@ -174,13 +174,13 @@ filterCssClass: "not-allowed", select: node => { const filepath = decodeURIComponent(node.id.replace(/\+/g, " ")); - block.view = "~/" + filepath; + block.view = "~/" + filepath.replace("wwwroot/", ""); editorService.close(); }, close: () => editorService.close() }; - editorService.filePicker(filePicker); + editorService.staticFilePicker(filePicker); }); }; From c1552cf7fcb8625bb94dc8b32d1850c5d841ad65 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 23 Mar 2022 11:36:34 +0100 Subject: [PATCH 32/42] Bump versions --- .../UmbracoPackage/.template.config/template.json | 2 +- .../UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 082f9301bf..74de126429 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.0-rc", + "defaultValue": "9.5.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index 810940c4eb..cd8347e206 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.0-rc", + "defaultValue": "9.5.0-rc", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 68962caef4..d7161acb1f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,10 +3,10 @@ - 9.4.0 - 9.4.0 - 9.4.0-rc - 9.4.0 + 9.5.0 + 9.5.0 + 9.5.0-rc + 9.5.0 9.0 en-US Umbraco CMS From d0823d4236b527882571dfd04ed59bcbe52a9717 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Mar 2022 11:34:29 +0100 Subject: [PATCH 33/42] Fix obsolete constructor in RecurringHostedServiceBase (#12172) * Use NullLogger in obsoleted constructor * Create missing logger during execution instead --- src/Umbraco.Core/StaticApplicationLogging.cs | 14 +++++--------- .../HostedServices/RecurringHostedServiceBase.cs | 15 ++++++--------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index 73078b0f42..d7dfc8dd9a 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -6,18 +6,14 @@ namespace Umbraco.Cms.Core { public static class StaticApplicationLogging { - private static ILoggerFactory _loggerFactory; + private static ILoggerFactory s_loggerFactory; - public static void Initialize(ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } + public static void Initialize(ILoggerFactory loggerFactory) => s_loggerFactory = loggerFactory; public static ILogger Logger => CreateLogger(); - public static ILogger CreateLogger() - { - return _loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); - } + public static ILogger CreateLogger() => s_loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); + + public static ILogger CreateLogger(Type type) => s_loggerFactory?.CreateLogger(type) ?? NullLogger.Instance; } } diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index c1c7cdf3cf..5247a125bc 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -4,10 +4,9 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Cms.Core; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -46,11 +45,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Scheduled for removal in V11 [Obsolete("Please use constructor that takes an ILogger instead")] protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) - { - _period = period; - _delay = delay; - _logger = StaticServiceProvider.Instance.GetRequiredService().CreateLogger(GetType()); - } + : this(null, period, delay) + { } /// public Task StartAsync(CancellationToken cancellationToken) @@ -82,7 +78,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices } catch (Exception ex) { - _logger.LogError(ex, "Unhandled exception in recurring hosted service."); + ILogger logger = _logger ?? StaticApplicationLogging.CreateLogger(GetType()); + logger.LogError(ex, "Unhandled exception in recurring hosted service."); } finally { @@ -108,7 +105,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices { if (disposing) { - _timer?.Dispose(); + _timer?.Dispose(); } _disposedValue = true; From 037580b305d0b0771dbe7f5e0f40dfdceeae62ec Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Mar 2022 11:34:29 +0100 Subject: [PATCH 34/42] Fix obsolete constructor in RecurringHostedServiceBase (#12172) * Use NullLogger in obsoleted constructor * Create missing logger during execution instead (cherry picked from commit d0823d4236b527882571dfd04ed59bcbe52a9717) --- .../UmbracoPackage/.template.config/template.json | 2 +- .../UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 10 +++++----- src/Umbraco.Core/StaticApplicationLogging.cs | 14 +++++--------- .../HostedServices/RecurringHostedServiceBase.cs | 15 ++++++--------- 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 1889fa706e..64da9a2553 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.0", + "defaultValue": "9.4.1", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index 780d560154..a2d6400f8f 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.0", + "defaultValue": "9.4.1", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 19773adb37..328f3c2278 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,12 +1,12 @@ - + - 9.4.0 - 9.4.0 - 9.4.0 - 9.4.0 + 9.4.1 + 9.4.1 + 9.4.1 + 9.4.1 9.0 en-US Umbraco CMS diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index 73078b0f42..d7dfc8dd9a 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -6,18 +6,14 @@ namespace Umbraco.Cms.Core { public static class StaticApplicationLogging { - private static ILoggerFactory _loggerFactory; + private static ILoggerFactory s_loggerFactory; - public static void Initialize(ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } + public static void Initialize(ILoggerFactory loggerFactory) => s_loggerFactory = loggerFactory; public static ILogger Logger => CreateLogger(); - public static ILogger CreateLogger() - { - return _loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); - } + public static ILogger CreateLogger() => s_loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); + + public static ILogger CreateLogger(Type type) => s_loggerFactory?.CreateLogger(type) ?? NullLogger.Instance; } } diff --git a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs index c1c7cdf3cf..5247a125bc 100644 --- a/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs +++ b/src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs @@ -4,10 +4,9 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Cms.Core; namespace Umbraco.Cms.Infrastructure.HostedServices { @@ -46,11 +45,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices // Scheduled for removal in V11 [Obsolete("Please use constructor that takes an ILogger instead")] protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay) - { - _period = period; - _delay = delay; - _logger = StaticServiceProvider.Instance.GetRequiredService().CreateLogger(GetType()); - } + : this(null, period, delay) + { } /// public Task StartAsync(CancellationToken cancellationToken) @@ -82,7 +78,8 @@ namespace Umbraco.Cms.Infrastructure.HostedServices } catch (Exception ex) { - _logger.LogError(ex, "Unhandled exception in recurring hosted service."); + ILogger logger = _logger ?? StaticApplicationLogging.CreateLogger(GetType()); + logger.LogError(ex, "Unhandled exception in recurring hosted service."); } finally { @@ -108,7 +105,7 @@ namespace Umbraco.Cms.Infrastructure.HostedServices { if (disposing) { - _timer?.Dispose(); + _timer?.Dispose(); } _disposedValue = true; From 02e76c8227959bc881ec6ae5e14a9b2ebd22ad91 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 25 Mar 2022 08:58:07 +0100 Subject: [PATCH 35/42] Configuration to control the creation of default data (#12122) * Added configuration and checks for creation of default Umbraco data. * Fixed configuration binding issues. * Updated comments. * Added DefaultDataCreationSettings to the JSON schema. * Removed option to not install default relation types as Umbraco relies on (and will recreate) them if they aren't there. * Renamed configuration class used for install of default data and converted to named optios. * Fix to failing unit tests. * Fixes for integration tests. * Apply suggestions from code review Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> * Further fix from code review. * Updated naming as per PR review suggestions. * Update src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> --- src/JsonSchema/AppSettings.cs | 2 + .../Models/InstallDefaultDataSettings.cs | 73 ++ src/Umbraco.Core/Constants-Configuration.cs | 15 + src/Umbraco.Core/Constants-DataTypes.cs | 1 - .../UmbracoBuilder.Configuration.cs | 13 + .../Migrations/Install/DatabaseDataCreator.cs | 978 ++++++++++++++---- .../Install/DatabaseSchemaCreator.cs | 45 +- .../Install/DatabaseSchemaCreatorFactory.cs | 20 +- .../Persistence/DatabaseBuilderTests.cs | 4 +- .../Testing/BaseTestDatabase.cs | 4 +- .../Migrations/AdvancedMigrationTests.cs | 4 +- .../Persistence/SchemaValidationTest.cs | 7 +- .../Persistence/SqlServerTableByTableTest.cs | 75 +- .../Customizations/UmbracoCustomizations.cs | 4 +- .../Umbraco.Core/Components/ComponentTests.cs | 2 +- 15 files changed, 1000 insertions(+), 247 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index f9aa6b500c..0851baba2e 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -92,6 +92,8 @@ namespace JsonSchema public ContentDashboardSettings ContentDashboard { get; set; } public HelpPageSettings HelpPage { get; set; } + + public InstallDefaultDataSettings DefaultDataCreation { get; set; } } /// diff --git a/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs new file mode 100644 index 0000000000..377e893bbf --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs @@ -0,0 +1,73 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Configuration.Models +{ + /// + /// An enumeration of options available for control over installation of default Umbraco data. + /// + public enum InstallDefaultDataOption + { + /// + /// Do not install any items of this type (other than Umbraco defined essential ones). + /// + None, + + /// + /// Only install the default data specified in the + /// + Values, + + /// + /// Install all default data, except that specified in the + /// + ExceptValues, + + /// + /// Install all default data. + /// + All + } + + /// + /// Typed configuration options for installation of default data. + /// + public class InstallDefaultDataSettings + { + /// + /// Gets or sets a value indicating whether to create default data on installation. + /// + public InstallDefaultDataOption InstallData { get; set; } = InstallDefaultDataOption.All; + + /// + /// Gets or sets a value indicating which default data (languages, data types, etc.) should be created when is + /// set to or . + /// + /// + /// + /// For languages, the values provided should be the ISO codes for the languages to be included or excluded, e.g. "en-US". + /// If removing the single default language, ensure that a different one is created via some other means (such + /// as a restore from Umbraco Deploy schema data). + /// + /// + /// For data types, the values provided should be the Guid values used by Umbraco for the data type, listed at: + /// + /// Some data types - such as the string label - cannot be excluded from install as they are required for core Umbraco + /// functionality. + /// Otherwise take care not to remove data types required for default Umbraco media and member types, unless you also + /// choose to exclude them. + /// + /// + /// For media types, the values provided should be the Guid values used by Umbraco for the media type, listed at: + /// https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs. + /// + /// + /// For member types, the values provided should be the Guid values used by Umbraco for the member type, listed at: + /// https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs. + /// + /// + public IList Values { get; set; } = new List(); + } +} diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index bdbd13b2a4..8ee6040deb 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -56,6 +56,21 @@ namespace Umbraco.Cms.Core public const string ConfigPackageMigration = ConfigPrefix + "PackageMigration"; public const string ConfigContentDashboard = ConfigPrefix + "ContentDashboard"; public const string ConfigHelpPage = ConfigPrefix + "HelpPage"; + public const string ConfigInstallDefaultData = ConfigPrefix + "InstallDefaultData"; + + public static class NamedOptions + { + public static class InstallDefaultData + { + public const string Languages = "Languages"; + + public const string DataTypes = "DataTypes"; + + public const string MediaTypes = "MediaTypes"; + + public const string MemberTypes = "MemberTypes"; + } + } } } } diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index 12445ea589..ba8827cd26 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -211,7 +211,6 @@ namespace Umbraco.Cms.Core /// public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers); - /// /// Guid for Date Picker with time as string /// diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 91e6f71415..6d09d82300 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -90,6 +90,19 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions(); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.Languages, + builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.Languages}")); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes}")); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes}")); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes, + builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes}")); + builder.Services.Configure(options => options.MergeReplacements(builder.Config)); return builder; diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index b19802996b..e13764140a 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NPoco; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0; @@ -19,12 +22,25 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private readonly IDatabase _database; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; + private readonly IOptionsMonitor _installDefaultDataSettings; - public DatabaseDataCreator(IDatabase database, ILogger logger, IUmbracoVersion umbracoVersion) + private readonly IDictionary> _entitiesToAlwaysCreate = new Dictionary>() + { + { + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + new List + { + Cms.Core.Constants.DataTypes.Guids.LabelString, + } + } + }; + + public DatabaseDataCreator(IDatabase database, ILogger logger, IUmbracoVersion umbracoVersion, IOptionsMonitor installDefaultDataSettings) { _database = database; _logger = logger; _umbracoVersion = umbracoVersion; + _installDefaultDataSettings = installDefaultDataSettings; } /// @@ -37,54 +53,91 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install _logger.LogInformation("Creating data in {TableName}", tableName); if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.Node)) + { CreateNodeData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.Lock)) + { CreateLockData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.ContentType)) + { CreateContentTypeData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.User)) + { CreateUserData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.UserGroup)) + { CreateUserGroupData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.User2UserGroup)) + { CreateUser2UserGroupData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.UserGroup2App)) + { CreateUserGroup2AppData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup)) + { CreatePropertyTypeGroupData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType)) + { CreatePropertyTypeData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.Language)) + { CreateLanguageData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType)) + { CreateContentChildTypeData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.DataType)) + { CreateDataTypeData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.RelationType)) + { CreateRelationTypeData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue)) + { CreateKeyValueData(); + } if (tableName.Equals(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery)) + { CreateLogViewerQueryData(); + } _logger.LogInformation("Done creating table {TableName} data.", tableName); } private void CreateNodeData() + { + CreateNodeDataForDataTypes(); + CreateNodeDataForMediaTypes(); + CreateNodeDataForMemberTypes(); + } + + private void CreateNodeDataForDataTypes() { void InsertDataTypeNodeDto(int id, int sortOrder, string uniqueId, string text) { @@ -100,66 +153,287 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install UniqueId = new Guid(uniqueId), Text = text, NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, - CreateDate = DateTime.Now + CreateDate = DateTime.Now, }; - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, nodeDto); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + uniqueId, + nodeDto, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); } _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = Cms.Core.Constants.ObjectTypes.SystemRoot, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = Cms.Core.Constants.ObjectTypes.ContentRecycleBin, CreateDate = DateTime.Now }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaRecycleBin, CreateDate = DateTime.Now }); + InsertDataTypeNodeDto(Cms.Core.Constants.DataTypes.LabelString, 35, Cms.Core.Constants.DataTypes.Guids.LabelString, "Label (string)"); InsertDataTypeNodeDto(Cms.Core.Constants.DataTypes.LabelInt, 36, Cms.Core.Constants.DataTypes.Guids.LabelInt, "Label (integer)"); InsertDataTypeNodeDto(Cms.Core.Constants.DataTypes.LabelBigint, 36, Cms.Core.Constants.DataTypes.Guids.LabelBigInt, "Label (bigint)"); InsertDataTypeNodeDto(Cms.Core.Constants.DataTypes.LabelDateTime, 37, Cms.Core.Constants.DataTypes.Guids.LabelDateTime, "Label (datetime)"); InsertDataTypeNodeDto(Cms.Core.Constants.DataTypes.LabelTime, 38, Cms.Core.Constants.DataTypes.Guids.LabelTime, "Label (time)"); InsertDataTypeNodeDto(Cms.Core.Constants.DataTypes.LabelDecimal, 39, Cms.Core.Constants.DataTypes.Guids.LabelDecimal, "Label (decimal)"); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadGuid, Text = "Upload File", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadVideo, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadVideo}", SortOrder = 35, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadVideoGuid, Text = "Upload Video", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadAudio, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadAudio}", SortOrder = 36, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadAudioGuid, Text = "Upload Audio", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadArticle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadArticle}", SortOrder = 37, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadArticleGuid, Text = "Upload Article", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadVectorGraphics, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadVectorGraphics}", SortOrder = 38, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadVectorGraphicsGuid, Text = "Upload Vector Graphics", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Textarea, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Textarea}", SortOrder = 33, UniqueId = Cms.Core.Constants.DataTypes.Guids.TextareaGuid, Text = "Textarea", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Textbox, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Textbox}", SortOrder = 32, UniqueId = Cms.Core.Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.RichtextEditor, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.RichtextEditor}", SortOrder = 4, UniqueId = Cms.Core.Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.NumericGuid, Text = "Numeric", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Boolean, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Boolean}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.CheckboxGuid, Text = "True/false", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DropdownGuid, Text = "Dropdown", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DatePickerGuid, Text = "Date Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.RadioboxGuid, Text = "Radiobox", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DropdownMultipleGuid, Text = "Dropdown multiple", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DateTime, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DateTime}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ListViewContentGuid, Text = Cms.Core.Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ListViewMediaGuid, Text = Cms.Core.Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ListViewMembersGuid, Text = Cms.Core.Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Cms.Core.Constants.Conventions.MediaTypes.Folder, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Cms.Core.Constants.Conventions.MediaTypes.Image, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Cms.Core.Constants.Conventions.MediaTypes.File, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1034, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1034", SortOrder = 2, UniqueId = new Guid("f6c515bb-653c-4bdc-821c-987729ebe327"), Text = Cms.Core.Constants.Conventions.MediaTypes.Video, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1035, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1035", SortOrder = 2, UniqueId = new Guid("a5ddeee0-8fd8-4cee-a658-6f1fcdb00de3"), Text = Cms.Core.Constants.Conventions.MediaTypes.Audio, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1036, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1036", SortOrder = 2, UniqueId = new Guid("a43e3414-9599-4230-a7d3-943a21b20122"), Text = Cms.Core.Constants.Conventions.MediaTypes.Article, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1037, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1037", SortOrder = 2, UniqueId = new Guid("c4b1efcf-a9d5-41c4-9621-e9d273b52a9c"), Text = "Vector Graphics (SVG)", NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Upload, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadGuid, Text = "Upload File", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.UploadVideo, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadVideo, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadVideo}", SortOrder = 35, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadVideoGuid, Text = "Upload Video", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.UploadAudio, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadAudio, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadAudio}", SortOrder = 36, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadAudioGuid, Text = "Upload Audio", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.UploadArticle, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadArticle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadArticle}", SortOrder = 37, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadArticleGuid, Text = "Upload Article", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.UploadVectorGraphics, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.UploadVectorGraphics, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.UploadVectorGraphics}", SortOrder = 38, UniqueId = Cms.Core.Constants.DataTypes.Guids.UploadVectorGraphicsGuid, Text = "Upload Vector Graphics", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Textarea, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Textarea, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Textarea}", SortOrder = 33, UniqueId = Cms.Core.Constants.DataTypes.Guids.TextareaGuid, Text = "Textarea", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Textstring, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Textbox, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Textbox}", SortOrder = 32, UniqueId = Cms.Core.Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.RichtextEditor, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.RichtextEditor, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.RichtextEditor}", SortOrder = 4, UniqueId = Cms.Core.Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Numeric, + new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.NumericGuid, Text = "Numeric", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Checkbox, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Boolean, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Boolean}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.CheckboxGuid, Text = "True/false", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.CheckboxList, + new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Dropdown, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DropdownGuid, Text = "Dropdown", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.DatePicker, + new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DatePickerGuid, Text = "Date Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Radiobox, + new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.RadioboxGuid, Text = "Radiobox", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.DropdownMultiple, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DropdownMultipleGuid, Text = "Dropdown multiple", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.ApprovedColor, + new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.DatePickerWithTime, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DateTime, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DateTime}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.ListViewContent, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ListViewContentGuid, Text = Cms.Core.Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.ListViewMedia, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ListViewMediaGuid, Text = Cms.Core.Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.ListViewMembers, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ListViewMembersGuid, Text = Cms.Core.Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.Tags, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Tags, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Tags}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.TagsGuid, Text = "Tags", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.ImageCropper, + new NodeDto { NodeId = Cms.Core.Constants.DataTypes.ImageCropper, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.ImageCropper}", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ImageCropperGuid, Text = "Image Cropper", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.Tags, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.Tags}", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Cms.Core.Constants.DataTypes.ImageCropper, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Cms.Core.Constants.DataTypes.ImageCropper}", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Cms.Core.Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = Cms.Core.Constants.ObjectTypes.MemberType, CreateDate = DateTime.Now }); + // New UDI pickers with newer Ids + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.ContentPicker, + new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.ContentPickerGuid, Text = "Content Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.MemberPicker, + new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MemberPickerGuid, Text = "Member Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.MediaPicker, + new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPickerGuid, Text = "Media Picker (legacy)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.MultipleMediaPicker, + new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MultipleMediaPickerGuid, Text = "Multiple Media Picker (legacy)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.RelatedLinks, + new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.RelatedLinksGuid, Text = "Multi URL Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); - //New UDI pickers with newer Ids - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker (legacy)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker (legacy)", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Multi URL Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.MediaPicker3, + new NodeDto { NodeId = 1051, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1051", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3Guid, Text = "Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.MediaPicker3Multiple, + new NodeDto { NodeId = 1052, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1052", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleGuid, Text = "Multiple Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.MediaPicker3SingleImage, + new NodeDto { NodeId = 1053, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1053", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3SingleImageGuid, Text = "Image Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleImages, + new NodeDto { NodeId = 1054, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1054", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleImagesGuid, Text = "Multiple Image Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1051, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1051", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3Guid, Text = "Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1052, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1052", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleGuid, Text = "Multiple Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1053, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1053", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3SingleImageGuid, Text = "Image Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1054, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1054", SortOrder = 2, UniqueId = Cms.Core.Constants.DataTypes.Guids.MediaPicker3MultipleImagesGuid, Text = "Multiple Image Media Picker", NodeObjectType = Cms.Core.Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + private void CreateNodeDataForMediaTypes() + { + var folderUniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + folderUniqueId.ToString(), + new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = folderUniqueId, Text = Cms.Core.Constants.Conventions.MediaTypes.Folder, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + var imageUniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + imageUniqueId.ToString(), + new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = imageUniqueId, Text = Cms.Core.Constants.Conventions.MediaTypes.Image, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + + var fileUniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + fileUniqueId.ToString(), + new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = fileUniqueId, Text = Cms.Core.Constants.Conventions.MediaTypes.File, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + + var videoUniqueId = new Guid("f6c515bb-653c-4bdc-821c-987729ebe327"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + videoUniqueId.ToString(), + new NodeDto { NodeId = 1034, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1034", SortOrder = 2, UniqueId = videoUniqueId, Text = Cms.Core.Constants.Conventions.MediaTypes.Video, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + + var audioUniqueId = new Guid("a5ddeee0-8fd8-4cee-a658-6f1fcdb00de3"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + audioUniqueId.ToString(), + new NodeDto { NodeId = 1035, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1035", SortOrder = 2, UniqueId = audioUniqueId, Text = Cms.Core.Constants.Conventions.MediaTypes.Audio, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + + var articleUniqueId = new Guid("a43e3414-9599-4230-a7d3-943a21b20122"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + articleUniqueId.ToString(), + new NodeDto { NodeId = 1036, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1036", SortOrder = 2, UniqueId = articleUniqueId, Text = Cms.Core.Constants.Conventions.MediaTypes.Article, NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + + var svgUniqueId = new Guid("c4b1efcf-a9d5-41c4-9621-e9d273b52a9c"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + svgUniqueId.ToString(), + new NodeDto { NodeId = 1037, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1037", SortOrder = 2, UniqueId = svgUniqueId, Text = "Vector Graphics (SVG)", NodeObjectType = Cms.Core.Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); + } + + private void CreateNodeDataForMemberTypes() + { + var memberUniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"); + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes, + memberUniqueId.ToString(), + new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = memberUniqueId, Text = Cms.Core.Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = Cms.Core.Constants.ObjectTypes.MemberType, CreateDate = DateTime.Now }, + Cms.Core.Constants.DatabaseSchema.Tables.Node, + "id"); } private void CreateLockData() @@ -182,20 +456,55 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private void CreateContentTypeData() { - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Cms.Core.Constants.Conventions.MediaTypes.Folder, Icon = Cms.Core.Constants.Icons.MediaFolder, Thumbnail = Cms.Core.Constants.Icons.MediaFolder, IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Cms.Core.Constants.Conventions.MediaTypes.Image, Icon = Cms.Core.Constants.Icons.MediaImage, Thumbnail = Cms.Core.Constants.Icons.MediaImage, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Cms.Core.Constants.Conventions.MediaTypes.File, Icon = Cms.Core.Constants.Icons.MediaFile, Thumbnail = Cms.Core.Constants.Icons.MediaFile, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 540, NodeId = 1034, Alias = Cms.Core.Constants.Conventions.MediaTypes.VideoAlias, Icon = Cms.Core.Constants.Icons.MediaVideo, Thumbnail = Cms.Core.Constants.Icons.MediaVideo, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 541, NodeId = 1035, Alias = Cms.Core.Constants.Conventions.MediaTypes.AudioAlias, Icon = Cms.Core.Constants.Icons.MediaAudio, Thumbnail = Cms.Core.Constants.Icons.MediaAudio, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 542, NodeId = 1036, Alias = Cms.Core.Constants.Conventions.MediaTypes.ArticleAlias, Icon = Cms.Core.Constants.Icons.MediaArticle, Thumbnail = Cms.Core.Constants.Icons.MediaArticle, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 543, NodeId = 1037, Alias = Cms.Core.Constants.Conventions.MediaTypes.VectorGraphicsAlias, Icon = Cms.Core.Constants.Icons.MediaVectorGraphics, Thumbnail = Cms.Core.Constants.Icons.MediaVectorGraphics, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + // Insert content types only if the corresponding Node record exists (which may or may not have been created depending on configuration + // of media or member types to create). - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Cms.Core.Constants.Conventions.MemberTypes.DefaultAlias, Icon = Cms.Core.Constants.Icons.Member, Thumbnail = Cms.Core.Constants.Icons.Member, Variations = (byte) ContentVariation.Nothing }); + // Media types. + if (_database.Exists(1031)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Cms.Core.Constants.Conventions.MediaTypes.Folder, Icon = Cms.Core.Constants.Icons.MediaFolder, Thumbnail = Cms.Core.Constants.Icons.MediaFolder, IsContainer = false, AllowAtRoot = true, Variations = (byte)ContentVariation.Nothing }); + } + + if (_database.Exists(1032)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Cms.Core.Constants.Conventions.MediaTypes.Image, Icon = Cms.Core.Constants.Icons.MediaImage, Thumbnail = Cms.Core.Constants.Icons.MediaImage, AllowAtRoot = true, Variations = (byte)ContentVariation.Nothing }); + } + + if (_database.Exists(1033)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Cms.Core.Constants.Conventions.MediaTypes.File, Icon = Cms.Core.Constants.Icons.MediaFile, Thumbnail = Cms.Core.Constants.Icons.MediaFile, AllowAtRoot = true, Variations = (byte)ContentVariation.Nothing }); + } + + if (_database.Exists(1034)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 540, NodeId = 1034, Alias = Cms.Core.Constants.Conventions.MediaTypes.VideoAlias, Icon = Cms.Core.Constants.Icons.MediaVideo, Thumbnail = Cms.Core.Constants.Icons.MediaVideo, AllowAtRoot = true, Variations = (byte)ContentVariation.Nothing }); + } + + if (_database.Exists(1035)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 541, NodeId = 1035, Alias = Cms.Core.Constants.Conventions.MediaTypes.AudioAlias, Icon = Cms.Core.Constants.Icons.MediaAudio, Thumbnail = Cms.Core.Constants.Icons.MediaAudio, AllowAtRoot = true, Variations = (byte)ContentVariation.Nothing }); + } + + if (_database.Exists(1036)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 542, NodeId = 1036, Alias = Cms.Core.Constants.Conventions.MediaTypes.ArticleAlias, Icon = Cms.Core.Constants.Icons.MediaArticle, Thumbnail = Cms.Core.Constants.Icons.MediaArticle, AllowAtRoot = true, Variations = (byte)ContentVariation.Nothing }); + } + + if (_database.Exists(1037)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 543, NodeId = 1037, Alias = Cms.Core.Constants.Conventions.MediaTypes.VectorGraphicsAlias, Icon = Cms.Core.Constants.Icons.MediaVectorGraphics, Thumbnail = Cms.Core.Constants.Icons.MediaVectorGraphics, AllowAtRoot = true, Variations = (byte)ContentVariation.Nothing }); + } + + // Member type. + if (_database.Exists(1044)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Cms.Core.Constants.Conventions.MemberTypes.DefaultAlias, Icon = Cms.Core.Constants.Icons.Member, Thumbnail = Cms.Core.Constants.Icons.Member, Variations = (byte)ContentVariation.Nothing }); + } } private void CreateUserData() { - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.User, "id", false, new UserDto { Id = Cms.Core.Constants.Security.SuperUserId, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.User, "id", false, new UserDto { Id = Cms.Core.Constants.Security.SuperUserId, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = string.Empty, UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); } private void CreateUserGroupData() @@ -204,7 +513,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = Cms.Core.Constants.Security.WriterGroupAlias, Name = "Writers", DefaultPermissions = "CAH:FN", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = Cms.Core.Constants.Security.EditorGroupAlias, Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5FïN", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Cms.Core.Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Cms.Core.Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Cms.Core.Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = string.Empty, CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); } private void CreateUser2UserGroupData() @@ -235,67 +544,136 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private void CreatePropertyTypeGroupData() { - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Image), ContentTypeNodeId = 1032, Text = "Image", Alias = "image", SortOrder = 1 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.File), ContentTypeNodeId = 1033, Text = "File", Alias = "file", SortOrder = 1, }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 52, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Video), ContentTypeNodeId = 1034, Text = "Video", Alias = "video", SortOrder = 1 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 53, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Audio), ContentTypeNodeId = 1035, Text = "Audio", Alias = "audio", SortOrder = 1 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 54, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Article), ContentTypeNodeId = 1036, Text = "Article", Alias = "article", SortOrder = 1 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 55, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.VectorGraphics), ContentTypeNodeId = 1037, Text = "Vector Graphics", Alias = "vectorGraphics", SortOrder = 1 }); - //membership property group - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Membership), ContentTypeNodeId = 1044, Text = Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupName, Alias = Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupAlias, SortOrder = 1 }); + // Insert property groups only if the corresponding content type node record exists (which may or may not have been created depending on configuration + // of media or member types to create). + + // Media property groups. + if (_database.Exists(1032)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Image), ContentTypeNodeId = 1032, Text = "Image", Alias = "image", SortOrder = 1 }); + } + + if (_database.Exists(1033)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.File), ContentTypeNodeId = 1033, Text = "File", Alias = "file", SortOrder = 1, }); + } + + if (_database.Exists(1034)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 52, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Video), ContentTypeNodeId = 1034, Text = "Video", Alias = "video", SortOrder = 1 }); + } + + if (_database.Exists(1035)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 53, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Audio), ContentTypeNodeId = 1035, Text = "Audio", Alias = "audio", SortOrder = 1 }); + } + + if (_database.Exists(1036)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 54, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Article), ContentTypeNodeId = 1036, Text = "Article", Alias = "article", SortOrder = 1 }); + } + + if (_database.Exists(1037)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 55, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.VectorGraphics), ContentTypeNodeId = 1037, Text = "Vector Graphics", Alias = "vectorGraphics", SortOrder = 1 }); + } + + // Membership property group. + if (_database.Exists(1044)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, UniqueId = new Guid(Cms.Core.Constants.PropertyTypeGroups.Membership), ContentTypeNodeId = 1044, Text = Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupName, Alias = Cms.Core.Constants.Conventions.Member.StandardPropertiesGroupAlias, SortOrder = 1 }); + } } private void CreatePropertyTypeData() { - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.ImageCropper, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Upload, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "File", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); + // Insert property types only if the corresponding property group record exists (which may or may not have been created depending on configuration + // of media or member types to create). - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 40, UniqueId = 40.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadVideo, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Video", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 41, UniqueId = 41.ToGuid(), DataTypeId = -92, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 42, UniqueId = 42.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); + // Media property types. + if (_database.Exists(3)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.ImageCropper, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 43, UniqueId = 43.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadAudio, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Audio", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 44, UniqueId = 44.ToGuid(), DataTypeId = -92, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 45, UniqueId = 45.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); + if (_database.Exists(4)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Upload, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "File", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte)ContentVariation.Nothing }); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 46, UniqueId = 46.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadArticle, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Article", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 47, UniqueId = 47.ToGuid(), DataTypeId = -92, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 48, UniqueId = 48.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); + if (_database.Exists(52)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 40, UniqueId = 40.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadVideo, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Video", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 41, UniqueId = 41.ToGuid(), DataTypeId = -92, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 42, UniqueId = 42.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1034, PropertyTypeGroupId = 52, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte)ContentVariation.Nothing }); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 49, UniqueId = 49.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadVectorGraphics, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Vector Graphics", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 50, UniqueId = 50.ToGuid(), DataTypeId = -92, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 51, UniqueId = 51.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); + if (_database.Exists(53)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 43, UniqueId = 43.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadAudio, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Audio", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 44, UniqueId = 44.ToGuid(), DataTypeId = -92, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 45, UniqueId = 45.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1035, PropertyTypeGroupId = 53, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte)ContentVariation.Nothing }); + } - //membership property types - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Textarea, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.Comments, Name = Cms.Core.Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.FailedPasswordAttempts, Name = Cms.Core.Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.IsApproved, Name = Cms.Core.Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.IsLockedOut, Name = Cms.Core.Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastLockoutDate, Name = Cms.Core.Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastLoginDate, Name = Cms.Core.Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastPasswordChangeDate, Name = Cms.Core.Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + if (_database.Exists(54)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 46, UniqueId = 46.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadArticle, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Article", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 47, UniqueId = 47.ToGuid(), DataTypeId = -92, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 48, UniqueId = 48.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1036, PropertyTypeGroupId = 54, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte)ContentVariation.Nothing }); + } + + if (_database.Exists(55)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 49, UniqueId = 49.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.UploadVectorGraphics, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Cms.Core.Constants.Conventions.Media.File, Name = "Vector Graphics", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 50, UniqueId = 50.ToGuid(), DataTypeId = -92, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Cms.Core.Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 51, UniqueId = 51.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelBigint, ContentTypeId = 1037, PropertyTypeGroupId = 55, Alias = Cms.Core.Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte)ContentVariation.Nothing }); + } + + // Membership property types. + if (_database.Exists(11)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Textarea, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.Comments, Name = Cms.Core.Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.FailedPasswordAttempts, Name = Cms.Core.Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.IsApproved, Name = Cms.Core.Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.IsLockedOut, Name = Cms.Core.Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastLockoutDate, Name = Cms.Core.Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastLoginDate, Name = Cms.Core.Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Cms.Core.Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Cms.Core.Constants.Conventions.Member.LastPasswordChangeDate, Name = Cms.Core.Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing }); + } } - private void CreateLanguageData() - { - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true }); - } + private void CreateLanguageData() => + ConditionalInsert( + Cms.Core.Constants.Configuration.NamedOptions.InstallDefaultData.Languages, + "en-us", + new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true }, + Cms.Core.Constants.DatabaseSchema.Tables.Language, + "id"); private void CreateContentChildTypeData() { + // Insert data if the corresponding Node records exist (which may or may not have been created depending on configuration + // of media types to create). + if (!_database.Exists(1031)) + { + return; + } + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1034 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1035 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1036 }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1037 }); + + for (int i = 1032; i <= 1037; i++) + { + if (_database.Exists(i)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = i }); + } + } } private void CreateDataTypeData() @@ -310,9 +688,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install }; if (configuration != null) + { dataTypeDto.Configuration = configuration; + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, dataTypeDto); + if (_database.Exists(id)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, dataTypeDto); + } } //layouts for the list view @@ -320,123 +703,288 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install const string listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; const string layouts = "[" + cardLayout + "," + listLayout + "]"; - // TODO: Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Boolean, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -51, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.Integer, DbType = "Integer" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -87, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.TinyMce, DbType = "Ntext", - Configuration = "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Textbox, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Textarea, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Upload, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); + // Insert data types only if the corresponding Node record exists (which may or may not have been created depending on configuration + // of data types to create). + if (_database.Exists(Cms.Core.Constants.DataTypes.Boolean)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Boolean, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" }); + } + + if (_database.Exists(-51)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -51, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.Integer, DbType = "Integer" }); + } + + if (_database.Exists(-87)) + { + _database.Insert( + Cms.Core.Constants.DatabaseSchema.Tables.DataType, + "pk", + false, + new DataTypeDto + { + NodeId = -87, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.TinyMce, + DbType = "Ntext", + Configuration = "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}" + }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.Textbox)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Textbox, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.Textarea)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Textarea, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.Upload)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Upload, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); + } + InsertDataTypeDto(Cms.Core.Constants.DataTypes.LabelString, Cms.Core.Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); InsertDataTypeDto(Cms.Core.Constants.DataTypes.LabelInt, Cms.Core.Constants.PropertyEditors.Aliases.Label, "Integer", "{\"umbracoDataValueType\":\"INT\"}"); InsertDataTypeDto(Cms.Core.Constants.DataTypes.LabelBigint, Cms.Core.Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}"); InsertDataTypeDto(Cms.Core.Constants.DataTypes.LabelDateTime, Cms.Core.Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}"); InsertDataTypeDto(Cms.Core.Constants.DataTypes.LabelDecimal, Cms.Core.Constants.PropertyEditors.Aliases.Label, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}"); InsertDataTypeDto(Cms.Core.Constants.DataTypes.LabelTime, Cms.Core.Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"TIME\"}"); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.DateTime, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" }); + + if (_database.Exists(Cms.Core.Constants.DataTypes.DateTime)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.DateTime, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); + } + + if (_database.Exists(-37)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" }); + } + InsertDataTypeDto(Cms.Core.Constants.DataTypes.DropDownSingle, Cms.Core.Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}"); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.DateTime", DbType = "Date", Configuration = "{\"format\":\"YYYY-MM-DD\"}" }); + + if (_database.Exists(-40)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" }); + } + + if (_database.Exists(-41)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.DateTime", DbType = "Date", Configuration = "{\"format\":\"YYYY-MM-DD\"}" }); + } + InsertDataTypeDto(Cms.Core.Constants.DataTypes.DropDownMultiple, Cms.Core.Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}"); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.Tags, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", - Configuration = "{\"group\":\"default\", \"storageType\":\"Json\"}" - }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.ImageCropper, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultContentListView, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", - Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultMediaListView, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", - Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.DefaultMembersListView, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", - Configuration = "{\"pageSize\":10, \"orderBy\":\"username\", \"orderDirection\":\"asc\", \"includeProperties\":[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]}" }); - //New UDI pickers with newer Ids - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1046, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ContentPicker, DbType = "Nvarchar" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1047, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MemberPicker, DbType = "Nvarchar" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1049, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext", - Configuration = "{\"multiPicker\":1}" }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MultiUrlPicker, DbType = "Ntext" }); - - - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + if (_database.Exists(-43)) { - NodeId = Cms.Core.Constants.DataTypes.UploadVideo, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, - DbType = "Nvarchar", - Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"mp4\"}, {\"id\":1, \"value\":\"webm\"}, {\"id\":2, \"value\":\"ogv\"}]}" - }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" }); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + if (_database.Exists(Cms.Core.Constants.DataTypes.Tags)) { - NodeId = Cms.Core.Constants.DataTypes.UploadAudio, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, - DbType = "Nvarchar", - Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"mp3\"}, {\"id\":1, \"value\":\"weba\"}, {\"id\":2, \"value\":\"oga\"}, {\"id\":3, \"value\":\"opus\"}]}" - }); + _database.Insert( + Cms.Core.Constants.DatabaseSchema.Tables.DataType, + "pk", + false, + new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.Tags, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.Tags, + DbType = "Ntext", + Configuration = "{\"group\":\"default\", \"storageType\":\"Json\"}" + }); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + if (_database.Exists(Cms.Core.Constants.DataTypes.ImageCropper)) { - NodeId = Cms.Core.Constants.DataTypes.UploadArticle, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, - DbType = "Nvarchar", - Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"pdf\"}, {\"id\":1, \"value\":\"docx\"}, {\"id\":2, \"value\":\"doc\"}]}" - }); + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Cms.Core.Constants.DataTypes.ImageCropper, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" }); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + if (_database.Exists(Cms.Core.Constants.DataTypes.DefaultContentListView)) { - NodeId = Cms.Core.Constants.DataTypes.UploadVectorGraphics, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, - DbType = "Nvarchar", - Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"svg\"}]}" - }); + _database.Insert( + Cms.Core.Constants.DatabaseSchema.Tables.DataType, + "pk", + false, + new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.DefaultContentListView, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ListView, + DbType = "Nvarchar", + Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" + }); + } - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { - NodeId = 1051, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, - DbType = "Ntext", - Configuration = "{\"multiple\": false, \"validationLimit\":{\"min\":0,\"max\":1}}" - }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { - NodeId = 1052, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, - DbType = "Ntext", - Configuration = "{\"multiple\": true}" - }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { - NodeId = 1053, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, - DbType = "Ntext", - Configuration = "{\"filter\":\"" + Cms.Core.Constants.Conventions.MediaTypes.Image + "\", \"multiple\": false, \"validationLimit\":{\"min\":0,\"max\":1}}" - }); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { - NodeId = 1054, - EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, - DbType = "Ntext", - Configuration = "{\"filter\":\"" + Cms.Core.Constants.Conventions.MediaTypes.Image + "\", \"multiple\": true}" - }); + if (_database.Exists(Cms.Core.Constants.DataTypes.DefaultMediaListView)) + { + _database.Insert( + Cms.Core.Constants.DatabaseSchema.Tables.DataType, + "pk", + false, + new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.DefaultMediaListView, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ListView, + DbType = "Nvarchar", + Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" + }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.DefaultMembersListView)) + { + _database.Insert( + Cms.Core.Constants.DatabaseSchema.Tables.DataType, + "pk", + false, + new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.DefaultMembersListView, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ListView, + DbType = "Nvarchar", + Configuration = "{\"pageSize\":10, \"orderBy\":\"username\", \"orderDirection\":\"asc\", \"includeProperties\":[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]}" + }); + } + + // New UDI pickers with newer Ids + if (_database.Exists(1046)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1046, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.ContentPicker, DbType = "Nvarchar" }); + } + + if (_database.Exists(1047)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1047, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MemberPicker, DbType = "Nvarchar" }); + } + + if (_database.Exists(1048)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MemberPicker, DbType = "Ntext" }); + } + + if (_database.Exists(1049)) + { + _database.Insert( + Cms.Core.Constants.DatabaseSchema.Tables.DataType, + "pk", + false, + new DataTypeDto + { + NodeId = 1049, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker, + DbType = "Ntext", + Configuration = "{\"multiPicker\":1}" + }); + } + + if (_database.Exists(1050)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MultiUrlPicker, DbType = "Ntext" }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.UploadVideo)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.UploadVideo, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, + DbType = "Nvarchar", + Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"mp4\"}, {\"id\":1, \"value\":\"webm\"}, {\"id\":2, \"value\":\"ogv\"}]}" + }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.UploadAudio)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.UploadAudio, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, + DbType = "Nvarchar", + Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"mp3\"}, {\"id\":1, \"value\":\"weba\"}, {\"id\":2, \"value\":\"oga\"}, {\"id\":3, \"value\":\"opus\"}]}" + }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.UploadArticle)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.UploadArticle, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, + DbType = "Nvarchar", + Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"pdf\"}, {\"id\":1, \"value\":\"docx\"}, {\"id\":2, \"value\":\"doc\"}]}" + }); + } + + if (_database.Exists(Cms.Core.Constants.DataTypes.UploadVectorGraphics)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = Cms.Core.Constants.DataTypes.UploadVectorGraphics, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.UploadField, + DbType = "Nvarchar", + Configuration = "{\"fileExtensions\":[{\"id\":0, \"value\":\"svg\"}]}" + }); + } + + if (_database.Exists(1051)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = 1051, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, + DbType = "Ntext", + Configuration = "{\"multiple\": false, \"validationLimit\":{\"min\":0,\"max\":1}}" + }); + } + + if (_database.Exists(1052)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = 1052, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, + DbType = "Ntext", + Configuration = "{\"multiple\": true}" + }); + } + + if (_database.Exists(1053)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = 1053, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, + DbType = "Ntext", + Configuration = "{\"filter\":\"" + Cms.Core.Constants.Conventions.MediaTypes.Image + "\", \"multiple\": false, \"validationLimit\":{\"min\":0,\"max\":1}}" + }); + } + + if (_database.Exists(1054)) + { + _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto + { + NodeId = 1054, + EditorAlias = Cms.Core.Constants.PropertyEditors.Aliases.MediaPicker3, + DbType = "Ntext", + Configuration = "{\"filter\":\"" + Cms.Core.Constants.Conventions.MediaTypes.Image + "\", \"multiple\": true}" + }); + } } private void CreateRelationTypeData() { - var relationType = new RelationTypeDto { Id = 1, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = true, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, IsDependency = false}; - relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 2, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Document, ParentObjectType = Cms.Core.Constants.ObjectTypes.Document, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName, IsDependency = false }; - relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 3, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Cms.Core.Constants.ObjectTypes.Media, ParentObjectType = Cms.Core.Constants.ObjectTypes.Media, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName, IsDependency = false }; - relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + CreateRelationTypeData(1, Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, Cms.Core.Constants.ObjectTypes.Document, Cms.Core.Constants.ObjectTypes.Document, true, false); + CreateRelationTypeData(2, Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName, Cms.Core.Constants.ObjectTypes.Document, Cms.Core.Constants.ObjectTypes.Document, false, false); + CreateRelationTypeData(3, Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName, Cms.Core.Constants.ObjectTypes.Media, Cms.Core.Constants.ObjectTypes.Media, false, false); + CreateRelationTypeData(4, Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaAlias, Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaName, null, null, false, true); + CreateRelationTypeData(5, Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias, Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentName, null, null, false, true); + } - relationType = new RelationTypeDto { Id = 4, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedMediaName, IsDependency = true }; + private void CreateRelationTypeData(int id, string alias, string name, Guid? parentObjectType, Guid? childObjectType, bool dual, bool isDependency) + { + var relationType = new RelationTypeDto { Id = id, Alias = alias, ChildObjectType = childObjectType, ParentObjectType = parentObjectType, Dual = dual, Name = name, IsDependency = isDependency }; relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); - _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); - relationType = new RelationTypeDto { Id = 5, Alias = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Cms.Core.Constants.Conventions.RelationTypes.RelatedDocumentName, IsDependency = true }; - relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name); _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); } @@ -447,8 +995,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private void CreateKeyValueData() { - // on install, initialize the umbraco migration plan with the final state - + // On install, initialize the umbraco migration plan with the final state. var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion)); var stateValueKey = upgrader.StateValueKey; var finalState = upgrader.Plan.FinalState; @@ -458,14 +1005,51 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private void CreateLogViewerQueryData() { - var defaultData = MigrateLogViewerQueriesFromFileToDb.DefaultLogQueries.ToArray(); + LogViewerQueryDto[] defaultData = MigrateLogViewerQueriesFromFileToDb.DefaultLogQueries.ToArray(); for (int i = 0; i < defaultData.Length; i++) { - var dto = defaultData[i]; + LogViewerQueryDto dto = defaultData[i]; dto.Id = i+1; _database.Insert(Cms.Core.Constants.DatabaseSchema.Tables.LogViewerQuery, "id", false, dto); } } + + private void ConditionalInsert( + string configKey, + string id, + TDto dto, + string tableName, + string primaryKeyName, + bool autoIncrement = false) + { + var alwaysInsert = _entitiesToAlwaysCreate.ContainsKey(configKey) && + _entitiesToAlwaysCreate[configKey].InvariantContains(id.ToString()); + + InstallDefaultDataSettings installDefaultDataSettings = _installDefaultDataSettings.Get(configKey); + + // If there's no configuration, we assume to create. + if (installDefaultDataSettings == null) + { + alwaysInsert = true; + } + + if (!alwaysInsert && installDefaultDataSettings.InstallData == InstallDefaultDataOption.None) + { + return; + } + + if (!alwaysInsert && installDefaultDataSettings.InstallData == InstallDefaultDataOption.Values && !installDefaultDataSettings.Values.InvariantContains(id)) + { + return; + } + + if (!alwaysInsert && installDefaultDataSettings.InstallData == InstallDefaultDataOption.ExceptValues && installDefaultDataSettings.Values.InvariantContains(id)) + { + return; + } + + _database.Insert(tableName, primaryKeyName, autoIncrement, dto); + } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index 9dab0bd14a..20d01a3f1f 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NPoco; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Infrastructure.Migrations.Notifications; @@ -11,6 +15,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; using ColumnInfo = Umbraco.Cms.Infrastructure.Persistence.SqlSyntax.ColumnInfo; @@ -88,15 +93,33 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly IUmbracoVersion _umbracoVersion; + private readonly IOptionsMonitor _defaultDataCreationSettings; - public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger, - ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion, IEventAggregator eventAggregator) + [Obsolete("Please use constructor taking all parameters. Scheduled for removal in V11.")] + public DatabaseSchemaCreator( + IUmbracoDatabase database, + ILogger logger, + ILoggerFactory loggerFactory, + IUmbracoVersion umbracoVersion, + IEventAggregator eventAggregator) + : this (database, logger, loggerFactory, umbracoVersion, eventAggregator, StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + public DatabaseSchemaCreator( + IUmbracoDatabase database, + ILogger logger, + ILoggerFactory loggerFactory, + IUmbracoVersion umbracoVersion, + IEventAggregator eventAggregator, + IOptionsMonitor defaultDataCreationSettings) { _database = database ?? throw new ArgumentNullException(nameof(database)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion)); _eventAggregator = eventAggregator; + _defaultDataCreationSettings = defaultDataCreationSettings; if (_database?.SqlContext?.SqlSyntax == null) { @@ -153,8 +176,11 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install if (creatingNotification.Cancel == false) { - var dataCreation = new DatabaseDataCreator(_database, - _loggerFactory.CreateLogger(), _umbracoVersion); + var dataCreation = new DatabaseDataCreator( + _database, + _loggerFactory.CreateLogger(), + _umbracoVersion, + _defaultDataCreationSettings); foreach (Type table in OrderedTables) { CreateTable(false, table, dataCreation); @@ -419,9 +445,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install where T : new() { Type tableType = typeof(T); - CreateTable(overwrite, tableType, - new DatabaseDataCreator(_database, _loggerFactory.CreateLogger(), - _umbracoVersion)); + CreateTable( + overwrite, + tableType, + new DatabaseDataCreator( + _database, + _loggerFactory.CreateLogger(), + _umbracoVersion, + _defaultDataCreationSettings)); } /// diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreatorFactory.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreatorFactory.cs index a582e1b02a..6511557247 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreatorFactory.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreatorFactory.cs @@ -1,7 +1,12 @@ +using System; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Infrastructure.Migrations.Install { @@ -14,22 +19,35 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install private readonly ILoggerFactory _loggerFactory; private readonly IUmbracoVersion _umbracoVersion; private readonly IEventAggregator _eventAggregator; + private readonly IOptionsMonitor _installDefaultDataSettings; + [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in V11.")] public DatabaseSchemaCreatorFactory( ILogger logger, ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion, IEventAggregator eventAggregator) + : this(logger, loggerFactory, umbracoVersion, eventAggregator, StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + public DatabaseSchemaCreatorFactory( + ILogger logger, + ILoggerFactory loggerFactory, + IUmbracoVersion umbracoVersion, + IEventAggregator eventAggregator, + IOptionsMonitor installDefaultDataSettings) { _logger = logger; _loggerFactory = loggerFactory; _umbracoVersion = umbracoVersion; _eventAggregator = eventAggregator; + _installDefaultDataSettings = installDefaultDataSettings; } public DatabaseSchemaCreator Create(IUmbracoDatabase database) { - return new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion, _eventAggregator); + return new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion, _eventAggregator, _installDefaultDataSettings); } } } diff --git a/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs b/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs index 500d71dddc..20021dbd2c 100644 --- a/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs +++ b/tests/Umbraco.Tests.Integration.SqlCe/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs @@ -2,11 +2,13 @@ using System; using System.IO; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using NPoco; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence; @@ -67,7 +69,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence using (var database = UmbracoDatabaseFactory.CreateDatabase()) using (var transaction = database.GetTransaction()) { - schemaHelper = new DatabaseSchemaCreator(database, Mock.Of>(), NullLoggerFactory.Instance, new UmbracoVersion(), Mock.Of()); + schemaHelper = new DatabaseSchemaCreator(database, Mock.Of>(), NullLoggerFactory.Instance, new UmbracoVersion(), Mock.Of(), Mock.Of>(x => x.CurrentValue == new InstallDefaultDataSettings())); schemaHelper.InitializeDatabaseSchema(); transaction.Complete(); } diff --git a/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs b/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs index 564c45d5ad..f345c44b42 100644 --- a/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs +++ b/tests/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs @@ -10,9 +10,11 @@ using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence; @@ -137,7 +139,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing using (NPoco.ITransaction transaction = database.GetTransaction()) { - var schemaCreator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger(), _loggerFactory, new UmbracoVersion(), Mock.Of()); + var schemaCreator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger(), _loggerFactory, new UmbracoVersion(), Mock.Of(), Mock.Of>(x => x.CurrentValue == new InstallDefaultDataSettings())); schemaCreator.InitializeDatabaseSchema(); transaction.Complete(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs index 40dbad176e..2366ddcaee 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs @@ -6,9 +6,11 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Migrations; using Umbraco.Cms.Core.Scoping; @@ -56,7 +58,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Migrations upgrader.Execute(MigrationPlanExecutor, ScopeProvider, Mock.Of()); - var helper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator); + var helper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator, Mock.Of>()); bool exists = helper.TableExists("umbracoUser"); Assert.IsTrue(exists); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs index 13f543c130..59dedd397f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs @@ -1,6 +1,9 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Tests.Common.Testing; @@ -22,7 +25,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence using (var scope = ScopeProvider.CreateScope()) { - var schema = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator); + var schema = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion, EventAggregator, Mock.Of>(x => x.CurrentValue == new InstallDefaultDataSettings())); result = schema.ValidateSchema(DatabaseSchemaCreator.OrderedTables); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs index 4b3d159ff5..8fa1d7ef38 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs @@ -1,8 +1,12 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Tests.Common.Testing; @@ -23,7 +27,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -36,7 +40,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -50,7 +54,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -65,7 +69,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -79,7 +83,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -94,7 +98,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -108,7 +112,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -124,7 +128,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -138,7 +142,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -151,7 +155,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -166,7 +170,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -180,7 +184,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -197,7 +201,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -213,7 +217,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -227,7 +231,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -240,7 +244,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -253,7 +257,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -266,7 +270,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -279,7 +283,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -295,7 +299,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -312,7 +316,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -327,7 +331,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -345,7 +349,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -362,7 +366,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -377,7 +381,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -392,7 +396,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -405,7 +409,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -418,7 +422,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -439,7 +443,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); @@ -452,7 +456,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -466,7 +470,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -480,7 +484,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -495,7 +499,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence { using (var scope = ScopeProvider.CreateScope()) { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator); + var helper = CreateDatabaseSchemaCreator(scope); helper.CreateTable(); helper.CreateTable(); @@ -504,5 +508,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence scope.Complete(); } } + + private DatabaseSchemaCreator CreateDatabaseSchemaCreator(IScope scope) => + new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion, EventAggregator, Mock.Of>(x => x.CurrentValue == new InstallDefaultDataSettings())); } } diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs index 672bbd0862..9a4ac2f206 100644 --- a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs +++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.BackOffice.Install; using Umbraco.Cms.Web.BackOffice.Routing; @@ -34,7 +35,8 @@ namespace Umbraco.Cms.Tests.UnitTests.AutoFixture.Customizations .Customize(new ConstructorCustomization(typeof(MemberController), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(BackOfficeController), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(MemberManager), new GreedyConstructorQuery())); + .Customize(new ConstructorCustomization(typeof(MemberManager), new GreedyConstructorQuery())) + .Customize(new ConstructorCustomization(typeof(DatabaseSchemaCreatorFactory), new GreedyConstructorQuery())); // When requesting an IUserStore ensure we actually uses a IUserLockoutStore fixture.Customize>(cc => cc.FromFactory(Mock.Of>)); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 4173417582..aaac553bbf 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -55,7 +55,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components Mock.Of>(x => x.CurrentValue == connectionStrings), new MapperCollection(() => Enumerable.Empty()), TestHelper.DbProviderFactoryCreator, - new DatabaseSchemaCreatorFactory(loggerFactory.CreateLogger(), loggerFactory, new UmbracoVersion(), Mock.Of()), + new DatabaseSchemaCreatorFactory(loggerFactory.CreateLogger(), loggerFactory, new UmbracoVersion(), Mock.Of(), Mock.Of>()), mapperCollection); var fs = new FileSystems(loggerFactory, IOHelper, Options.Create(globalSettings), Mock.Of()); From 7d18914ae6a69273924a30660ff7ae4099634a48 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 29 Mar 2022 08:32:02 +0200 Subject: [PATCH 36/42] v9: fix cannot map mvc route to client side request (#12088) * Implement UmbracoRequestOptions and check in UmbracoRequestMiddleware.cs * Fix breaking change * Add suggestion from Marc * Amend names Co-authored-by: Nikolaj Geisle --- .../UmbracoVirtualPageFilterAttribute.cs | 12 +++-- .../Middleware/UmbracoRequestMiddleware.cs | 46 ++++++++++++++++++- .../Routing/UmbracoRequestOptions.cs | 14 ++++++ 3 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.Common/Routing/UmbracoRequestOptions.cs diff --git a/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs index b2b3ed1d39..7b73b02b70 100644 --- a/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs @@ -2,10 +2,12 @@ using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Web; @@ -54,12 +56,14 @@ namespace Umbraco.Cms.Web.Common.Filters { if (content != null) { - IUmbracoContextAccessor umbracoContextAccessor = context.HttpContext.RequestServices.GetRequiredService(); + UriUtility uriUtility = context.HttpContext.RequestServices.GetRequiredService(); + + Uri originalRequestUrl = new Uri(context.HttpContext.Request.GetEncodedUrl()); + Uri cleanedUrl = uriUtility.UriToUmbraco(originalRequestUrl); + IPublishedRouter router = context.HttpContext.RequestServices.GetRequiredService(); - var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); - - IPublishedRequestBuilder requestBuilder = await router.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + IPublishedRequestBuilder requestBuilder = await router.CreateRequestAsync(cleanedUrl); requestBuilder.SetPublishedContent(content); IPublishedRequest publishedRequest = requestBuilder.Build(); diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 539c1c844f..5bddb85bbb 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -22,7 +22,9 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.WebAssets; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.Profiler; +using Umbraco.Cms.Web.Common.Routing; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Middleware @@ -53,6 +55,7 @@ namespace Umbraco.Cms.Web.Common.Middleware private readonly IRuntimeState _runtimeState; private readonly IVariationContextAccessor _variationContextAccessor; private readonly IDefaultCultureAccessor _defaultCultureAccessor; + private readonly IOptions _umbracoRequestOptions; private readonly SmidgeOptions _smidgeOptions; private readonly WebProfiler _profiler; @@ -66,6 +69,43 @@ namespace Umbraco.Cms.Web.Common.Middleware private static object s_firstBackOfficeRequestLocker = new object(); #pragma warning restore IDE0044 // Add readonly modifier + /// + /// Initializes a new instance of the class. + /// + // Obsolete, scheduled for removal in V11 + [Obsolete("Use constructor that takes an IOptions")] + public UmbracoRequestMiddleware( + ILogger logger, + IUmbracoContextFactory umbracoContextFactory, + IRequestCache requestCache, + PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler, + IEventAggregator eventAggregator, + IProfiler profiler, + IHostingEnvironment hostingEnvironment, + UmbracoRequestPaths umbracoRequestPaths, + BackOfficeWebAssets backOfficeWebAssets, + IOptions smidgeOptions, + IRuntimeState runtimeState, + IVariationContextAccessor variationContextAccessor, + IDefaultCultureAccessor defaultCultureAccessor) + : this( + logger, + umbracoContextFactory, + requestCache, + publishedSnapshotServiceEventHandler, + eventAggregator, + profiler, + hostingEnvironment, + umbracoRequestPaths, + backOfficeWebAssets, + smidgeOptions, + runtimeState, + variationContextAccessor, + defaultCultureAccessor, + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + /// /// Initializes a new instance of the class. /// @@ -82,7 +122,8 @@ namespace Umbraco.Cms.Web.Common.Middleware IOptions smidgeOptions, IRuntimeState runtimeState, IVariationContextAccessor variationContextAccessor, - IDefaultCultureAccessor defaultCultureAccessor) + IDefaultCultureAccessor defaultCultureAccessor, + IOptions umbracoRequestOptions) { _logger = logger; _umbracoContextFactory = umbracoContextFactory; @@ -95,6 +136,7 @@ namespace Umbraco.Cms.Web.Common.Middleware _runtimeState = runtimeState; _variationContextAccessor = variationContextAccessor; _defaultCultureAccessor = defaultCultureAccessor; + _umbracoRequestOptions = umbracoRequestOptions; _smidgeOptions = smidgeOptions.Value; _profiler = profiler as WebProfiler; // Ignore if not a WebProfiler } @@ -103,7 +145,7 @@ namespace Umbraco.Cms.Web.Common.Middleware public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // do not process if client-side request - if (context.Request.IsClientSideRequest()) + if (context.Request.IsClientSideRequest() && !_umbracoRequestOptions.Value.HandleAsServerSideRequest(context.Request)) { // we need this here because for bundle requests, these are 'client side' requests that we need to handle LazyInitializeBackOfficeServices(context.Request.Path); diff --git a/src/Umbraco.Web.Common/Routing/UmbracoRequestOptions.cs b/src/Umbraco.Web.Common/Routing/UmbracoRequestOptions.cs new file mode 100644 index 0000000000..2b27970cd6 --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/UmbracoRequestOptions.cs @@ -0,0 +1,14 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace Umbraco.Cms.Web.Common.Routing +{ + public class UmbracoRequestOptions + { + /// + /// Gets the delegate that checks if we're gonna handle a request as a client-side request + /// this returns true by default and can be overwritten in Startup.cs + /// + public Func HandleAsServerSideRequest { get; set; } = x => false; + } +} From 1ff6a953bb684f93e6a5aa7d0b4492d101992691 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 29 Mar 2022 09:05:40 +0200 Subject: [PATCH 37/42] Corrected the base URL to use when retrieving and setting public access details. (#12178) * Corrected the base URL to use when retrieving and setting public access details. * Fixed original issue from a merge conflict.. Now using the correct v9+ resource Co-authored-by: Bjarke Berg --- .../src/common/resources/content.resource.js | 4 ++-- .../src/views/content/content.protect.controller.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index b6f45ead0e..7e6c6658e5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -1259,7 +1259,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { getPublicAccess: function (contentId) { return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetPublicAccess", { + umbRequestHelper.getApiUrl("publicAccessApiBaseUrl", "GetPublicAccess", { contentId: contentId }) ), @@ -1308,7 +1308,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostPublicAccess", publicAccess) + umbRequestHelper.getApiUrl("publicAccessApiBaseUrl", "PostPublicAccess", publicAccess) ), "Failed to update public access for content item with id " + contentId ); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 92efb24e63..6561ed3499 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentProtectController($scope, $q, contentResource, memberResource, memberGroupResource, navigationService, localizationService, editorService) { + function ContentProtectController($scope, $q, publicAccessResource, memberResource, memberGroupResource, navigationService, localizationService, editorService) { var vm = this; var id = $scope.currentNode.id; @@ -30,7 +30,7 @@ vm.loading = true; // get the current public access protection - contentResource.getPublicAccess(id).then(function (publicAccess) { + publicAccessResource.getPublicAccess(id).then(function (publicAccess) { vm.loading = false; // init the current settings for public access (if any) @@ -94,7 +94,7 @@ vm.buttonState = "busy"; var groups = _.map(vm.groups, function (group) { return encodeURIComponent(group.name); }); var usernames = _.map(vm.members, function (member) { return member.username; }); - contentResource.updatePublicAccess(id, groups, usernames, vm.loginPage.id, vm.errorPage.id).then( + publicAccessResource.updatePublicAccess(id, groups, usernames, vm.loginPage.id, vm.errorPage.id).then( function () { localizationService.localize("publicAccess_paIsProtected", [$scope.currentNode.name]).then(function (value) { vm.success = { @@ -181,11 +181,11 @@ vm.members.push(newMember); } }) - ); + ); }); editorService.close(); navigationService.allowHideDialog(true); - // wait for all the member lookups to complete + // wait for all the member lookups to complete vm.loading = true; $q.all(promises).then(function() { vm.loading = false; @@ -239,7 +239,7 @@ function removeProtectionConfirm() { vm.buttonState = "busy"; - contentResource.removePublicAccess(id).then( + publicAccessResource.removePublicAccess(id).then( function () { localizationService.localize("publicAccess_paIsRemoved", [$scope.currentNode.name]).then(function(value) { vm.success = { From 0418be43184d3ed399ed30914d4e187684686d2e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 29 Mar 2022 09:05:40 +0200 Subject: [PATCH 38/42] Corrected the base URL to use when retrieving and setting public access details. (#12178) * Corrected the base URL to use when retrieving and setting public access details. * Fixed original issue from a merge conflict.. Now using the correct v9+ resource Co-authored-by: Bjarke Berg --- .../src/common/resources/content.resource.js | 4 ++-- .../src/views/content/content.protect.controller.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index b6f45ead0e..7e6c6658e5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -1259,7 +1259,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { getPublicAccess: function (contentId) { return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetPublicAccess", { + umbRequestHelper.getApiUrl("publicAccessApiBaseUrl", "GetPublicAccess", { contentId: contentId }) ), @@ -1308,7 +1308,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostPublicAccess", publicAccess) + umbRequestHelper.getApiUrl("publicAccessApiBaseUrl", "PostPublicAccess", publicAccess) ), "Failed to update public access for content item with id " + contentId ); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 92efb24e63..6561ed3499 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function ContentProtectController($scope, $q, contentResource, memberResource, memberGroupResource, navigationService, localizationService, editorService) { + function ContentProtectController($scope, $q, publicAccessResource, memberResource, memberGroupResource, navigationService, localizationService, editorService) { var vm = this; var id = $scope.currentNode.id; @@ -30,7 +30,7 @@ vm.loading = true; // get the current public access protection - contentResource.getPublicAccess(id).then(function (publicAccess) { + publicAccessResource.getPublicAccess(id).then(function (publicAccess) { vm.loading = false; // init the current settings for public access (if any) @@ -94,7 +94,7 @@ vm.buttonState = "busy"; var groups = _.map(vm.groups, function (group) { return encodeURIComponent(group.name); }); var usernames = _.map(vm.members, function (member) { return member.username; }); - contentResource.updatePublicAccess(id, groups, usernames, vm.loginPage.id, vm.errorPage.id).then( + publicAccessResource.updatePublicAccess(id, groups, usernames, vm.loginPage.id, vm.errorPage.id).then( function () { localizationService.localize("publicAccess_paIsProtected", [$scope.currentNode.name]).then(function (value) { vm.success = { @@ -181,11 +181,11 @@ vm.members.push(newMember); } }) - ); + ); }); editorService.close(); navigationService.allowHideDialog(true); - // wait for all the member lookups to complete + // wait for all the member lookups to complete vm.loading = true; $q.all(promises).then(function() { vm.loading = false; @@ -239,7 +239,7 @@ function removeProtectionConfirm() { vm.buttonState = "busy"; - contentResource.removePublicAccess(id).then( + publicAccessResource.removePublicAccess(id).then( function () { localizationService.localize("publicAccess_paIsRemoved", [$scope.currentNode.name]).then(function(value) { vm.success = { From 181a7cb27df092e2ef8784868d64c61e64098935 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Mar 2022 09:33:37 +0200 Subject: [PATCH 39/42] Bump versions to 9.4.2 --- .../UmbracoPackage/.template.config/template.json | 2 +- .../UmbracoProject/.template.config/template.json | 2 +- src/Directory.Build.props | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/build/templates/UmbracoPackage/.template.config/template.json index 64da9a2553..cea06dc245 100644 --- a/build/templates/UmbracoPackage/.template.config/template.json +++ b/build/templates/UmbracoPackage/.template.config/template.json @@ -24,7 +24,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.1", + "defaultValue": "9.4.2", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/build/templates/UmbracoProject/.template.config/template.json b/build/templates/UmbracoProject/.template.config/template.json index a2d6400f8f..7f1ebf1d05 100644 --- a/build/templates/UmbracoProject/.template.config/template.json +++ b/build/templates/UmbracoProject/.template.config/template.json @@ -57,7 +57,7 @@ "version": { "type": "parameter", "datatype": "string", - "defaultValue": "9.4.1", + "defaultValue": "9.4.2", "description": "The version of Umbraco to load using NuGet", "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" }, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 328f3c2278..f7531e3995 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,10 +3,10 @@ - 9.4.1 - 9.4.1 - 9.4.1 - 9.4.1 + 9.4.2 + 9.4.2 + 9.4.2 + 9.4.2 9.0 en-US Umbraco CMS From 93badabcb1e63f93fda0aa7793140b6689efc148 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 31 Mar 2022 11:52:02 +0200 Subject: [PATCH 40/42] Added extra (unnessasary) WHERE-clause to help sql server generate a smarter query plan (#12198) --- .../Repositories/Implement/TrackedReferencesRepository.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs index 0e70d47cbf..478018ed96 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs @@ -159,7 +159,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "cn", aliasRight: "c") .LeftJoin("ct").On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", aliasRight: "ct") .LeftJoin("ctn").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", aliasRight: "ctn") - .Where(x => x.NodeId == id, "pn"); + .Where(x => x.NodeId == id, "pn") + .Where(x => x.ChildId == id || x.ParentId == id, "r"); // This last Where is purely to help SqlServer make a smarter query plan. More info https://github.com/umbraco/Umbraco-CMS/issues/12190 if (filterMustBeIsDependency) { From 80c90f23d1499d7f7a4a2269d54610859df05921 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Wed, 6 Apr 2022 08:42:10 +0100 Subject: [PATCH 41/42] Fix issue - changing a document type broke the nucache data structure (#12209) (cherry picked from commit 15df448274edb291ae568148c61baa7541615247) --- src/Umbraco.PublishedCache.NuCache/ContentStore.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index 98fc4a3ffe..ddb8ea9057 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -443,10 +443,18 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache refreshedIdsA.Contains(x.ContentTypeId) && BuildKit(x, out _))) { - // replacing the node: must preserve the parents + // replacing the node: must preserve the relations var node = GetHead(_contentNodes, kit.Node.Id)?.Value; if (node != null) + { + // Preserve children kit.Node.FirstChildContentId = node.FirstChildContentId; + kit.Node.LastChildContentId = node.LastChildContentId; + + // Also preserve siblings + kit.Node.NextSiblingContentId = node.NextSiblingContentId; + kit.Node.PreviousSiblingContentId = node.PreviousSiblingContentId; + } SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); From 20666218d268e0cb8f0a2d9ddb3e0bf0d0ff031e Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 24 Mar 2022 13:48:43 +0100 Subject: [PATCH 42/42] Move templates to root --- build/azure-pipelines.yml | 4 +- build/build.ps1 | 63 +++++------------- .../UmbracoProject/.template.config/icon.png | Bin 22265 -> 0 bytes .../UmbracoProject/Views/_ViewImports.cshtml | 8 --- .../Umbraco.Templates.nuspec | 11 ++- .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../UmbracoPackage/package.manifest | 0 .../UmbracoPackage/UmbracoPackage.csproj | 0 .../build/UmbracoPackage.targets | 0 .../UmbracoProject/.gitignore | 0 .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 2 +- .../.template.config/template.json | 0 .../Properties/launchSettings.json | 0 .../UmbracoProject/UmbracoProject.csproj | 0 .../appsettings.Development.json | 0 .../UmbracoProject/appsettings.json | 0 .../.template.config => templates}/icon.png | Bin 20 files changed, 28 insertions(+), 62 deletions(-) delete mode 100644 build/templates/UmbracoProject/.template.config/icon.png delete mode 100644 build/templates/UmbracoProject/Views/_ViewImports.cshtml rename {build/templates => templates}/Umbraco.Templates.nuspec (64%) rename {build/templates => templates}/UmbracoPackage/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoPackage/.template.config/ide.host.json (89%) rename {build/templates => templates}/UmbracoPackage/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest (100%) rename {build/templates => templates}/UmbracoPackage/UmbracoPackage.csproj (100%) rename {build/templates => templates}/UmbracoPackage/build/UmbracoPackage.targets (100%) rename {build/templates => templates}/UmbracoProject/.gitignore (100%) rename {build/templates => templates}/UmbracoProject/.template.config/dotnetcli.host.json (100%) rename {build/templates => templates}/UmbracoProject/.template.config/ide.host.json (98%) rename {build/templates => templates}/UmbracoProject/.template.config/template.json (100%) rename {build/templates => templates}/UmbracoProject/Properties/launchSettings.json (100%) rename {build/templates => templates}/UmbracoProject/UmbracoProject.csproj (100%) rename {build/templates => templates}/UmbracoProject/appsettings.Development.json (100%) rename {build/templates => templates}/UmbracoProject/appsettings.json (100%) rename {build/templates/UmbracoPackage/.template.config => templates}/icon.png (100%) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 38d5e7673b..064b7aec0b 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -414,7 +414,7 @@ stages: #Update the version in templates also $templatePath = - 'build/templates/UmbracoProject/.template.config/template.json' + 'templates/UmbracoProject/.template.config/template.json' $a = Get-Content $templatePath -raw | ConvertFrom-Json @@ -424,7 +424,7 @@ stages: $templatePath = - 'build/templates/UmbracoPackage/.template.config/template.json' + 'templates/UmbracoPackage/.template.config/template.json' $a = Get-Content $templatePath -raw | ConvertFrom-Json diff --git a/build/build.ps1 b/build/build.ps1 index da4733d432..24fd548c61 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -11,7 +11,7 @@ [Alias("loc")] [switch] $local = $false, - # enable docfx + # enable docfx [Parameter(Mandatory=$false)] [Alias("doc")] [switch] $docfx = $false, @@ -40,7 +40,7 @@ @{ Continue = $continue }) if ($ubuild.OnError()) { return } - Write-Host "Umbraco Cms Build" + Write-Host "Umbraco CMS Build" Write-Host "Umbraco.Build v$($ubuild.BuildVersion)" # ################################################################ @@ -84,7 +84,7 @@ $this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache) $this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix) - $ignore = $this.ClearEnvVar("NODE_NO_HTTP2") + $this.ClearEnvVar("NODE_NO_HTTP2") }) $ubuild.DefineMethod("CompileBelle", @@ -171,11 +171,6 @@ $src = "$($this.SolutionRoot)\src" $log = "$($this.BuildTemp)\build.umbraco.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Umbraco" Write-Host "Logging to $log" @@ -255,27 +250,21 @@ $buildConfiguration = "Release" $log = "$($this.BuildTemp)\msbuild.tests.log" - if ($this.BuildEnv.VisualStudio -eq $null) - { - throw "Build environment does not provide VisualStudio." - } - Write-Host "Compile Tests" Write-Host "Logging to $log" # beware of the weird double \\ at the end of paths # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ - &$this.BuildEnv.VisualStudio.MsBuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` - /p:WarningLevel=0 ` - /p:Configuration=$buildConfiguration ` - /p:Platform=AnyCPU ` - /p:UseWPP_CopyWebApplication=True ` - /p:PipelineDependsOnBuild=False ` - /p:OutDir="$($this.BuildTemp)\tests\\" ` - /p:Verbosity=minimal ` - /t:Build ` - /tv:"$($this.BuildEnv.VisualStudio.ToolsVersion)" ` - /p:UmbracoBuild=True ` + &dotnet msbuild "$($this.SolutionRoot)\tests\Umbraco.Tests\Umbraco.Tests.csproj" ` + -target:Build ` + -property:WarningLevel=0 ` + -property:Configuration=$buildConfiguration ` + -property:Platform=AnyCPU ` + -property:UseWPP_CopyWebApplication=True ` + -property:PipelineDependsOnBuild=False ` + -property:OutDir="$($this.BuildTemp)\tests\\" ` + -property:Verbosity=minimal ` + -property:UmbracoBuild=True ` > $log # copy Umbraco.Persistence.SqlCe files into WebApp @@ -292,10 +281,6 @@ $src = "$($this.SolutionRoot)\src" $tmp = "$($this.BuildTemp)" - $out = "$($this.BuildOutput)" - $templates = "$($this.SolutionRoot)\build\templates" - - $buildConfiguration = "Release" # cleanup build Write-Host "Clean build" @@ -309,7 +294,6 @@ # create directories Write-Host "Create directories" mkdir "$tmp\WebApp\App_Data" > $null - mkdir "$tmp\Templates" > $null #mkdir "$tmp\WebApp\Media" > $null #mkdir "$tmp\WebApp\Views" > $null @@ -332,10 +316,6 @@ { $nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages") } - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\bin\amd64") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\WebApp\bin\x86") - #$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\WebApp\bin\amd64") # copy Belle Write-Host "Copy Belle" @@ -343,19 +323,6 @@ $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\js", "*", "$tmp\WebApp\wwwroot\umbraco\js") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\lib", "*", "$tmp\WebApp\wwwroot\umbraco\lib") $this.CopyFiles("$src\Umbraco.Web.UI\wwwroot\umbraco\views", "*", "$tmp\WebApp\wwwroot\umbraco\views") - - - - # Prepare templates - Write-Host "Copy template files" - $this.CopyFiles("$templates", "*", "$tmp\Templates") - - Write-Host "Copy files for dotnet templates" - $this.CopyFiles("$src\Umbraco.Web.UI", "Program.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI", "Startup.cs", "$tmp\Templates\UmbracoProject") - $this.CopyFiles("$src\Umbraco.Web.UI\Views", "*", "$tmp\Templates\UmbracoProject\Views") - - $this.RemoveDirectory("$tmp\Templates\UmbracoProject\bin") }) @@ -389,7 +356,7 @@ { Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" - $params = "-Source", $nugetsourceUmbraco + $params = "-Source", $nugetsourceUmbraco &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\umbraco-netcore-only.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) @@ -397,7 +364,7 @@ $ubuild.DefineMethod("PackageNuGet", { $nuspecs = "$($this.SolutionRoot)\build\NuSpecs" - $templates = "$($this.BuildTemp)\Templates" + $templates = "$($this.SolutionRoot)\templates" Write-Host "Create NuGet packages" diff --git a/build/templates/UmbracoProject/.template.config/icon.png b/build/templates/UmbracoProject/.template.config/icon.png deleted file mode 100644 index 6e94105808e0f05cb21726b4f729c979ed7d33ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22265 zcmb@tcT`i~vp5=R5F{v7sfzR}9YXH{(pv!OB=n{PLY0nyfYN&l0#XF&QiF&n9f8oB zh#;W^sZ#WJ{Cscu-FNSO@1Mt7S&MUKX76cx_Uzen;tljPDajehK_C#N)&n&o5QqTx z_eVwwyxBi&E&+jvTUr3Elg;KB{mR*9Sq5;l@FFCXPYwjxtW{iVEa%C@3(1C*05e4$9NR z>j@Mk&;HN2P~iW+uZ7v~{Db1>F3%3rHMpbd{TO~nLP$dBKDz?>9l6I&2&j>o#=j8* zPx9<8ettesVc~#)0HFXeA@9e|!Xh#fnI+0C_%3$_x=v?uQAl% zPaGe+`uMqed)@gvroDqV(ode99f05Yx542k*Z&;d>&d@~4=6(TFGN^G=)Um(jqc}) z_+QZfLjD`NkBhgT_Y)UypZ^fxzfAET;QvMlsPBI!LD~EKPsqBu|L^FYp8rLRCw}Vw zfFk})ssDwif1luqNuUp0*a-f_8~NA~uI>-_^1JtsCjROLYT$~3d%)COJ>gzY0PV`N zi_3`oKcE)>8&uWX!~3x@;8t*X_J0xm)g4sJ)7}|w?&{>{@}HyqC*UF6%lW@S{&MGm z{A*rQFIPYw(SKO@hY0qcFnM+vNx-rG8vP%T#s3KU4_iS0;ZN7w31Cm`AFBTZX{o9j zJoZModH}E|Mw%*jwA584MPwu;1x1DaB?@3TC_w9q2voE81F%Hzi;4)|7Z`u=>87o-U@HG&^jnP!N7?Y`2>);HD!g%2N_jRSFKrUd zN4I-LpTbnvv1pkwcIA%LX|Ii_&_Kb&YV+~g_z!NQet|k|7CIj@qxGX@1>Y6pld1=F zE_WW6ekc;MaudJ4tYOc2khtCh)3J2550vTkb5p-~mvWIBBrhf=_*S=*E|2G?3eR&f zm285b+o^%!Lg!nVbaX$1yALZqX}{rW)M8D9Q*(Tt;N4Cznc-wC#fchyTl)QHx7zdQ zG^1)JuK0Yy*Jh!&=URW{dI+<$-ocF!DxX5$P3Kd4yzY^T1;w7S9gCjupk)ZtsATcR z@^g!~-k-VmB(9@mVq;}o+9T&Qs;D<1QKzBe|YRV?4{GFx17X>qcE5E$=lRjc+r6NJfKDKH`9@?dn z+HSi#R;hwHC({1RvW3!mUk}>ytF*osz1G&%&Ci*V){c>~d#e=|Ji|Gz&A z9FkTZBqrNhvNj>sr7wKcWu-XDFfgDodWIC!iHd5r7>$k9lGaVYjD7;7>sQfx-~aLS~x>7N9H=CeB1F zb&|oDf7hI?IKJ55INqQ3vSGq(mc~YUJh@*>)F>Xgf1P|wC2iSSb5t z>-yTE;~J7ZB;_oZ#I4Xxs>26@MLfCDuyHHB<9&6OMr;S!Ej49-k$P&9uNiAS!W2D) zD@U6j6c3LC z=+pSlw_Z|Kmuz@M`5lF>ah+NO$^s>*ZakF*st#}Fxg%Wul1{3p*mD2fxrL1w#w)#~ zhj@p`KYC^YkL%*8WFskebvhlzjEoj*OFe|<-5(LR7h0E84-?c(xJi$qzZWfZg-8G!%`1vUZ`7)BZv-T@}fI?-+$N9&xRfQ^~?iR1n4ozJ=eai%LRGnmUiG-(NO-UY%U@v|V zuseSSunvT1i!Tf(W*D1JLIi#84d!Q+xb4Nzw%X49G}Xk-#2LH@qa)d^Fj!rVh&;D{ zZziIjVU;5dv-+u$Re!5EtJEX#o{xkNO?|vZ_{n@@1o?Dw$!Zr7ioLT?b8@4j?`aV$ z8tp8yGwGGH zn)@ZN8U|gsE#V`o#qh|Zq{_&JnaT|~qi&)inH;fPa>G7RWpHkzC;xNr=*;T!<}HGz z?;#}Q!>yp!(%0VJZyVUB$8Gio!r9_d#)?&%`=Z~tLKZW*!R|wkjLqLFOw~AX&bu8W zRuQXEKc!d;?XL=Z42GDnTeZGD&n-&}F;*A>ZU{Cr2N-#_$%$yN*ju^xTw9!E4>xWk z6;<`INNsYRs=ynPcba87PAA|Ku0qK~w)m^M(JPjk7LiK;vp zZV z=jV-Cc0Vttl=lx9l(U?=>%R+H6aoe=$Qu9T1fq+lB-mFqUgq~r@oKD0&aGkAuXltEg&v7wqkHX=A8cs%z+a_Z5m@5P?ZU+p~WCj8VksQu2TgTrMF%({qe z#SpckNtQWbY$UryppNkMH_WY*zdpKS+)uQrN=9-0vWK)OZ%1o*lO@rWZ37#RzM)Bd& zaz1^c6?Nqc-qrfXos)IDC9d3tA2FB_Oq_vZ+K9{G!c3$^Zn+XFVjr-|66)^!A5#y30byA|-seUL&# z!;fC}0XQNjm#~niJECESJ3LBZqS}(VhTp*jCEh~bb@S|g)loM&Tf03L;#TQ!Nnj!p z#s+2naM8g3zPJ5buHJwpxRbJ0w}%pD{jF`d=0;lO*qKzgcS-1+rktQn7zE2n9~3mE zdHX1CV*cpZg>P|qep0})qnqlC{XS&pqzUy-29`B7j(4GQqYUDoPd>Iux9)g0JiLv$ zxoT)fTMPH@o&5T0!_Hb%cXAmwAv0N|Ot7Htgz#_Y^}F$D3A2J;-BnfA{?yy9rqX;$ z=;yaFV33x^?{hOJr@)$WHl+m>st~d$p&%)0yj8i=DGkd~t30tuXxJJncH1{a%`Xfu z!wNJG<~Aeh(B*u4A!gLw6In`RQ|{v;nLQDN$D)g(Q^Vi6By}v~5$mMkM3^;X-d_Il z+l@Eu1BfT9!%%x&++<#O3i|oEM#yqgj&&P!KJChL^$A9`GCrX-fO9wpS^EC`NTrDm zmla`4_@;LbK+1a^)KBd9T(-sU^KhL&&$YR0425)K1SLv$ z4X6lP33?*QKQFE<3@1=$t(&2P4Jk=>9A#S;H|SrH{LDovtMs2zullc=M+6Wh;;{l^ zI(-~?LVN&*Aw0|H70N!pm+lVnHXSB7{AsM-uw^t`;jjS9+x~-(_+MVD>n?UMBd8JIxiuK`SPLv;IAJv!}hV(98+E4`LzaO>2M-{ z1apm5jqA27>GsIkHvzHg^0wHn*crsEO`n$t7)MWTy5gI3&S0pq3vh3WWv*XmJn=G* zZv_}ij1HY*LV1ehRCJHjuzU#bf{^SiiI;z%V1b!RXE!NrJ)~zUyyROhgd=t$r=)jpl4q+91y&7^mL&JPLya*23bh%~yu8-gEZo=Rur! z*VWeXpk|`xoaPSJRU^N%fkcJN^{+B8+v0MitGl$7ODa>tHFrsOP-k%|Go54*ECXQu z4NP3bQ-a0k0GUPOtWJm+`K;B9XZw>sfcd8rSj=Q(U_}-68eOy~DtsR1#@<5&!k_+2W0yqi zfZ@F$F|i>HDj&hz{t(_sUZoq;&NszIf3LLuF54}(q zKAvDHyiO&)cX;@x24k#vja%`fz*?(;9UAn`jS=Ok8ck#8p)fQA+6wwZGjG0h!VsNf zK?n`!bZL1)44c@)&3H1^sII5!x_CN~Nhyek>1c7+Lj(=GnX+wnd=jQJd!ZiJV9AB^ z%W>G(Bf|oMZ|w)i2&JMOR$He0d!Y|s4THUi6evAD;HD6W*NmX$`0~D)R@^yqfy7;W zDU^668_}*6MmrJ>X<0u*EnZU;>ywx~mp(>x z%QdX-&4B#nK{4+9Y3s&sIkCosA0RVO58>hzWO6No z601AC;;q>`|5xPciHKawE7AHl+y6ksPc}kHVTsj%TT~$w+?cKm`4m`eKHDu#U0k3m z>uY*ufi2g96lyT`T%XXJ94u~X#2F<4r9hIA;9ilW^bZd=Oj0a9BRr|9(u*?}g2kys z=@=GwGoJpI6svB4q^Ea*N6O3^lKfspB2lH5&%^BY%SND!**VRxg@8rahxyJ|IBfdb zE~#AgjYjR35IX3oqL^547sQSLZ`Pz6pfz3I$4@hyn=rVXI9j#G@2>`EA1$;>y&8o% zHu1RDB-+bp8nSXdgv>LuZpt_#C#S0<%ujR%TnT$?!5N91l$q|s=$h=r4-OB1FC&6Q zrnX2Km)yo`6T3H=;zqW>;&rQUvvVwb9aL*lK-JZ$)>#U$xT>CG4EH!yCAH!$F|iIi zqRPfOtJe<*&Cl(0*UyHiR)H{#NbszVC?b)Je)YwO|Je49({x_=%||8(1cLGkv+Kfd z{-Y6R1a_Y~`gVnpO<;YNlf%jBFL}Y?qdqtDMh9?Vs|o(`gRDd!GhnQ`>A04ou*lTb zyMpj~i2j42ajI2P=E{-boncbLnzr^~_Zc_~N|^jh(Z>PX+?=^NBvIak-&thjz~f$R zhMK&F?G*IymC-7I;1PXs=!{g>@r#r%B<@Y5zC?Ti)ei$0fvE71VepM2g@B;rTZ`9` zNSHT_G~~L3hwzeOTTTw0gTnUb12fuAomVEkxgRZ$S+>}KIO8D%HEbP9As8CWw9ev! zNwGxyx_LxpllwL=$AX%^_+I*o%4dfMIiw5V&TGgr3m6_YJX{k3jnJ9c*|B@8Dx>&{ z46Exty{}5Q`s!9Ipn_LKoWZRiso3w9uM9GaXNVrfK%mQCThFREM`yd~W*^1wK#%)@ zE#Y478+_n&tr!I}sXyVsxatCXQ$Q%e{Md5fd^ORZi@avb5T!y&HutUGK|Z2l7+dDxihLUZhcr!{axB7e{d2`lkQ@zVu~%CBLR zL0=z%1R!kSz5&)F5M5Nj<>K(DAn7;DNq16`b+kC)p`o3k4_TM~X^gEf=#2(Tn-}`q zfIJub$sS79XMY^OCARK(@AIet7Za$@zjhk4U;y$p3pm)uD6bx3*h+?jGG|DaS!v6% zh}viL+3NG+^Ba?5m+w=gF?^q5K{O+T*N(|8*JBlmSb46 z9qp2f1{Cj+oOP7w4IwW~@s5FD|K*~1^lmn)`7ecI83P^Ki86o(S!5;P`G#_w4LfE1Y-S(ggk z{0p4wz2@H_nYJa@uh&W>6WWsccTJGY9j&YnFt)Glxc?3#)+mMzU`A~_fY_JdZ?il2bBG0&F5H4kNFlp>z5j&l40IIsT9){rN^ z1dTykv>9?mLS*qg?fP8Y({}P~@adVad8q2);)*NtpV{B9DPKO!K{1fey1B-w%4`14p?JgJUD=fJBK^&zDc`ogGU#W~R7i zp1}e4kT4}KCFV98Q;zy%T*qHi71ufrP0*7oW?N`)V+dCHF-1gH?~kpTT+`R~Rm6<_ z4}uJvuWsO(gKlVRC-f_HRT2!=mdSKC%EnP@! zb@sWs;yzz9xtBV8@s99(!u63#; zEi*AKw;-uDKW{h>wWKY3SmYo}oDP5#itTP~?@?)Jt7_TlN1l$UsN?NYu!BG8($@SV zt=x;86e6ocRy7hq4XyR}rQk0Duk5M?hm*S+W ziR#;O8JJq0F|B8o(9+^!<-A#AC>ctz?Upf^UFE=7!1aXv)z^k%Hn^aUFT&Gxn?wjq z|0*3$l2(~@9A64HQcesH0970PddI+et55L*V5Ch3*<9^hm0jvI=(*tKBE#C?QynX+ zdA(c*^mE2--EJoRm{v<*@+B4a(~9>|66cz8;mIL7UfxzD`GYkStOGxw;{>dQgsthw z5^FyG2QJ#;&o(DJKGhsU4eZx;IU0ylRq6}lEEC^wB^{V(vB0P541DzQjknY+bg1>; zAe?Vzxf?AQF-9HhNH>nYCtTIVza;4aj;x`Z&GNYxG=o^)LnnN53Q1e1 z`jNP)T=WOlwFO`9MuDT^C?hYf2(^YS0zdTA=lpnD6g|R#VDRRb;GcWy#)(?HaBEV zY1c%H4+d^@`97?UsGW(xR@V_r(#zA?z`{Lp343l9)n{bf7FYLg+KR~U4GE#I%TQ&2 zuG-gcF`e8NH;=Z$^;hBfd8JDPe&z+Bk;VKE4&$52%e4X$JXouU^>@EJ`?o{Tkvwg;(G8Z>88R1bEfo&2zkt1>R=kFt4s6sqOJ^8;dWcVc~k`l z4UcT}Yfqn=9u*f|(a~nF(jO;v7#hyeovkL4{H#rxP3P0YBozq0vYJx{E|odzU0hD<1JNN%GuUsHqm&4!J+<;}bc~R9tA8S8YPqp-|6X6NRs?RUCwDY6Rw3BADs=NU?FCUlX;^y<^P(44CA6#~pN8r{#^Ok6UP&=m z51mWmy#y4j)UVi$#G^!Uv=k4k%Po6zHH4>QWJ2ov8D?sd zx!MMxg^s&+p75DO@?oRSO!uvbC3oyxVlOnyvPLcvspvyAT6K8if~W%ZQ*XxxYkH(K zEMx?(>eCV-h!3vi)~fM(75P=D;t|3EkZ)$yt{{&0OK8=e2KXJ|cXUTY{M1YCp=3)G z0>M|9jlf6XKoXp;G|X90FM^E+SW`DEdRb~}$&g^lbo> zN*odDB(Q$47$)pEc!v$#A`-e6cVUU`g%%pDGx?otbL3jY-Oh}_YxB{FbSPbIDW%ZX zZKAUE6Pk-mmk0j67l7ZCChL?Q-sM%GS>MeJ#&teSMO04+rJJ6nejgdx2{}%6tp0o` zp6F(dNCX_{8Mlw;;5pOA%^Km6wTtY0j-@^L;el$NsV1ANeiYk>9Hh&e3g)oq8R>}u2Xj=5aq&Pi8}42{8d>Z<(%L>4mmpd5hq!l8i+|% zV?sY%t+fh_&q0=-whH{gvmQ0A*%hSR{vsemB;O|3KND%H83M9?bfh$T$6w@79Hrn; zcNQVgqm|j)&ZHZ9PFj${Yo{3Rxj_E)Als&~h3|**9MT9#q7eoXZ2$vlffsW%Vz_2$ z*e0C5)}o@428gJ~N9Y9nRC%v*L{Ne_+j2D*>rU#A2v!SctOB)&;-t$qf4`LTp@MrO z-W2oM{BXollGiu{`zxDkNdh<9JA6@7t8G+E3Wj6BJ})h7 z9O_3Oa`i%o7sgbL)w-28(HfE5yV33?O!^U4$HS^IX}neF3!u6ea+G$^D`c^xq^L%0+C!ooBqkwz?`@iZ@9m_Q;r2ysa=7?D<`^XtpHm%xEoRv)IM?KT;*1F zinlQp=@=E@{mcgoAgX?Fq4K8Gq^PvRr@qB4egT=8y4Nh97=^Uav!=4xZ61Bh*vZC_ zwc*MrZFY2Lw3ue3;jDM0sywf0rX~e2Qx?P|tMN{?_X~KvfAHrJ-Iw3*vW~c8UnA}y zOvQ+H`g@`JhXx{^T#xL`SQ;w>dvZ4J(PzJ!)hUgIGM2ZCn@PMizqB)3xie(9%XN$X z9cH+yiY?pr=#Id(x8@aPQi!ogpF?5BoKOevPMaoV6vmM?)=2T~-wA;V@+P77P={ve*WyI$X|%@brZW-SkB2rxJnRc!97NNb%W+g+e>e+s7j|S=6b%U3W01W#a*& zG|IL<3^@L+3NGw zZtT=08~{#*MxC2T(3Lt>>4?+GYNly@VL!nFBVt^A$Nha3$0$3ZLBsV`+8nL4lN4nb zF<|1_iYGA*9i@Y)1P5Cu%63nZ=sjpw8mdQlz4Jc{y|gi^3C{Qh+E}I9-i% zcqppii+JDT8~cqd+d2KG(rMYn2+7G*I3ko#KW6h)lv+jm`^?PQNa1N_d<9TqWw!tjlnx1 z9~firS%gLjRP+X{k*{_h5xQ%McWCqm2?M<#EWRIkJ`d+=oQeY0$Ol23j^mtVDEH{o zerdhKT=!#P?k9;e6C9XOb*W;mJfyjZ)OR|jY*=hZf}D*4wF8sf=tr8`ZVgniBd`NN z@*q&y>QdOG=Mjn7CBg|2U_vN^7fx7C4@p=u5Bie@aZbB|q zxSpeXGiSqtdfL9MibgO|2Da%K`J8RJ;W`#*uacuH%(Ah&eXU!jPkfEI$`Rj}XWC0# zpXTSB&>p_g_DMka)o9JSjl1{EXH6>CqIxmP&V)KJf6r`*FW41~Q>G3<)m7fA5le&D z7iusx0$Yh+nmndG{JWPTa+`<5u2(Z*wAlEs=XZ~n-fXz9E0Kp_n!H(`nw~S-G zKakLwtLi}OxGb+hf>5?G)xC!|PGS3lKZ8C&Vf+13o1J9}lws7fkyhiDzY5fF(^{j{ zF}r!8n2;}_sJhhKk6j2ek@dhWMWRvYc}*(JF0<-(6QW@5=3|6R4zUf#(gkojXE>sj ze<3MC(3yTL^;rNG_amH!$8M(KT)|(Ps_OT{pOlPm;?}oHPmb;2{>-S(E#-Vw2DfKl ztbv?S4Txq(Yl66j-@JDG-7=eZS--y(pAbS6KmT?c5b_-h_wH~z1sn@(!_vx-8C7g= z5TohC^Y8g71a?SZdBth1UIXv~`@KS5Gi24O>o{h1T}w|#@TCl&2QHG`6FwX_K~?1( zYpuF2#$pHEsZr$cnb!%Sy#*G|b;%I?Y|e3?6}I=B!xHVMjyi!FAi5fWQ|JhA;Mj3H zrT(FJH@9HOcU7}2^vWN7yM+U1&WB*EHFpj(aRXSPFKF?LZW7+BdJxLU>?e46%a+Pa z0ojzd#?29QCF!|f86SmY)gu&s(Rcj9wH`7_IPjq37Mc$_-aVMkgbq&-Vf~3w%+E7o=|p%-lcbRsMQS~K$y>!U%P)I3A%iAi(W7#ws`;;WZt)2$6r2SC<${;d6c72!Vw3wrMC!b=0}us2 zGvk2sF9}x`;I>ltDdpl$A6d;K4CAWy(|-H+eYY$485KnLute`U_%JAZh!YkknN~+A zye`1!UQnrc#z-C3mkjiv8gOo0VM-$79Q`GIy^qDpdYmQ`Phi5p`P}o)N#Z9@3yv{l zlH)h&o|TUd^)@=^IJ1F>0X$YgXHoztC=1IlPx*-aZGD}vJhtrg*czE*WzckcSOSAg)~n@~@fHkTpW1shuO9WPE}|JSq&?=WzxKCRxYbg5T}@EW#h z9kpT0g;>3O{L>#s%4=c6bL5axJoEIR@b-u(eKXqyTZ1GK~xo;ru^hoUn$0eL+LnCbyU#@OL92ab37-ThJG~5bg6oW>x6W40g8eLv{PYZNNAO0jJ@oOBCh0VAU z`GyzNxdI4R>8J zd*h=G#+p2Atu03dQOCj~pp~GW&rg~!@gBxJVyxuqIb=37<951I2*nxyYQN~ir&2kZ zj$QM;P%Gog?^Q1Z%heY#&g1RJ^pDiin_2C|g2YZ~X}vXKNjXBQ7(5PG}L%DI;47V)yK*2oZAK~my>NyisNEob08@OJmG-;?1!yoJ`{9V?dG5rg9&bYCQ_T_0rT}c7S zzy$J}`)8$@UM-W|D^OM7Sx>}15QoyC%4Ei??%^qGHBhQa9)lj(w>t@)zWOEXjkOrM zwp^EKueCCZ`7=+S=wF9WUs}&eFbj^<* zG}GasOI|-m`)IzWxjBn2QXh?gE`+n8W1)bf6Ziy}5+XlUWf8EEerJ%&_rH&wm=Ev}=GXU|06KP2g`W=>P3`CL5mr)` z?S|q9U`;46U$q|3sP z3T+cZ?2H^^PJr{|W@cC0@+0@4ky6fvdu!{4O-O{SWlAzzKy+OZ&v|U_+icUPWMH7k z7GB_z$#ohft7m>j72<;O1L=Ut{N7i2>nB~)SQt0?@W6YV z^nJshg0Lsl4cn%eeNa#QO!eXY`1y(7bsY6&Mt%s&Acw%~f?w+fOxG-MYI7R{6Eu!F z$wZxYYbDDLB)kdfy$4i<&z5V-*S=e!YwKa7qLh2M{rvNBNZjey z_QK8&eE2vc3xpOYOuuxO-fEn3)Oc;qD!Ks|P&yemj|@7Xl<2b1FN%=vWupBN;~|e& zE7)2Iy_V$O$pqJ>u##A&uP(WMb|VQS8z1?T_U`?Zl%#wfk7zpjH$~GJviYUe8Yr8l zf$w%nDVUm5>=M$47qVjU?Fe@K{u1w;ucE1pZ?i6|3@fSh7#p9 z;Kpp@mpGm#B*Dn$D*+oDp_j6IJ;JQMe%gmK=tUTRP<=}XvyxRyswLw?E*MyI+@gPv zuCOS~?+%7Ldlq zejBrPf4=WsDIYjAJHteA+xHLeYS1H}yb4Db=P!Hd<(Ru~?O4e3ZAiM+*@GA6)uiSJ zkF};`7Z)~%$!{Q9uxQVWeHHne0&EA-wRLHB4cR93kYs|>Qp7ba`8ktEY;M|zw^!$Z zHMkr(;D|MQe{}lT&tQPt;2!JH7{~o&|BoCD!KoD3wWfTA@Qc8!ZnGi1N8BAmX9D#i zwC}PlKIXQ#E)iT(*WcEEy7@ZRpM5FGpNo?lxN6@a>Q1WqnHv25bv@)r{Lk&x&G6~v zQFvx8o|OsYMHEIf;&V(3tvnG+IuBQT*8Y?8?|pwf;*qokZ!V?FuN27;0 z3(pe@yfD!Aay6Y<1l|5}>S^~h!FctBOFbk8u@LdJ>_;)lkS;fL|5xh-_9j3=PqF{n zW>uTE@+qnFlzKLSjhgJs)O^A?wJ4;|Efg6F;yk0ir9^2Q#&E_{KWx=LY>DPYcaxr( z9|g;c=z zkhW3GosXmAdV$Ur4s#LyNBGF{u(r%wCNnN zq89hG29gt%L!ukw4U?mBn#_B1Nyx+vvmmm`wfkGvATPKR3q`QIPU5RY$mz3Vw? zqE6UiUEEk{*xH4!e?jEz&>8(|K==yba#Hf_5#{H3qfg~v19aLL;UD?}L@NZmZSs&v zAN{_etkqqDUmZbN(SWmx_yg*su|4B`J;{STV_NOl0-dIfHq=^2r)_7J6wt7#ZW;;? zfFGBYC1)D{xpk%wqyd`btYi5ZTreV_odiPohx9D#?5vG;8n{sA!P8AGgtFx_o_ahp zHR7qyPth9;0$S2qiyfT>1t%+i|5zb9&HB-8(z0_TjTZziPosY(^=t!2?Fm~Ib%_;F zH&{+qTrP;L&e=*m8u>#=e4~qWdMe}wO8*RLk$G!Di$(ewABK(BxHvWyj`iW|_&u|> zF{=2Zs^p#?o0^~_o7!eN+ehUE?JC{}!b&8LQlNb$<#WCe2eUzh!C3NB3OR!oJL`|b zN4J2_LuoI}E{XH1^qOo=@A|1SX(|JW4bTq@e9PqBZPOx^)LdjBO<&P5Q|x}eTj{wa z(bgJpo0)WvgjH}|zQQj@B7gABA5MJmnXVE23lfu8W#*Q&CNGi#)iD6PEp6{d#q3U=Y5tM^(dLr~9ANyNFfIe7x(&4g@hXtm#lO_Rd+u|({_!sYMY<0w>6img|&-yS+7#jHXDT6P= zjCMd)anmjK&;Ep8b;b~FJq_x%ZqgrOl3nu&n{(cD&tT65(HYv}ycx7q@v2;O!r|@j3WdqL*f1)FX9hzNcPWB>ZT4L!ysQABTyt^1-Hc1u|RqTA{H!# zJUX^jt~$(1?u3%e2O){-Z+g<~R(v=ek~C92effvxEDcCDzZ41^ZYKf>p&zLE=S2!e zTl@E^>k|n(YLd-@%A_BCl`%j3{m$n_Xxp5b+jdpN;p0n{kAq3->Dy=p#~!6b9vh|G zcQpIH(=C3FU14Nn3VgTv_ECC+Gn&R_Y*BtIAKvqJkiW&1S&7QV9G&pT+9mhW-sD9K z(d_M0Pc%l}_|vmm+V=vF0#N-pcgkFBH>B+*%dhrZ@NxOG-X3xXD!g#%g~scQ-;qG~ z*siM7@t@~G{5fI+@lH5q&!wYfJZ$dU~=@$leHW4juLdq6Tu79aE#CAe26?*%elPvi~fB!?;$yS7Nq&y%S>*G;kD>X!h-6GkK;=!;1-|#9~ocWzeCss{6yE=Uv ziRhFGZ?$d-%X}yuCMSPqz-}>Vxx%_4^wG1(gRgiI#Ap!ar>I z3Lr#vP_XI`nfzccA}CbXH=Zd)@&!nW8H7K}`0k$Dt&o^tkeq)4O=yW{AuD22iU`s_ zLp{^Dk)rO&ZTR910r&eNTX4Bbi{9jnUO-U2VxBEtFp$^nZs+~WQ}wk|$XBpWup>=n?-zL!p6Xa~I2Hu-4=(W- z{DDA?{q5i0YoN~4AP5!>$=8SoAtzaPrEOZ@mwDjG62d@U)429WhW__3G^y(NJ4QCj zOLUW(O~8_q^ox|+$xWtMam6$LFJzquGddB0;;hdz|4$+3{m@j>^>JE=Mw&uEx)qR? z)ez}jL`6WP2BfKkKq#^_l^PLI5ZDz3Y0{(z2)#&&BI-hDf|LYEkfjNMus}lh8Qy>3 z&9BML+&kyYopa`#&-YnXzj1ni7_2qn)V=Y#FdU+R(c zq|GZ~M&?h9{+)?j(kNueT}h36&i)8Uf!KW@@ehMq1_$yb9b z)6;ED`5!{IE-@dzq4L>Zc8EhI0FaE!vLM(Hi7Zb#OKQYdS+9kKd&Ho!G6a4fXR6yg zeEgYA;5X+?=8EE9uDWZ5uDt%_`02)Nc zZqtC@SBTIl@ui&Pn*aS^{I2PJP{7Me+JT1)Z$V$X+q=yE-w;tCVP%}oY)Ax^zP9Uh zceVYtV(A)hYznw*V@r41l}TU9PpA*Z?)1=t)NV(ic~Qq9OaNTGmSjg?UnpJPP`wSV zt(5TY*@e^n9BG3HFJEE8^l;FYB_6?nn894YU_6dHU5`3C_QL{=szVb61Pa=xX$JD= z>wgGNkO@zV23|Nu3F>HrqS&JD56|x*A6vY%_IgSd$kb-=c(%r0`;zjLURUTSHmFfl z9XgkaI&;1res$Z%)VaGo?nyA!Jj&Eo=S*NFh^IL+@v|gv%EojTS=mb7Dy^oU+tRj~ z&6f0{(nTT{J`4nHg$_7|IO$b`hHk9H7=Rd=2HKU@>Nu(!zfk0sWr1#(zKLZ3Xx%0> znJ@VbOU_)Y?!4gQ1jR*z&E>icmIbLBZ?DJ!z2`7H)5LNa1N5dDqs3y9zyLulk8WGy z&ye>RjEUA!oo6GZQNIjS1V*wPJErh8OHo%WgTZ7~iPO&q5!Q6w=Y}2-_0AJq^||*n znWy|ry)mi95=2S({y`@=1poNtYl;YTNzVQErZGu&W=5@-{NHV3>GP|+2V4+e+)uat z$6p6+wsP`2d#NF=Iq7JDUnQ&TeUdCE;&JxQrWC)}_M_0NWv2bgO5+;}gVV{$IBU(LAF)G+l6T4Gje>k~nOc4s^4_>KH zAm-_^s9_3aS=iJsAcWw_A^ib2j-$}vUVYL7HtIsgRkF?5)cMMsg=&yD!MJ{#@?zI zMzl9Sf5M91!9kr^-~IGRV($z;f3hlv9&8Um>NW7MK6?qNbcb))mG?k4NFz?rq~%B- z58^wZ(aaB7Lc5?=Y9R^M+Uka3ls^mUeaZD3!FoGzl_6nw)PCfl!nS`{H}W>1#Tm^zv&{ zu-UwBs6U<`BuW!|3Eh~%sl~9t!B2ufO=F8*Ao?fKFK-}qLqUsG!41YJZ=W;R7b8XZ zouxb$7*P@BVjY**J$jGCdKCk}!8--*X;*Xg?EZ}|>;9<4ecDa-s`zD+V$f}S%YcGQ zNL;A|1rjKju@jQp_ZQ$2GcGOLM_&jMXlM*hN5j>`QudF&+v&>!IAaDE%WUD3OR{!$ zI=Z>*nY=6XfUhYE*!@biahV=8@nvjugp|kFE}mb*EfcMY3YFXKC*1>9O6d|P)on7; zG&Qfd9wqruSc~VgJ~z_LSsL;yqA3=8I%{&aq!hIXim!?~OQ57r{toXinBonGNOw<* z=4%EL`_M;Cd)e zNoM54r5EN4NI~C0+j+tT#(uqQIoU@z_JHyTEH%m|Y}k52Ul^(6$gcs@f$i}#A#^zO z*UyI)b#7pP-Y*$j9wL2>?zzRhj8rn>m%h0n1p7|o3IXu^@G)uI%s`?A3Gm7YqWBgc zGk`8}=Yk(*I{{A9H^2?kBxNPX?U(=J)0xe93ZUK$XF&JHU=gh2HW%8ZD%`cf+{hH+ zv%wCdZ`5lcAgX;XL5d(;eXqy4979 z0WvabtQE%U&s=un@MTDloIa2Vo4>H8-PURi7+rNx^s@zsb1?WnVa2ZETW_`kNjhLy zq2H}!La;aKqMKB9#-^rQG5c2WKSSk5B|+TFr3#5WZkyox;z?taA134?W&jOg7X+59 zuC1kd1s0_eivp{2Oy<9+8ZM3bS$Si`&Qx0ZbGO6x>}{@HwgRK^YdXlQ-b~0b`0BHA z1>zZlm8Rj7AVZTi(}uBotTNX4Vbp^-pzMyz~fCKc{-`il?665Mh6KV0#F`&E^6rq8134N=O&c z$21)55A7{qCKPqd@oS_QC5Q17S%wxz{H(YL{lv}k1Np)xJ`$m9@1&84@B zZ=KR=ThbEw?40Rl=N$ane3fH``+#%nIB|6HTUqDFmb*rywS^?=z~7mx93vdMTl2nl zC{)mGS|C zo?@Lkb+@LsyWp8b*0S;V#wft{>%A-=*=P9=`<}T!-h=3JtY1nDC!>zTRx+nJ_jsh;weyz~EKLXMqhp3)a7Dc^ zsMe|bNEl^b1M3u)5LTGX35}Mw3TB7mJ;52g+%c)lH=rNPnfM@tWaL%Y#%wKt$uRk|ymMBR@8`jo0>N2ful;zY#R1BcKydc>?kL7q6Yk!_N?e1Akf3+5TMwptbOY?E@|1lPLIR- z?mVKujj5}T_s<&n6Sz?XHf(gZzIhj82rF1GqBlH=jy!#FCS6DbVEncY}0jD%ClBUBWxec%E@TilIR&L4y>DkTj~ zm|BVMtCh_~@}Q98D??Sv=i8^zuZ$YnPM9asuARlY!j$Lgok-!*)aKzwzNkbC|cezPQd@eQ^+{H)`;OMFq!%z>}xB;!2qLKAPq5sl}L(Rwjl z8^;vfR+)GSJGu71(HBstJ7{T_(Wj#ro3{;o=4ou8UKG6I9Y4O#_dp6V&rJ#u$;~lw z{u09V0XSw7T+O03;b&&r8<%fQX}PDsBc;2>)U2QsW?bS~0igvXhOhgqNbS6MUGFai z%I$iwvG|&KvCjFh=zW*C21U>`d*kSC<9xy5fAA7B3P#PNT?Vo-JKfRsQaiG4L(S5+ zKVNs?qQ>eK!)3Ypzrup#o7)abA-xZVsv&v*^ zV~W4EQX}oX;({QTcx1h0)H!0kU2Oj=9gEnl&XB0TGFBsBuc30+hwp4pe?jrDuUPie zJHjtViKWgWhmo7FZg}2y8TC2p%G%C9iB#RqJ^cx z*1}3P*B*JV?D{DVB;S^=+_cBDeDCjX)Vt)#-rPa7809>BAV^O?=u;bPTpfjp@<6 z81FyrI)*fME$hHrI^VA<+a71@k`qSQD)1KLGDkf#X@i=Zkd3tKVcR=D5`H8_o*g)X zYr3Bj-zxk4690lzDQx$~mu+TDNC#~v)IIwmHl^BIuU8{0g0iy{zOMMs3z)cS4)NJ< tgG}UV=j@07oS9reO7Q>xkPdc^+H3eZKk3N>nu8o1*G(+0R2sQG`5$QAj<)~+ diff --git a/build/templates/UmbracoProject/Views/_ViewImports.cshtml b/build/templates/UmbracoProject/Views/_ViewImports.cshtml deleted file mode 100644 index cb9a0b658e..0000000000 --- a/build/templates/UmbracoProject/Views/_ViewImports.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@using Umbraco.Web.UI -@using Umbraco.Extensions -@using Umbraco.Web.PublishedModels -@using Umbraco.Cms.Core.Models.PublishedContent -@using Microsoft.AspNetCore.Html -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Smidge -@inject Smidge.SmidgeHelper SmidgeHelper diff --git a/build/templates/Umbraco.Templates.nuspec b/templates/Umbraco.Templates.nuspec similarity index 64% rename from build/templates/Umbraco.Templates.nuspec rename to templates/Umbraco.Templates.nuspec index 21201d8d55..6561a41060 100644 --- a/build/templates/Umbraco.Templates.nuspec +++ b/templates/Umbraco.Templates.nuspec @@ -14,8 +14,15 @@ umbraco - + + + + + + + + + - diff --git a/build/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/dotnetcli.host.json rename to templates/UmbracoPackage/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json similarity index 89% rename from build/templates/UmbracoPackage/.template.config/ide.host.json rename to templates/UmbracoPackage/.template.config/ide.host.json index 8d3bae3e3c..aa4eb34552 100644 --- a/build/templates/UmbracoPackage/.template.config/ide.host.json +++ b/templates/UmbracoPackage/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoPackage", "text": "Umbraco Package - An empty Umbraco CMS package (Plugin)" diff --git a/build/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json similarity index 100% rename from build/templates/UmbracoPackage/.template.config/template.json rename to templates/UmbracoPackage/.template.config/template.json diff --git a/build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest similarity index 100% rename from build/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest rename to templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest diff --git a/build/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj similarity index 100% rename from build/templates/UmbracoPackage/UmbracoPackage.csproj rename to templates/UmbracoPackage/UmbracoPackage.csproj diff --git a/build/templates/UmbracoPackage/build/UmbracoPackage.targets b/templates/UmbracoPackage/build/UmbracoPackage.targets similarity index 100% rename from build/templates/UmbracoPackage/build/UmbracoPackage.targets rename to templates/UmbracoPackage/build/UmbracoPackage.targets diff --git a/build/templates/UmbracoProject/.gitignore b/templates/UmbracoProject/.gitignore similarity index 100% rename from build/templates/UmbracoProject/.gitignore rename to templates/UmbracoProject/.gitignore diff --git a/build/templates/UmbracoProject/.template.config/dotnetcli.host.json b/templates/UmbracoProject/.template.config/dotnetcli.host.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/dotnetcli.host.json rename to templates/UmbracoProject/.template.config/dotnetcli.host.json diff --git a/build/templates/UmbracoProject/.template.config/ide.host.json b/templates/UmbracoProject/.template.config/ide.host.json similarity index 98% rename from build/templates/UmbracoProject/.template.config/ide.host.json rename to templates/UmbracoProject/.template.config/ide.host.json index 1ee7a492aa..d44cb154c1 100644 --- a/build/templates/UmbracoProject/.template.config/ide.host.json +++ b/templates/UmbracoProject/.template.config/ide.host.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/vs-2017.3.host", "order" : 0, - "icon": "icon.png", + "icon": "../../icon.png", "description": { "id": "UmbracoProject", "text": "Umbraco Web Application - An empty Umbraco CMS web application" diff --git a/build/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json similarity index 100% rename from build/templates/UmbracoProject/.template.config/template.json rename to templates/UmbracoProject/.template.config/template.json diff --git a/build/templates/UmbracoProject/Properties/launchSettings.json b/templates/UmbracoProject/Properties/launchSettings.json similarity index 100% rename from build/templates/UmbracoProject/Properties/launchSettings.json rename to templates/UmbracoProject/Properties/launchSettings.json diff --git a/build/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj similarity index 100% rename from build/templates/UmbracoProject/UmbracoProject.csproj rename to templates/UmbracoProject/UmbracoProject.csproj diff --git a/build/templates/UmbracoProject/appsettings.Development.json b/templates/UmbracoProject/appsettings.Development.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.Development.json rename to templates/UmbracoProject/appsettings.Development.json diff --git a/build/templates/UmbracoProject/appsettings.json b/templates/UmbracoProject/appsettings.json similarity index 100% rename from build/templates/UmbracoProject/appsettings.json rename to templates/UmbracoProject/appsettings.json diff --git a/build/templates/UmbracoPackage/.template.config/icon.png b/templates/icon.png similarity index 100% rename from build/templates/UmbracoPackage/.template.config/icon.png rename to templates/icon.png