From c70c8d86f9ffe00660f835906cc06c1c627d3603 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:58:52 +0200 Subject: [PATCH 1/6] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 662e2268cb..0880e2551d 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 662e2268cbdbf7562fe9bcd4fc9222bb24e26563 +Subproject commit 0880e2551d8f0e3dc095742795f5182b9467d6a1 From a76af1de9db3444d0bbaf6b324d9cbfe6276e49a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:47:33 +0200 Subject: [PATCH 2/6] Fix `IContentBase.GetUdi()` extension method to support document-blueprint entity type (#16939) * Add tests for all UDI entity types * Fix IContentBase UDI entity type for blueprints * Remove redundant switch statements and reorder methods (cherry picked from commit 609b5f76d4c74e9a0ee9a7d1ffa62562ffbfaf23) --- .../Extensions/UdiGetterExtensions.cs | 424 +++++++++--------- .../Builders/ContentBuilder.cs | 10 + .../Extensions/UdiGetterExtensionsTests.cs | 268 ++++++++++- 3 files changed, 460 insertions(+), 242 deletions(-) diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs index e4b11ccb6c..66c5002604 100644 --- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs @@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Extensions; /// -/// Provides extension methods that return udis for Umbraco entities. +/// Provides extension methods that return UDIs for Umbraco entities. /// public static class UdiGetterExtensions { @@ -19,11 +19,177 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static GuidUdi GetUdi(this ITemplate entity) + public static Udi GetUdi(this IEntity entity) { ArgumentNullException.ThrowIfNull(entity); - return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); + return entity switch + { + // Concrete types + EntityContainer container => container.GetUdi(), + Script script => script.GetUdi(), + Stylesheet stylesheet => stylesheet.GetUdi(), + // Interfaces + IContentBase contentBase => contentBase.GetUdi(), + IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(), + IDataType dataType => dataType.GetUdi(), + IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(), + ILanguage language => language.GetUdi(), + IMemberGroup memberGroup => memberGroup.GetUdi(), + IPartialView partialView => partialView.GetUdi(), + IRelationType relationType => relationType.GetUdi(), + ITemplate template => template.GetUdi(), + IWebhook webhook => webhook.GetUdi(), + _ => throw new NotSupportedException($"Entity type {entity.GetType().FullName} is not supported."), + }; + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this EntityContainer entity) + { + ArgumentNullException.ThrowIfNull(entity); + + string entityType; + if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) + { + entityType = Constants.UdiEntityType.DataTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) + { + entityType = Constants.UdiEntityType.DocumentTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) + { + entityType = Constants.UdiEntityType.MediaTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint) + { + entityType = Constants.UdiEntityType.DocumentBlueprintContainer; + } + else + { + throw new NotSupportedException($"Contained object type {entity.ContainedObjectType} is not supported."); + } + + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this Script entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this Stylesheet entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContentBase entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return entity switch + { + IContent content => content.GetUdi(), + IMedia media => media.GetUdi(), + IMember member => member.GetUdi(), + _ => throw new NotSupportedException($"Content base type {entity.GetType().FullName} is not supported."), + }; + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContent entity) + { + ArgumentNullException.ThrowIfNull(entity); + + string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document; + + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IMedia entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IMember entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IContentTypeComposition entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return entity switch + { + IContentType contentType => contentType.GetUdi(), + IMediaType mediaType => mediaType.GetUdi(), + IMemberType memberType => memberType.GetUdi(), + _ => throw new NotSupportedException($"Composition type {entity.GetType().FullName} is not supported."), + }; } /// @@ -68,42 +234,6 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.MemberType, entity.Key).EnsureClosed(); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMemberGroup entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IContentTypeComposition entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType = entity switch - { - IContentType => Constants.UdiEntityType.DocumentType, - IMediaType => Constants.UdiEntityType.MediaType, - IMemberType => Constants.UdiEntityType.MemberType, - _ => throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)), - }; - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - /// /// Gets the entity identifier of the entity. /// @@ -118,129 +248,6 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.DataType, entity.Key).EnsureClosed(); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this EntityContainer entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType; - if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) - { - entityType = Constants.UdiEntityType.DataTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) - { - entityType = Constants.UdiEntityType.DocumentTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) - { - entityType = Constants.UdiEntityType.MediaTypeContainer; - } - else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint) - { - entityType = Constants.UdiEntityType.DocumentBlueprintContainer; - } - else - { - throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType)); - } - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMedia entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IContent entity) - { - ArgumentNullException.ThrowIfNull(entity); - - string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document; - - return new GuidUdi(entityType, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static GuidUdi GetUdi(this IMember entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Stylesheet entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Script entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); - } - - /// - /// Gets the UDI from a path. - /// - /// The type of the entity. - /// The path. - /// - /// The entity identifier of the entity. - /// - private static StringUdi GetUdiFromPath(string entityType, string path) - { - string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/"); - - return new StringUdi(entityType, id).EnsureClosed(); - } - /// /// Gets the entity identifier of the entity. /// @@ -262,11 +269,11 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static StringUdi GetUdi(this IPartialView entity) + public static StringUdi GetUdi(this ILanguage entity) { ArgumentNullException.ThrowIfNull(entity); - return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path); + return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); } /// @@ -276,19 +283,25 @@ public static class UdiGetterExtensions /// /// The entity identifier of the entity. /// - public static GuidUdi GetUdi(this IContentBase entity) + public static GuidUdi GetUdi(this IMemberGroup entity) { ArgumentNullException.ThrowIfNull(entity); - string type = entity switch - { - IContent => Constants.UdiEntityType.Document, - IMedia => Constants.UdiEntityType.Media, - IMember => Constants.UdiEntityType.Member, - _ => throw new NotSupportedException(string.Format("Content base type {0} is not supported.", entity.GetType().FullName)), - }; + return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); + } - return new GuidUdi(type, entity.Key).EnsureClosed(); + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this IPartialView entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path); } /// @@ -305,6 +318,20 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed(); } + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this ITemplate entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); + } + /// /// Gets the entity identifier of the entity. /// @@ -320,56 +347,17 @@ public static class UdiGetterExtensions } /// - /// Gets the entity identifier of the entity. + /// Gets the UDI from a path. /// - /// The entity. + /// The type of the entity. + /// The path. /// /// The entity identifier of the entity. /// - public static StringUdi GetUdi(this ILanguage entity) + private static StringUdi GetUdiFromPath(string entityType, string path) { - ArgumentNullException.ThrowIfNull(entity); + string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/"); - return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static Udi GetUdi(this IEntity entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return entity switch - { - // Concrete types - EntityContainer container => container.GetUdi(), - Stylesheet stylesheet => stylesheet.GetUdi(), - Script script => script.GetUdi(), - // Content types - IContentType contentType => contentType.GetUdi(), - IMediaType mediaType => mediaType.GetUdi(), - IMemberType memberType => memberType.GetUdi(), - IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(), - // Content - IContent content => content.GetUdi(), - IMedia media => media.GetUdi(), - IMember member => member.GetUdi(), - IContentBase contentBase => contentBase.GetUdi(), - // Other - IDataType dataTypeComposition => dataTypeComposition.GetUdi(), - IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(), - ILanguage language => language.GetUdi(), - IMemberGroup memberGroup => memberGroup.GetUdi(), - IPartialView partialView => partialView.GetUdi(), - IRelationType relationType => relationType.GetUdi(), - ITemplate template => template.GetUdi(), - IWebhook webhook => webhook.GetUdi(), - _ => throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)), - }; + return new StringUdi(entityType, id).EnsureClosed(); } } diff --git a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs index 05cc4b80dd..53c2f50f10 100644 --- a/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/ContentBuilder.cs @@ -53,6 +53,7 @@ public class ContentBuilder private int? _sortOrder; private bool? _trashed; private DateTime? _updateDate; + private bool? _blueprint; private int? _versionId; DateTime? IWithCreateDateBuilder.CreateDate @@ -145,6 +146,13 @@ public class ContentBuilder set => _updateDate = value; } + public ContentBuilder WithBlueprint(bool blueprint) + { + _blueprint = blueprint; + + return this; + } + public ContentBuilder WithVersionId(int versionId) { _versionId = versionId; @@ -217,6 +225,7 @@ public class ContentBuilder { var id = _id ?? 0; var versionId = _versionId ?? 0; + var blueprint = _blueprint ?? false; var key = _key ?? Guid.NewGuid(); var parentId = _parentId ?? -1; var parent = _parent; @@ -253,6 +262,7 @@ public class ContentBuilder content.Id = id; content.VersionId = versionId; + content.Blueprint = blueprint; content.Key = key; content.CreateDate = createDate; content.UpdateDate = updateDate; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs index b5da0a4f2f..f5a5e79234 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -13,15 +14,22 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions; [TestFixture] public class UdiGetterExtensionsTests { - [TestCase("style.css", "umb://stylesheet/style.css")] - [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] - [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] - public void GetUdiForStylesheet(string path, string expected) + [TestCase(Constants.ObjectTypes.Strings.DataType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://data-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.DocumentType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.MediaType, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media-type-container/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase(Constants.ObjectTypes.Strings.DocumentBlueprint, "6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-blueprint-container/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForEntityContainer(Guid containedObjectType, Guid key, string expected) { - var builder = new StylesheetBuilder(); - var stylesheet = builder.WithPath(path).Build(); - var result = stylesheet.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + EntityContainer entity = new EntityContainer(containedObjectType) + { + Key = key + }; + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("script.js", "umb://script/script.js")] @@ -29,10 +37,195 @@ public class UdiGetterExtensionsTests [TestCase("editor/script.js", "umb://script/editor/script.js")] public void GetUdiForScript(string path, string expected) { - var builder = new ScriptBuilder(); - var script = builder.WithPath(path).Build(); - var result = script.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + Script entity = new ScriptBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("style.css", "umb://stylesheet/style.css")] + [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] + [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] + public void GetUdiForStylesheet(string path, string expected) + { + Stylesheet entity = new StylesheetBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", false, "umb://document/6ad82c70685c4e049b36d81bd779d16f")] + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", true, "umb://document-blueprint/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForContent(Guid key, bool blueprint, string expected) + { + Content entity = new ContentBuilder() + .WithKey(key) + .WithBlueprint(blueprint) + .WithContentType(ContentTypeBuilder.CreateBasicContentType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMedia(Guid key, string expected) + { + Media entity = new MediaBuilder() + .WithKey(key) + .WithMediaType(MediaTypeBuilder.CreateImageMediaType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMember(Guid key, string expected) + { + Member entity = new MemberBuilder() + .WithKey(key) + .WithMemberType(MemberTypeBuilder.CreateSimpleMemberType()) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentBase)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://document-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForContentType(Guid key, string expected) + { + IContentType entity = new ContentTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://media-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMediaType(Guid key, string expected) + { + IMediaType entity = new MediaTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMemberType(Guid key, string expected) + { + IMemberType entity = new MemberTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IContentTypeComposition)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://data-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForDataType(Guid key, string expected) + { + DataType entity = new DataTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://dictionary-item/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForDictionaryItem(Guid key, string expected) + { + DictionaryItem entity = new DictionaryItemBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("en-US", "umb://language/en-US")] + [TestCase("en", "umb://language/en")] + public void GetUdiForLanguage(string isoCode, string expected) + { + ILanguage entity = new LanguageBuilder() + .WithCultureInfo(isoCode) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://member-group/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForMemberGroup(Guid key, string expected) + { + MemberGroup entity = new MemberGroupBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("test.cshtml", "umb://partial-view/test.cshtml")] @@ -40,26 +233,53 @@ public class UdiGetterExtensionsTests [TestCase("editor/test.cshtml", "umb://partial-view/editor/test.cshtml")] public void GetUdiForPartialView(string path, string expected) { - var builder = new PartialViewBuilder(); - var partialView = builder + IPartialView entity = new PartialViewBuilder() .WithPath(path) .Build(); - var result = partialView.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://relation-type/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForRelationType(Guid key, string expected) + { + IRelationTypeWithIsDependency entity = new RelationTypeBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://template/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForTemplate(Guid key, string expected) + { + ITemplate entity = new TemplateBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://webhook/6ad82c70685c4e049b36d81bd779d16f")] - public void GetUdiForWebhook(string key, string expected) + public void GetUdiForWebhook(Guid key, string expected) { - var builder = new WebhookBuilder(); - var webhook = builder - .WithKey(Guid.Parse(key)) + Webhook entity = new WebhookBuilder() + .WithKey(key) .Build(); - Udi result = webhook.GetUdi(); - Assert.AreEqual(expected, result.ToString()); + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); - result = ((IEntity)webhook).GetUdi(); - Assert.AreEqual(expected, result.ToString()); + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); } } From 635d9b83f9e1837cd9a640a024b54bae2bdf9644 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:51:16 +0200 Subject: [PATCH 3/6] Use version of the assembly with the same name as the package ID (#16544) (cherry picked from commit 14a0e622781278ec6212c4daebd873de04677df4) --- .../Services/Implement/PackagingService.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs index c4e152e0b5..53d6c8ba6c 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs @@ -1,3 +1,6 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.Loader; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -354,8 +357,16 @@ public class PackagingService : IPackagingService if (!string.IsNullOrEmpty(packageManifest.Version)) { + // Always use package version from manifest installedPackage.Version = packageManifest.Version; } + else if (string.IsNullOrEmpty(installedPackage.Version) && + string.IsNullOrEmpty(installedPackage.PackageId) is false && + TryGetAssemblyInformationalVersion(installedPackage.PackageId, out string? version)) + { + // Use version of the assembly with the same name as the package ID + installedPackage.Version = version; + } } // Return all packages with an ID or name in the package manifest or package migrations @@ -414,4 +425,20 @@ public class PackagingService : IPackagingService return packageFile.CreateReadStream(); } + + private static bool TryGetAssemblyInformationalVersion(string name, [NotNullWhen(true)] out string? version) + { + foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies) + { + AssemblyName assemblyName = assembly.GetName(); + if (string.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase) && + assembly.TryGetInformationalVersion(out version)) + { + return true; + } + } + + version = null; + return false; + } } From 0c1daa290b677351d37e647afd4170e91dfa256c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:52:39 +0200 Subject: [PATCH 4/6] Add `RemoveDefault()` extension method to fluent API for CMS webhook events (#15424) * Add RemoveDefault extension method * Move default webhook event types to single list (cherry picked from commit 8f26263178656f092972e845e332962e9e158f1e) --- ...hookEventCollectionBuilderCmsExtensions.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs index 361891de6a..679e105b72 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs @@ -9,6 +9,15 @@ namespace Umbraco.Cms.Core.DependencyInjection; /// public static class WebhookEventCollectionBuilderCmsExtensions { + private static readonly Type[] _defaultTypes = + [ + typeof(ContentDeletedWebhookEvent), + typeof(ContentPublishedWebhookEvent), + typeof(ContentUnpublishedWebhookEvent), + typeof(MediaDeletedWebhookEvent), + typeof(MediaSavedWebhookEvent), + ]; + /// /// Adds the default webhook events. /// @@ -21,12 +30,24 @@ public static class WebhookEventCollectionBuilderCmsExtensions /// public static WebhookEventCollectionBuilderCms AddDefault(this WebhookEventCollectionBuilderCms builder) { - builder.Builder - .Add() - .Add() - .Add() - .Add() - .Add(); + builder.Builder.Add(_defaultTypes); + + return builder; + } + + /// + /// Removes the default webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms RemoveDefault(this WebhookEventCollectionBuilderCms builder) + { + foreach (Type type in _defaultTypes) + { + builder.Builder.Remove(type); + } return builder; } From d3a67fe4e0c2b22b6fb948ef52130e46d1591ea1 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 26 Sep 2024 08:45:08 +0200 Subject: [PATCH 5/6] Move all V14 User and User Group migration to pre-migrations (#17130) (cherry picked from commit 910d70302e695e0864296ef420a5a2ff77549c16) --- .../Migrations/Upgrade/UmbracoPlan.cs | 8 ++++---- .../Upgrade/UmbracoPremigrationPlan.cs | 4 ++++ .../Upgrade/V_14_0_0/AddGuidsToUserGroups.cs | 17 ++++++----------- .../Upgrade/V_14_0_0/AddGuidsToUsers.cs | 11 ++++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 7d6537d4da..713168a6c7 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -67,15 +67,15 @@ public class UmbracoPlan : MigrationPlan // To 14.0.0 To("{419827A0-4FCE-464B-A8F3-247C6092AF55}"); - To("{69E12556-D9B3-493A-8E8A-65EC89FB658D}"); - To("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}"); - To("{A8E01644-9F2E-4988-8341-587EF5B7EA69}"); + To("{69E12556-D9B3-493A-8E8A-65EC89FB658D}"); + To("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}"); + To("{A8E01644-9F2E-4988-8341-587EF5B7EA69}"); To("{E073DBC0-9E8E-4C92-8210-9CB18364F46E}"); To("{80D282A4-5497-47FF-991F-BC0BCE603121}"); To("{96525697-E9DC-4198-B136-25AD033442B8}"); To("{7FC5AC9B-6F56-415B-913E-4A900629B853}"); To("{1539A010-2EB5-4163-8518-4AE2AA98AFC6}"); - To("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}"); + To("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}"); To("{0D82C836-96DD-480D-A924-7964E458BD34}"); To("{1A0FBC8A-6FC6-456C-805C-B94816B2E570}"); To("{302DE171-6D83-4B6B-B3C0-AC8808A16CA1}"); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs index c9d23acb90..a8131a0da4 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs @@ -53,5 +53,9 @@ public class UmbracoPremigrationPlan : MigrationPlan // To 14.0.0 To("{76FBF80E-37E6-462E-ADC1-25668F56151D}"); + To("{37CF4AC3-8489-44BC-A7E8-64908FEEC656}"); + To("{7BCB5352-B2ED-4D4B-B27D-ECDED930B50A}"); + To("{3E69BF9B-BEAB-41B1-BB11-15383CCA1C7F}"); + To("{F12C609B-86B9-4386-AFA4-78E02857247C}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs index ad8e5091ea..67bdaf7395 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs @@ -20,6 +20,12 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase protected override void Migrate() { + // If the new column already exists we'll do nothing. + if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) + { + return; + } + // SQL server can simply add the column, but for SQLite this won't work, // so we'll have to create a new table and copy over data. if (DatabaseType != DatabaseType.SQLite) @@ -37,11 +43,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase using IDisposable notificationSuppression = scope.Notifications.Suppress(); ScopeDatabase(scope); - if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) - { - return; - } - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); AddColumnIfNotExists(columns, NewColumnName); @@ -68,12 +69,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase using IDisposable notificationSuppression = scope.Notifications.Suppress(); ScopeDatabase(scope); - // If the new column already exists we'll do nothing. - if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) - { - return; - } - // This isn't pretty, // But since you cannot alter columns, we have to copy the data over and delete the old table. // However we cannot do this due to foreign keys, so temporarily disable these keys while migrating. diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs index fe730fd2b8..6d5dbce1d8 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs @@ -26,6 +26,12 @@ internal class AddGuidsToUsers : UnscopedMigrationBase protected override void Migrate() { + if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName)) + { + Context.Complete(); + return; + } + InvalidateBackofficeUserAccess = true; using IScope scope = _scopeProvider.CreateScope(); using IDisposable notificationSuppression = scope.Notifications.Suppress(); @@ -75,11 +81,6 @@ internal class AddGuidsToUsers : UnscopedMigrationBase private void MigrateSqlite() { - if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName)) - { - return; - } - /* * We commit the initial transaction started by the scope. This is required in order to disable the foreign keys. * We then begin a new transaction, this transaction will be committed or rolled back by the scope, like normal. From a43db4ff0550d62f0263c14d9c9d3652e3fcb1a0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:52:40 +0200 Subject: [PATCH 6/6] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 0880e2551d..a5500fd8de 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 0880e2551d8f0e3dc095742795f5182b9467d6a1 +Subproject commit a5500fd8de2fb14285d8f99cd3d5edeb1c5eb462