From ea2ecab238919b2fc60e0a12b818a20ed6a8b9e9 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Tue, 23 Aug 2022 09:52:42 +0200 Subject: [PATCH 1/7] Add RC to version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 0f9ad08c15..9988b6b4bd 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.2.0", + "version": "10.2.0-rc", "assemblyVersion": { "precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor. }, From b6e0e2df565062033fb8cf541df9de1dd7278810 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 23 Aug 2022 10:51:45 +0200 Subject: [PATCH 2/7] Content modal heading fix (#12797) (#12880) * Fixes to modal and group headings * updated modal headings for h1 and h2 * Updated line-height Changed line-height: 0 to line-height: 1.3 and added margin: 0 Co-authored-by: Tiffany Prosser --- .../src/less/components/html/umb-group-panel.less | 2 ++ src/Umbraco.Web.UI.Client/src/less/modals.less | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less index 97646e57b9..32be2f2245 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less @@ -17,6 +17,8 @@ .umb-group-panel__header h2 { font-size: @fontSizeMedium; font-weight: bold; + line-height: 1.3; + margin: 0; } .umb-group-panel__content { diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 256d7baf0a..e944bba1b2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -16,6 +16,7 @@ white-space: nowrap } +.umb-modalcolumn-header h1, .umb-modalcolumn-header h2 { margin: 0; white-space: nowrap; From e7d6b9ef7a9be7940d43011ba5088acd82f2dcb5 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 23 Aug 2022 14:12:05 +0200 Subject: [PATCH 3/7] Add ClaimsPrincipalFactory and ensure that claims are flowed from the MemberIdentityUser (#12877) --- .../UmbracoBuilder.MembersIdentity.cs | 1 + .../Security/MemberClaimsPrincipalFactory.cs | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/Umbraco.Web.Common/Security/MemberClaimsPrincipalFactory.cs diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs index 123e39f5e2..79c60bc230 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs @@ -52,6 +52,7 @@ public static partial class UmbracoBuilderExtensions .AddRoleManager() .AddMemberManager() .AddSignInManager() + .AddClaimsPrincipalFactory() .AddErrorDescriber() .AddUserConfirmation>(); diff --git a/src/Umbraco.Web.Common/Security/MemberClaimsPrincipalFactory.cs b/src/Umbraco.Web.Common/Security/MemberClaimsPrincipalFactory.cs new file mode 100644 index 0000000000..dfc860e467 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberClaimsPrincipalFactory.cs @@ -0,0 +1,53 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Security; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.Security; + +/// +/// A for members +/// +public class MemberClaimsPrincipalFactory : UserClaimsPrincipalFactory +{ + /// + /// Initializes a new instance of the class. + /// + /// The user manager + /// The + public MemberClaimsPrincipalFactory( + UserManager userManager, + IOptions optionsAccessor) + : base(userManager, optionsAccessor) + { + } + + protected virtual string AuthenticationType => IdentityConstants.ApplicationScheme; + + /// + protected override async Task GenerateClaimsAsync(MemberIdentityUser user) + { + // Get the base + ClaimsIdentity baseIdentity = await base.GenerateClaimsAsync(user); + + // now create a new one with the correct authentication type + var memberIdentity = new ClaimsIdentity( + AuthenticationType, + Options.ClaimsIdentity.UserNameClaimType, + Options.ClaimsIdentity.RoleClaimType); + + // and merge all others from the base implementation + memberIdentity.MergeAllClaims(baseIdentity); + + // And merge claims added to the user, for instance in OnExternalLogin, we need to do this explicitly, since the claims are IdentityClaims, so it's not handled by memberIdentity. + foreach (Claim claim in user.Claims + .Where(claim => memberIdentity.HasClaim(claim.ClaimType, claim.ClaimValue) is false) + .Select(x => new Claim(x.ClaimType, x.ClaimValue))) + { + memberIdentity.AddClaim(claim); + } + + return memberIdentity; + } +} From b4f2de79b80bacb90705f1b21a64c02b23a1d6bb Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 24 Aug 2022 13:52:39 +0200 Subject: [PATCH 4/7] Bugfix: Variant permission languages needs a clear cache to work fully for current user (#12875) * emit event when user group is saved * clear current user cache when languages and user groups are saved (cherry picked from commit 128dd42b4711fad2cc1b0a5324934daa0af7a560) --- .../src/common/services/user.service.js | 12 +++++++++++- .../src/views/users/group.controller.js | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 00871caab1..ee9aa0864f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -8,6 +8,14 @@ angular.module('umbraco.services') // this is used so that we know when to go and get the user's remaining seconds directly. var lastServerTimeoutSet = null; + eventsService.on("editors.languages.languageSaved", () => { + service.refreshCurrentUser(); + }); + + eventsService.on("editors.userGroups.userGroupSaved", () => { + service.refreshCurrentUser(); + }); + function openLoginDialog(isTimedOut) { //broadcast a global event that the user is no longer logged in const args = { isTimedOut: isTimedOut }; @@ -158,7 +166,7 @@ angular.module('umbraco.services') } }); - return { + const service = { /** Internal method to display the login dialog */ _showLoginDialog: function () { @@ -292,4 +300,6 @@ angular.module('umbraco.services') } }; + return service; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js index 6e8238b431..712aa6fef3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function UserGroupEditController($scope, $location, $routeParams, userGroupsResource, localizationService, contentEditingHelper, editorService, overlayService) { + function UserGroupEditController($scope, $location, $routeParams, userGroupsResource, localizationService, contentEditingHelper, editorService, overlayService, eventsService) { var infiniteMode = $scope.model && $scope.model.infiniteMode; var id = infiniteMode ? $scope.model.id : $routeParams.id; @@ -107,6 +107,11 @@ setSectionIcon(vm.userGroup.sections); makeBreadcrumbs(); vm.page.saveButtonState = "success"; + + eventsService.emit("editors.userGroups.userGroupSaved", { + userGroup: vm.userGroup, + isNew: create + }); } }, function (err) { vm.page.saveButtonState = "error"; From 439878883a6555e5dce45ddc8cd5e78e263f4653 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 24 Aug 2022 16:17:56 +0200 Subject: [PATCH 5/7] V10/bugfix/variant permissions segments (#12890) * Remove null check from MapperContext.SetCulture and .SetSegment We need to be able to set these to null, since null = invariant / default segment * show segment label on property * Add ContentVariation to ContentPropertyDisplay * Add ContentVariation to DocumentTypeDisplay * Change variations to be on ContentTypeBasic.cs * don't cache value * show correct label and unlock text for culture and segment variations * make lock overlay take up less space Co-authored-by: nikolajlauridsen Co-authored-by: Zeegaan --- .../EmbeddedResources/Lang/en_us.xml | 6 ++++- .../ContentEditing/ContentPropertyDisplay.cs | 3 +++ .../Models/ContentEditing/ContentTypeBasic.cs | 3 +++ .../ContentEditing/DocumentTypeDisplay.cs | 3 +++ .../Mapping/ContentPropertyDisplayMapper.cs | 3 +++ .../Mapping/ContentTypeMapDefinition.cs | 4 +++ .../Models/Mapping/MapperContextExtensions.cs | 16 ++--------- .../content/umbtabbedcontent.directive.js | 6 +---- .../property/umbproperty.directive.js | 1 + .../property/umbpropertyeditor.directive.js | 11 ++++++++ .../less/components/umb-property-editor.less | 2 +- .../src/less/components/umb-property.less | 22 ++++++++++----- .../content/umb-tabbed-content.html | 6 +++-- .../property/umb-property-editor.html | 11 ++++++-- .../components/property/umb-property.html | 27 ++++++++++++++++--- 15 files changed, 89 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 70aa1c2d5c..9a44528b53 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -1967,7 +1967,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Fall back language none - %0% is shared across all languages.]]> + %0% is shared across languages and segments.]]> + %0% is shared across all languages.]]> + %0% is shared across all segments.]]> + Shared: Languages + Shared: Segments Add parameter diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs index d0f2b9aed6..9368de8ce1 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs @@ -15,6 +15,9 @@ public class ContentPropertyDisplay : ContentPropertyBasic Validation = new PropertyTypeValidation(); } + [DataMember(Name = "variations")] + public ContentVariation Variations { get; set; } + [DataMember(Name = "label", IsRequired = true)] [Required] public string? Label { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs index 90dd6ce5c9..0ba344f7fc 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs @@ -42,6 +42,9 @@ public class ContentTypeBasic : EntityBasic [DataMember(Name = "thumbnail")] public string? Thumbnail { get; set; } + [DataMember(Name = "variations")] + public ContentVariation Variations { get; set; } + /// /// Returns true if the icon represents a CSS class instead of a file path /// diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs index 3c292a7e6a..110ab98547 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs @@ -16,6 +16,9 @@ public class DocumentTypeDisplay : ContentTypeCompositionDisplay AllowedTemplates { get; set; } + [DataMember(Name = "variations")] + public ContentVariation Variations { get; set; } + [DataMember(Name = "defaultTemplate")] public EntityBasic? DefaultTemplate { get; set; } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs index eb6c6d92e0..22407219eb 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -56,6 +56,9 @@ internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper c.SortOrder).Select(x => x.Id.Value); target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias); target.LockedCompositeContentTypes = MapLockedCompositions(source); + target.Variations = source.Variations; } // no MapAll - relies on the non-generic method @@ -794,6 +797,7 @@ public class ContentTypeMapDefinition : IMapDefinition : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); target.Trashed = source.Trashed; target.Udi = source.Udi; + target.Variations = source.Variations; } // no MapAll - relies on the non-generic method diff --git a/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs b/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs index 70d4826ab6..c8637c3042 100644 --- a/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs +++ b/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs @@ -26,24 +26,12 @@ public static class MapperContextExtensions /// /// Sets a context culture. /// - public static void SetCulture(this MapperContext context, string? culture) - { - if (culture is not null) - { - context.Items[CultureKey] = culture; - } - } + public static void SetCulture(this MapperContext context, string? culture) => context.Items[CultureKey] = culture; /// /// Sets a context segment. /// - public static void SetSegment(this MapperContext context, string? segment) - { - if (segment is not null) - { - context.Items[SegmentKey] = segment; - } - } + public static void SetSegment(this MapperContext context, string? segment) => context.Items[SegmentKey] = segment; /// /// Get included properties. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js index b91baa16c0..4eefa5176d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js @@ -189,10 +189,6 @@ return false; } - if (property.$propertyEditorDisabledCache) { - return property.$propertyEditorDisabledCache; - } - var contentLanguage = $scope.content.language; var otherCreatedVariants = $scope.contentNodeModel.variants.filter(x => x.compositeId !== $scope.content.compositeId && (x.state !== "NotCreated" || x.name !== null)).length === 0; @@ -205,7 +201,7 @@ var canEditSegment = property.segment === $scope.content.segment; - return property.$propertyEditorDisabledCache = !canEditCulture || !canEditSegment; + return !canEditCulture || !canEditSegment; } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 25e55455db..702cd5aeda 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -19,6 +19,7 @@ }, bindings: { property: "=", + node: "<", elementKey: "@", // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) propertyAlias: "@", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js index cc9f36852a..2d1ff762d5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js @@ -11,6 +11,7 @@ function umbPropEditor(umbPropEditorHelper, localizationService) { return { scope: { model: "=", + node: "<", isPreValue: "@", preview: "<", allowUnlock: " 1 && !property.culture" + show-inherit="contentNodeModel.variants.length > 1 && property.variation !== 'CultureAndSegment'" inherits-from="defaultVariant.displayName"> - +
- + + + + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 40666133b2..8d0087b395 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -28,10 +28,31 @@ -
- - {{ vm.property.culture }} +
+ + + + + + + + + + + {{ vm.property.culture }} + + + + + + + + {{ vm.property.segment }} + Default + +
+
From 8f6e28e0ad7b845dfc1182c1beef522c27334ab7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 25 Aug 2022 12:31:10 +0200 Subject: [PATCH 6/7] Fixed issue with saving member groups, that was not persisted, if only the member groups was changed. (#12905) --- src/Umbraco.Infrastructure/Security/MemberUserStore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 3229264d39..70efeeb739 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -794,6 +794,7 @@ public class MemberUserStore : UmbracoUserStore Date: Thu, 25 Aug 2022 15:30:56 +0200 Subject: [PATCH 7/7] Fix memory leaks (#12900) * Fix leak for PublicAccessEntry * Fix memory leak for PropertyTypeCollection * Don't clone the lazy property group ID when caching property types, it is explicitly assigned at runtime (cherry picked from commit 10b8f63052c3c69c24c1d81d8e166003b3fc2db8) --- .../Collections/DeepCloneableList.cs | 24 ++----------------- .../EventClearingObservableCollection.cs | 11 ++++++++- src/Umbraco.Core/Models/ContentBase.cs | 8 ------- src/Umbraco.Core/Models/DeepCloneHelper.cs | 12 ++++++++++ .../Models/Entities/BeingDirtyBase.cs | 2 ++ src/Umbraco.Core/Models/PropertyType.cs | 7 +----- src/Umbraco.Core/Models/PublicAccessEntry.cs | 16 +++++++------ 7 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs index 301795281c..4f22ac094e 100644 --- a/src/Umbraco.Core/Collections/DeepCloneableList.cs +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -41,17 +41,7 @@ public class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty // we are cloning once, so create a new list in none mode // and deep clone all items into it var newList = new DeepCloneableList(ListCloneBehavior.None); - foreach (T item in this) - { - if (item is IDeepCloneable dc) - { - newList.Add((T)dc.DeepClone()); - } - else - { - newList.Add(item); - } - } + DeepCloneHelper.CloneListItems, T>(this, newList); return newList; case ListCloneBehavior.None: @@ -60,17 +50,7 @@ public class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty case ListCloneBehavior.Always: // always clone to new list var newList2 = new DeepCloneableList(ListCloneBehavior.Always); - foreach (T item in this) - { - if (item is IDeepCloneable dc) - { - newList2.Add((T)dc.DeepClone()); - } - else - { - newList2.Add(item); - } - } + DeepCloneHelper.CloneListItems, T>(this, newList2); return newList2; default: diff --git a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs index 579716456b..baf131ca80 100644 --- a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs +++ b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Collections; @@ -7,7 +8,7 @@ namespace Umbraco.Cms.Core.Collections; /// Allows clearing all event handlers ///
/// -public class EventClearingObservableCollection : ObservableCollection, INotifyCollectionChanged +public class EventClearingObservableCollection : ObservableCollection, INotifyCollectionChanged, IDeepCloneable { // need to explicitly implement with event accessor syntax in order to override in order to to clear // c# events are weird, they do not behave the same way as other c# things that are 'virtual', @@ -39,4 +40,12 @@ public class EventClearingObservableCollection : ObservableCollection event /// public void ClearCollectionChangedEvents() => _changed = null; + + public object DeepClone() + { + var clone = new EventClearingObservableCollection(); + DeepCloneHelper.CloneListItems, TValue>(this, clone); + + return clone; + } } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index e9fcc61e7c..8ecf0bfc8f 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -60,15 +60,8 @@ public abstract class ContentBase : TreeEntityBase, IContentBase _contentTypeId = contentType.Id; _properties = properties ?? throw new ArgumentNullException(nameof(properties)); _properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); - - // track all property types on this content type, these can never change during the lifetime of this single instance - // there is no real extra memory overhead of doing this since these property types are already cached on this object via the - // properties already. - AllPropertyTypes = new List(contentType.CompositionPropertyTypes); } - internal IReadOnlyList AllPropertyTypes { get; } - [IgnoreDataMember] public ISimpleContentType ContentType { get; private set; } @@ -146,7 +139,6 @@ public abstract class ContentBase : TreeEntityBase, IContentBase base.PerformDeepClone(clone); var clonedContent = (ContentBase)clone; - // Need to manually clone this since it's not settable clonedContent.ContentType = ContentType; diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index ce34dab6f1..7b9110f432 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -212,4 +212,16 @@ public static class DeepCloneHelper public bool IsList => GenericListType != null; } + + public static void CloneListItems(TList source, TList target) + where TList : ICollection + { + target.Clear(); + foreach (TEntity entity in source) + { + target.Add(entity is IDeepCloneable deepCloneableEntity + ? (TEntity)deepCloneableEntity.DeepClone() + : entity); + } + } } diff --git a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs index 887477c743..18bc984853 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs @@ -85,6 +85,8 @@ public abstract class BeingDirtyBase : IRememberBeingDirty /// public event PropertyChangedEventHandler? PropertyChanged; + protected void ClearPropertyChangedEvents() => PropertyChanged = null; + /// /// Registers that a property has changed. /// diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 0699ecbc0d..8fe28f7751 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -283,12 +283,7 @@ public class PropertyType : EntityBase, IPropertyType, IEquatable base.PerformDeepClone(clone); var clonedEntity = (PropertyType)clone; - - // need to manually assign the Lazy value as it will not be automatically mapped - if (PropertyGroupId != null) - { - clonedEntity._propertyGroupId = new Lazy(() => PropertyGroupId.Value); - } + clonedEntity.ClearPropertyChangedEvents(); } /// diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 8789ef5052..fdf3761366 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -10,7 +10,7 @@ namespace Umbraco.Cms.Core.Models; public class PublicAccessEntry : EntityBase { private readonly List _removedRules = new(); - private readonly EventClearingObservableCollection _ruleCollection; + private EventClearingObservableCollection _ruleCollection; private int _loginNodeId; private int _noAccessNodeId; private int _protectedNodeId; @@ -144,11 +144,13 @@ public class PublicAccessEntry : EntityBase var cloneEntity = (PublicAccessEntry)clone; - if (cloneEntity._ruleCollection != null) - { - cloneEntity._ruleCollection.ClearCollectionChangedEvents(); // clear this event handler if any - cloneEntity._ruleCollection.CollectionChanged += - cloneEntity.RuleCollection_CollectionChanged; // re-assign correct event handler - } + // clear this event handler if any + cloneEntity._ruleCollection.ClearCollectionChangedEvents(); + + // clone the rule collection explicitly + cloneEntity._ruleCollection = (EventClearingObservableCollection)_ruleCollection.DeepClone(); + + // re-assign correct event handler + cloneEntity._ruleCollection.CollectionChanged += cloneEntity.RuleCollection_CollectionChanged; } }