From ec761adee291aad73756da3f87c9a85baffccf50 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 13 Jul 2021 14:49:57 +0200 Subject: [PATCH] Rewrite parent key with alias for easier editing/merging of groups --- .../Migrations/Install/DatabaseDataCreator.cs | 14 +-- .../V_8_16_0/AddPropertyTypeGroupColumns.cs | 19 ++-- src/Umbraco.Core/Models/ContentTypeBase.cs | 20 ++-- .../Models/ContentTypeCompositionBase.cs | 43 +++------ src/Umbraco.Core/Models/IContentTypeBase.cs | 36 ++++--- src/Umbraco.Core/Models/PropertyGroup.cs | 91 +++++++++++------- .../Models/PropertyGroupCollection.cs | 93 ++++++++++++++++--- .../Packaging/PackageDataInstallation.cs | 24 +++-- .../Persistence/Dtos/PropertyTypeGroupDto.cs | 14 ++- .../Factories/PropertyGroupFactory.cs | 4 +- .../Mappers/PropertyGroupMapper.cs | 2 +- .../Implement/ContentTypeCommonRepository.cs | 2 +- .../Services/Implement/EntityXmlSerializer.cs | 6 +- .../Editors/DashboardController.cs | 1 - .../ContentEditing/PropertyGroupBasic.cs | 43 ++++++++- .../ContentEditing/PropertyGroupDisplay.cs | 42 +-------- src/Umbraco.Web/Models/ContentEditing/Tab.cs | 4 +- .../Mapping/ContentPropertyMapDefinition.cs | 4 +- .../Mapping/ContentTypeMapDefinition.cs | 12 +-- .../Models/Mapping/PropertyTypeGroupMapper.cs | 73 ++++++++------- .../Models/Mapping/TabsAndPropertiesMapper.cs | 20 ++-- 21 files changed, 323 insertions(+), 244 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index d1957f501c..aa719be256 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -223,14 +223,14 @@ namespace Umbraco.Core.Migrations.Install private void CreatePropertyTypeGroupData() { - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 52, ContentTypeNodeId = 1034, Text = "Video", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Video) }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 53, ContentTypeNodeId = 1035, Text = "Audio", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Audio) }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 54, ContentTypeNodeId = 1036, Text = "Article", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Article) }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 55, ContentTypeNodeId = 1037, Text = "Vector Graphics", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.VectorGraphics) }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, UniqueId = new Guid(Constants.PropertyTypeGroups.Image), ContentTypeNodeId = 1032, Text = "Image", Alias = "Image", SortOrder = 1 }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, UniqueId = new Guid(Constants.PropertyTypeGroups.File), ContentTypeNodeId = 1033, Text = "File", Alias = "File", SortOrder = 1, }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 52, UniqueId = new Guid(Constants.PropertyTypeGroups.Video), ContentTypeNodeId = 1034, Text = "Video", Alias = "Video", SortOrder = 1 }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 53, UniqueId = new Guid(Constants.PropertyTypeGroups.Audio), ContentTypeNodeId = 1035, Text = "Audio", Alias = "Audio", SortOrder = 1 }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 54, UniqueId = new Guid(Constants.PropertyTypeGroups.Article), ContentTypeNodeId = 1036, Text = "Article", Alias = "Article", SortOrder = 1 }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 55, UniqueId = new Guid(Constants.PropertyTypeGroups.VectorGraphics), ContentTypeNodeId = 1037, Text = "Vector Graphics", Alias = "VectorGraphics", SortOrder = 1 }); //membership property group - _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership), ContentTypeNodeId = 1044, Text = "Membership", Alias = "Membership", SortOrder = 1 }); } private void CreatePropertyTypeData() diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_16_0/AddPropertyTypeGroupColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_16_0/AddPropertyTypeGroupColumns.cs index e1141bab3f..12d4e2c898 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_16_0/AddPropertyTypeGroupColumns.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_16_0/AddPropertyTypeGroupColumns.cs @@ -13,19 +13,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_16_0 { var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); - // Add new columns - AddColumnIfNotExists(columns, "parentKey"); - AddColumnIfNotExists(columns, "type"); + AddColumn(columns, "type"); - // Create self-referencing foreign key - var foreignKeyName = "FK_" + PropertyTypeGroupDto.TableName + "_parentKey"; - if (SqlSyntax.GetConstraintsPerTable(Context.Database).Any(x => x.Item2.InvariantEquals(foreignKeyName)) == false) + AddColumn(columns, "alias", out var sqls); + var dtos = Database.Fetch(); + foreach (var dto in dtos) { - Create.ForeignKey(foreignKeyName) - .FromTable(PropertyTypeGroupDto.TableName).ForeignColumn("parentKey") - .ToTable(PropertyTypeGroupDto.TableName).PrimaryColumn("uniqueID") - .Do(); + // Generate alias from current name + dto.Alias = dto.Text.ToSafeAlias(); + + Database.Update(dto, x => new { x.Alias }); } + foreach (var sql in sqls) Database.Execute(sql); } } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 4ad3396d7a..e2dcfce756 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -298,23 +298,25 @@ namespace Umbraco.Core.Models public abstract bool PropertyTypeExists(string propertyTypeAlias); /// - /// Adds a PropertyGroup. - /// This method will also check if a group already exists with the same name and link it to the parent. + /// Adds a property group with the alias based on the name. + /// This method will also check if a group already exists with the same alias. /// /// Name of the PropertyGroup to add - /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + /// + /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + /// public abstract bool AddPropertyGroup(string groupName); /// /// Adds a PropertyGroup. /// This method will also check if a group already exists with the same name and link it to the parent. /// - /// The key. /// Name of the PropertyGroup to add + /// The alias. /// /// Returns True if a PropertyGroup with the passed in name was added, otherwise False /// - public abstract bool AddPropertyGroup(Guid key, string groupName); + public abstract bool AddPropertyGroup(string groupName, string alias); /// /// Adds a PropertyType to a specific PropertyGroup @@ -351,13 +353,14 @@ namespace Umbraco.Core.Models public bool MovePropertyType(string propertyTypeAlias, string propertyGroupName) { // get property, ensure it exists - var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); if (propertyType == null) return false; // get new group, if required, and ensure it exists var newPropertyGroup = propertyGroupName == null ? null - : PropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals(propertyGroupName)); + : PropertyGroups.FirstOrDefault(x => x.Alias == propertyGroupName) + ?? PropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals(propertyGroupName)); // TODO Clean up for v9 (only added for backwards compatibility with names) if (propertyGroupName != null && newPropertyGroup == null) return false; // get old group @@ -412,7 +415,8 @@ namespace Umbraco.Core.Models public void RemovePropertyGroup(string propertyGroupName) { // if no group exists with that name, do nothing - var group = PropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals(propertyGroupName)); + var group = PropertyGroups.FirstOrDefault(x => x.Alias == propertyGroupName) + ?? PropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals(propertyGroupName)); // TODO Clean up for v9 (only added for backwards compatibility with names) if (group == null) return; // first remove the group diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 3b9aac1781..aa48e149db 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -205,53 +205,35 @@ namespace Umbraco.Core.Models return CompositionPropertyTypes.Any(x => x.Alias == propertyTypeAlias); } - /// - /// Adds a PropertyGroup. - /// - /// Name of the PropertyGroup to add - /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + /// public override bool AddPropertyGroup(string groupName) { - return AddAndReturnPropertyGroup(null, groupName) != null; + return AddAndReturnPropertyGroup(groupName, groupName.ToSafeAlias()) != null; } - public override bool AddPropertyGroup(Guid key, string groupName) + /// + public override bool AddPropertyGroup(string groupName, string alias) { - return AddAndReturnPropertyGroup(key, groupName) != null; + return AddAndReturnPropertyGroup(groupName, alias) != null; } - private PropertyGroup AddAndReturnPropertyGroup(Guid? key, string name) + private PropertyGroup AddAndReturnPropertyGroup(string name, string alias) { // Ensure we don't have it already - if (PropertyGroups.Contains(name)) + if (PropertyGroups.Contains(alias)) return null; - // Only update name if key already exists - if (key.HasValue) - { - var existingGroup = PropertyGroups.FirstOrDefault(x => x.Key == key.Value); - if (existingGroup != null) - { - existingGroup.Name = name; - return null; - } - } - // Add new group var group = new PropertyGroup(SupportsPublishing) { - Name = name + Name = name, + Alias = alias }; - if (key.HasValue) - { - group.Key = key.Value; - } - // check if it is inherited - there might be more than 1 but we want the 1st, to // reuse its sort order - if there are more than 1 and they have different sort // orders... there isn't much we can do anyways - var inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals(name)); + var inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Alias == alias); if (inheritGroup == null) { // no, just local, set sort order @@ -284,8 +266,9 @@ namespace Umbraco.Core.Models return false; // get and ensure a group local to this content type - var group = PropertyGroups.FirstOrDefault(x => x.Name.InvariantEquals(propertyGroupName)) - ?? AddAndReturnPropertyGroup(null, propertyGroupName); + var group = PropertyGroups.FirstOrDefault(x => x.Alias == propertyGroupName) + ?? PropertyGroups.FirstOrDefault(x => x.Alias == propertyGroupName.ToSafeAlias()) // TODO Remove in v9 (only needed for backwards compatibility with names) + ?? AddAndReturnPropertyGroup(propertyGroupName, propertyGroupName.ToSafeAlias()); // TODO Do we need both name and alias for this to work? if (group == null) return false; diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index b1b238f717..4570202bc7 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -117,10 +117,10 @@ namespace Umbraco.Core.Models void RemovePropertyType(string propertyTypeAlias); /// - /// Removes a PropertyGroup from the current ContentType + /// Removes a property group from the current content type. /// - /// Name of the to remove - void RemovePropertyGroup(string propertyGroupName); + /// Name of the to remove + void RemovePropertyGroup(string propertyGroupName); // TODO Rename to propertyGroupAlias /// /// Checks whether a PropertyType with a given alias already exists @@ -135,7 +135,7 @@ namespace Umbraco.Core.Models /// to add /// Name of the PropertyGroup to add the PropertyType to /// Returns True if PropertyType was added, otherwise False - bool AddPropertyType(PropertyType propertyType, string propertyGroupName); + bool AddPropertyType(PropertyType propertyType, string propertyGroupName); // TODO Rename to propertyGroupAlias /// /// Adds a PropertyType, which does not belong to a PropertyGroup. @@ -145,23 +145,29 @@ namespace Umbraco.Core.Models bool AddPropertyType(PropertyType propertyType); /// - /// Adds a PropertyGroup. - /// This method will also check if a group already exists with the same name and link it to the parent. + /// Adds a property group with the alias based on the specified . /// - /// Name of the PropertyGroup to add - /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + /// Name of the group. + /// + /// Returns true if a property group with specified was added; otherwise, false. + /// + /// + /// This method will also check if a group already exists with the same alias. + /// bool AddPropertyGroup(string groupName); /// - /// Adds a PropertyGroup. - /// This method will also check if a group already exists with the same name and link it to the parent. + /// Adds a property group with the specified and . /// - /// The key. - /// Name of the PropertyGroup to add + /// Name of the group. + /// The alias. /// - /// Returns True if a PropertyGroup with the passed in name was added, otherwise False + /// Returns true if a property group with specified was added; otherwise, false. /// - bool AddPropertyGroup(Guid key, string groupName); + /// + /// This method will also check if a group already exists with the same alias. + /// + bool AddPropertyGroup(string groupName, string alias); /// /// Moves a PropertyType to a specified PropertyGroup @@ -169,7 +175,7 @@ namespace Umbraco.Core.Models /// Alias of the PropertyType to move /// Name of the PropertyGroup to move the PropertyType to /// - bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); + bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); // TODO Rename to propertyGroupAlias /// /// Gets an corresponding to this content type. diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 08b1b75ba4..59d9d4dd11 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -13,12 +13,12 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}")] + [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public class PropertyGroup : EntityBase, IEquatable { - private Guid? _parentKey; private PropertyGroupType _type; private string _name; + private string _alias; private int _sortOrder; private PropertyTypeCollection _propertyTypes; @@ -36,19 +36,6 @@ namespace Umbraco.Core.Models OnPropertyChanged(nameof(PropertyTypes)); } - /// - /// Gets or sets the parent key of the group. - /// - /// - /// The parent key. - /// - [DataMember] - public Guid? ParentKey - { - get => _parentKey; - set => SetPropertyValueAndDetectChanges(value, ref _parentKey, nameof(ParentKey)); - } - /// /// Gets or sets the type of the group. /// @@ -75,6 +62,19 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } + /// + /// Gets or sets the alias of the group. + /// + /// + /// The alias. + /// + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(value, ref _alias, nameof(Alias)); + } + /// /// Gets or sets the sort order of the group. /// @@ -121,9 +121,9 @@ namespace Umbraco.Core.Models } } - public bool Equals(PropertyGroup other) => base.Equals(other) || (other != null && Name.InvariantEquals(other.Name) && ParentKey == other.ParentKey && Type == other.Type); + public bool Equals(PropertyGroup other) => base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias); - public override int GetHashCode() => (base.GetHashCode(), Name?.ToLowerInvariant(), ParentKey, Type).GetHashCode(); + public override int GetHashCode() => (base.GetHashCode(), Type, Alias).GetHashCode(); protected override void PerformDeepClone(object clone) { @@ -133,17 +133,53 @@ namespace Umbraco.Core.Models if (clonedEntity._propertyTypes != null) { - clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any + clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any clonedEntity._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone - clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler + clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler } } } internal static class PropertyGroupExtensions { + private const char aliasSeparator = '/'; + + internal static string GetParentAlias(string alias) + { + var lastIndex = alias?.LastIndexOf(aliasSeparator) ?? -1; + if (lastIndex == -1) + { + return null; + } + + return alias.Substring(0, lastIndex); + } + + public static string GetParentAlias(this PropertyGroup propertyGroup) => GetParentAlias(propertyGroup.Alias); + + public static void UpdateParentAlias(this PropertyGroup propertyGroup, string parentAlias) + { + // Get current alias without parent + var alias = propertyGroup.Alias; + var lastIndex = alias?.LastIndexOf(aliasSeparator) ?? -1; + if (lastIndex != -1) + { + alias = alias.Substring(lastIndex + 1); + } + + // Update alias + if (string.IsNullOrEmpty(parentAlias)) + { + propertyGroup.Alias = alias; + } + else + { + propertyGroup.Alias = parentAlias + aliasSeparator + alias; + } + } + /// - /// Orders the property groups by hierarchy (so child groups are after their parent group) and removes circular references. + /// Orders the property groups by hierarchy (so child groups are after their parent group). /// /// The property groups. /// @@ -151,22 +187,15 @@ namespace Umbraco.Core.Models /// public static IEnumerable OrderByHierarchy(this IEnumerable propertyGroups) { - var groups = propertyGroups.ToList(); - var visitedParentKeys = new HashSet(groups.Count); + var groupsByParentAlias = propertyGroups.ToLookup(x => x.GetParentAlias()); - IEnumerable OrderByHierarchy(Guid? parentKey) + IEnumerable OrderByHierarchy(string parentAlias) { - if (parentKey.HasValue && visitedParentKeys.Add(parentKey.Value) == false) - { - // We already visited this parent key, stop to prevent a circular reference - yield break; - } - - foreach (var group in groups.Where(x => x.ParentKey == parentKey).OrderBy(x => x.Type).ThenBy(x => x.SortOrder)) + foreach (var group in groupsByParentAlias[parentAlias].OrderBy(x => x.SortOrder)) { yield return group; - foreach (var childGroup in OrderByHierarchy(group.Key)) + foreach (var childGroup in OrderByHierarchy(group.Alias)) { yield return childGroup; } diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index a30cc27c2a..d5f9b47fd5 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; using System.Threading; @@ -20,17 +21,16 @@ namespace Umbraco.Core.Models private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// - /// Property group names are case-insensitive and the internal lookup dictionary is disabled to support renaming groups. - /// internal PropertyGroupCollection() - : base(StringComparer.InvariantCultureIgnoreCase, -1) { } + /// + /// Initializes a new instance of the class. + /// + /// The groups. public PropertyGroupCollection(IEnumerable groups) - : this() { Reset(groups); } @@ -50,9 +50,28 @@ namespace Umbraco.Core.Models Add(group); } + public new PropertyGroup this[string key] + { + // TODO Remove this property in v9 (only needed for backwards compatibility with names) + get + { + var item = base[key]; + if (item == null && !key.Contains('/')) + { + item = base[key.ToSafeAlias()]; + } + + return item; + } + } + protected override void SetItem(int index, PropertyGroup item) { var oldItem = index >= 0 ? this[index] : item; + + oldItem.PropertyChanged -= PropertyGroup_PropertyChanged; + item.PropertyChanged += PropertyGroup_PropertyChanged; + base.SetItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); } @@ -60,18 +79,28 @@ namespace Umbraco.Core.Models protected override void RemoveItem(int index) { var removed = this[index]; + + removed.PropertyChanged -= PropertyGroup_PropertyChanged; + base.RemoveItem(index); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } protected override void InsertItem(int index, PropertyGroup item) { + item.PropertyChanged += PropertyGroup_PropertyChanged; + base.InsertItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } protected override void ClearItems() { + foreach (var item in this) + { + item.PropertyChanged -= PropertyGroup_PropertyChanged; + } + base.ClearItems(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } @@ -82,15 +111,21 @@ namespace Umbraco.Core.Models { _addLocker.EnterWriteLock(); + // Ensure alias is set + if (string.IsNullOrEmpty(item.Alias)) + { + item.Alias = item.Name.ToSafeAlias(); + } + // Note this is done to ensure existing groups can be renamed if (item.HasIdentity && item.Id > 0) { var index = IndexOfKey(item.Id); if (index != -1) { - var keyExists = Contains(item.Name); + var keyExists = Contains(item.Alias); if (keyExists) - throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates"); + throw new ArgumentException($"Naming conflict: changing the alias of property group '{item.Name}' would result in duplicates."); // Collection events will be raised in SetItem SetItem(index, item); @@ -99,7 +134,7 @@ namespace Umbraco.Core.Models } else { - var index = IndexOfKey(item.Name); + var index = IndexOfKey(item.Alias); if (index != -1) { // Collection events will be raised in SetItem @@ -118,25 +153,57 @@ namespace Umbraco.Core.Models } } + private void PropertyGroup_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + var item = sender as PropertyGroup; + + if (e.PropertyName == nameof(PropertyGroup.Alias)) + { + ChangeItemKey(item, item.Alias); + + // Update child aliases + foreach (var childItem in this.Where(x => x.GetParentAlias() == item.Alias)) + { + childItem.UpdateParentAlias(item.Alias); + } + } + } + + public new bool Contains(string key) + { + // TODO Remove this method in v9 (only needed for backwards compatibility with names) + return base.Contains(key) || (!key.Contains('/') && base.Contains(key.ToSafeAlias())); + } + public bool Contains(int id) { return this.Any(x => x.Id == id); } - public void RemoveItem(string propertyGroupName) + public void RemoveItem(string key) { - var index = IndexOfKey(propertyGroupName); + var index = IndexOfKey(key); // Only removes an item if the key was found if (index != -1) RemoveItem(index); } - public int IndexOfKey(string key) => this.FindIndex(x => x.Name.InvariantEquals(key)); + public int IndexOfKey(string key) + { + var index = this.FindIndex(x => x.Alias == key); + if (index == -1 && !key.Contains('/')) + { + // TODO Clean up for v9 (only needed for backwards compatibility with names) + index = this.IndexOfKey(key.ToSafeAlias()); + } + + return index; + } public int IndexOfKey(int id) => this.FindIndex(x => x.Id == id); - protected override string GetKeyForItem(PropertyGroup item) => item.Name; + protected override string GetKeyForItem(PropertyGroup item) => item.Alias; public event NotifyCollectionChangedEventHandler CollectionChanged; diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs index f1fd48e237..c51671e22d 100644 --- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -742,29 +742,27 @@ namespace Umbraco.Core.Packaging var propertyGroupElements = propertyGroupsContainer.Elements("Tab"); foreach (var propertyGroupElement in propertyGroupElements) { - var caption = propertyGroupElement.Element("Caption").Value; + var name = propertyGroupElement.Element("Caption").Value; // TODO Rename to Name (same in EntityXmlSerializer) + + var alias = propertyGroupElement.Element("Alias")?.Value; + if (string.IsNullOrEmpty(alias)) + { + alias = name.ToSafeAlias(); + } + + contentType.AddPropertyGroup(name, alias); + var propertyGroup = contentType.PropertyGroups[alias]; if (Guid.TryParse(propertyGroupElement.Element("Key")?.Value, out var key)) { - contentType.AddPropertyGroup(key, caption); + propertyGroup.Key = key; } - else - { - contentType.AddPropertyGroup(caption); - } - - var propertyGroup = contentType.PropertyGroups[caption]; if (Enum.TryParse(propertyGroupElement.Element("Type")?.Value, out var type)) { propertyGroup.Type = type; } - if (Guid.TryParse(propertyGroupElement.Element("ParentKey")?.Value, out var parentKey)) - { - propertyGroup.ParentKey = parentKey; - } - if (int.TryParse(propertyGroupElement.Element("SortOrder")?.Value, out var sortOrder)) { // Override the sort order with the imported value diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupDto.cs index 2f5f98000c..a043e2ca40 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupDto.cs @@ -23,22 +23,20 @@ namespace Umbraco.Core.Persistence.Dtos [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeGroupUniqueID")] public Guid UniqueId { get; set; } - [Column("parentKey")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(PropertyTypeGroupDto), Column = "uniqueID", Name = "FK_" + TableName + "_parentKey")] - public Guid? ParentKey { get; set; } + [Column("contenttypeNodeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] + public int ContentTypeNodeId { get; set; } [Column("type")] [Constraint(Default = 0)] public short Type { get; set; } - [Column("contenttypeNodeId")] - [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] - public int ContentTypeNodeId { get; set; } - [Column("text")] public string Text { get; set; } + [Column("alias")] + public string Alias { get; set; } + [Column("sortorder")] public int SortOrder { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index 0095ead7d0..d3e18dce08 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -35,9 +35,9 @@ namespace Umbraco.Core.Persistence.Factories group.Id = groupDto.Id; group.Key = groupDto.UniqueId; - group.ParentKey = groupDto.ParentKey; group.Type = (PropertyGroupType)groupDto.Type; group.Name = groupDto.Text; + group.Alias = groupDto.Alias; group.SortOrder = groupDto.SortOrder; group.PropertyTypes = new PropertyTypeCollection(isPublishing); @@ -107,10 +107,10 @@ namespace Umbraco.Core.Persistence.Factories var dto = new PropertyTypeGroupDto { UniqueId = propertyGroup.Key, - ParentKey = propertyGroup.ParentKey, Type = (short)propertyGroup.Type, ContentTypeNodeId = contentTypeId, Text = propertyGroup.Name, + Alias = propertyGroup.Alias, SortOrder = propertyGroup.SortOrder }; diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs index 936bc9fbca..3d3cd9fbb6 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs @@ -20,9 +20,9 @@ namespace Umbraco.Core.Persistence.Mappers { DefineMap(nameof(PropertyGroup.Id), nameof(PropertyTypeGroupDto.Id)); DefineMap(nameof(PropertyGroup.Key), nameof(PropertyTypeGroupDto.UniqueId)); - DefineMap(nameof(PropertyGroup.ParentKey), nameof(PropertyTypeGroupDto.ParentKey)); DefineMap(nameof(PropertyGroup.Type), nameof(PropertyTypeGroupDto.Type)); DefineMap(nameof(PropertyGroup.Name), nameof(PropertyTypeGroupDto.Text)); + DefineMap(nameof(PropertyGroup.Alias), nameof(PropertyTypeGroupDto.Alias)); DefineMap(nameof(PropertyGroup.SortOrder), nameof(PropertyTypeGroupDto.SortOrder)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index e9ff44f45f..af3b12f050 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -273,9 +273,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Id = dto.Id, Key = dto.UniqueId, - ParentKey = dto.ParentKey, Type = (PropertyGroupType)dto.Type, Name = dto.Text, + Alias = dto.Alias, SortOrder = dto.SortOrder }; } diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index db955ab4fe..cda7af4d3f 100644 --- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -487,7 +487,7 @@ namespace Umbraco.Core.Services.Implement new XElement("Key", propertyType.Key), new XElement("Type", propertyType.PropertyEditorAlias), new XElement("Definition", definition.Key), - new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), // TODO Rename to PropertyGroupKey + new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Alias), // TODO Rename to PropertyGroupAlias new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()), @@ -506,10 +506,10 @@ namespace Umbraco.Core.Services.Implement foreach (var propertyGroup in propertyGroups) { yield return new XElement("Tab", // TODO Rename to PropertyGroup - new XElement("Caption", propertyGroup.Name), // TODO Rename to Name new XElement("Key", propertyGroup.Key), - new XElement("ParentKey", propertyGroup.ParentKey), new XElement("Type", propertyGroup.Type.ToString()), + new XElement("Caption", propertyGroup.Name), // TODO Rename to Name (same in PackageDataInstallation) + new XElement("Alias", propertyGroup.Alias), new XElement("SortOrder", propertyGroup.SortOrder)); } } diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index e0d155a62e..cf56cc4be8 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -216,7 +216,6 @@ namespace Umbraco.Web.Editors { Id = x.Id, Key = x.Key, - ParentKey = x.ParentKey, Type = x.Type, Alias = x.Alias, Label = x.Label, diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs index 150f0631bb..ad2a027c15 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Runtime.Serialization; using Umbraco.Core.Models; @@ -37,9 +38,6 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "key")] public Guid Key { get; set; } - [DataMember(Name = "parentKey")] - public Guid? ParentKey { get; set; } - [DataMember(Name = "type")] public PropertyGroupType Type { get; set; } @@ -47,6 +45,10 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "name")] public string Name { get; set; } + [Required] + [DataMember(Name = "alias")] + public string Alias { get; set; } + [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } } @@ -63,4 +65,39 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } } + + internal static class PropertyGroupBasicExtensions + { + public static string GetParentAlias(this PropertyGroupBasic propertyGroup) + => PropertyGroupExtensions.GetParentAlias(propertyGroup.Alias); + + /// + /// Orders the property groups by hierarchy (so child groups are after their parent group). + /// + /// The display type of the property type. + /// The property groups. + /// + /// The ordered property groups. + /// + public static IEnumerable OrderByHierarchy(this IEnumerable propertyGroups) + where T : PropertyGroupBasic + { + var groupsByParentAlias = propertyGroups.ToLookup(x => x.GetParentAlias()); + + IEnumerable OrderByHierarchy(string parentAlias) + { + foreach (var group in groupsByParentAlias[parentAlias].OrderBy(x => x.SortOrder)) + { + yield return group; + + foreach (var childGroup in OrderByHierarchy(group.Alias)) + { + yield return childGroup; + } + } + } + + return OrderByHierarchy(null); + } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs index 7b61c1ce18..1523daacda 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -38,42 +36,4 @@ namespace Umbraco.Web.Models.ContentEditing [ReadOnly(true)] public IEnumerable ParentTabContentTypeNames { get; set; } } - - internal static class PropertyGroupDisplayExtensions - { - /// - /// Orders the property groups by hierarchy (so child groups are after their parent group) and removes circular references. - /// - /// The property groups. - /// - /// The ordered property groups. - /// - public static IEnumerable> OrderByHierarchy(this IEnumerable> propertyGroups) - where T : PropertyTypeDisplay - { - var groups = propertyGroups.ToList(); - var visitedParentKeys = new HashSet(groups.Count); - - IEnumerable> OrderByHierarchy(Guid? parentKey) - { - if (parentKey.HasValue && visitedParentKeys.Add(parentKey.Value) == false) - { - // We already visited this parent key, stop to prevent a circular reference - yield break; - } - - foreach (var group in groups.Where(x => x.ParentKey == parentKey).OrderBy(x => x.Type).ThenBy(x => x.SortOrder)) - { - yield return group; - - foreach (var childGroup in OrderByHierarchy(group.Key)) - { - yield return childGroup; - } - } - } - - return OrderByHierarchy(null); - } - } } diff --git a/src/Umbraco.Web/Models/ContentEditing/Tab.cs b/src/Umbraco.Web/Models/ContentEditing/Tab.cs index 6d952d4e10..a4ec974cc6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Tab.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Tab.cs @@ -17,9 +17,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "key")] public Guid Key { get; set; } - [DataMember(Name = "parentKey")] - public Guid? ParentKey { get; set; } - + [Obsolete("A generic tab should not reference property group specific types!")] [DataMember(Name = "type")] public PropertyGroupType Type { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs index 5f61608a19..75a394584b 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs @@ -37,10 +37,10 @@ namespace Umbraco.Web.Models.Mapping { target.Id = source.Id; target.Key = source.Key; - target.ParentKey = source.ParentKey; target.Type = source.Type; - target.IsActive = true; target.Label = source.Name; + target.Alias = source.Alias; + target.IsActive = true; } private void Map(Property source, ContentPropertyBasic target, MapperContext context) diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 944c3ddbf0..191201d348 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -302,9 +302,9 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.Key = source.Key; - target.ParentKey = source.ParentKey; target.Type = source.Type; target.Name = source.Name; + target.Alias = source.Alias; target.SortOrder = source.SortOrder; } @@ -315,9 +315,9 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.Key = source.Key; - target.ParentKey = source.ParentKey; target.Type = source.Type; target.Name = source.Name; + target.Alias = source.Alias; target.SortOrder = source.SortOrder; } @@ -328,9 +328,9 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.Key = source.Key; - target.ParentKey = source.ParentKey; target.Type = source.Type; target.Name = source.Name; + target.Alias = source.Alias; target.SortOrder = source.SortOrder; target.Inherited = source.Inherited; @@ -344,9 +344,9 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.Key = source.Key; - target.ParentKey = source.ParentKey; target.Type = source.Type; target.Name = source.Name; + target.Alias = source.Alias; target.SortOrder = source.SortOrder; target.Inherited = source.Inherited; @@ -447,7 +447,7 @@ namespace Umbraco.Web.Models.Mapping var destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not var destGroups = new List(); var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); - var parentKeys = sourceGroups.Where(x => x.ParentKey.HasValue).Select(x => x.ParentKey.Value).Distinct().ToArray(); + var sourceGroupParentAliases = sourceGroups.Select(x => x.GetParentAlias()).Distinct().ToArray(); foreach (var sourceGroup in sourceGroups) { // get the dest group @@ -461,7 +461,7 @@ namespace Umbraco.Web.Models.Mapping // if the group has no local properties and is not used as parent, skip it, ie sort-of garbage-collect // local groups which would not have local properties anymore - if (destProperties.Length == 0 && !parentKeys.Contains(sourceGroup.Key)) + if (destProperties.Length == 0 && !sourceGroupParentAliases.Contains(sourceGroup.Alias)) continue; // ensure no duplicate alias, then assign the group properties collection diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs index f1a5d84d37..c4b46d6f63 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs @@ -70,51 +70,50 @@ namespace Umbraco.Web.Models.Mapping var groups = new List>(); // add groups local to this content type - foreach (var tab in source.PropertyGroups) + foreach (var propertyGroup in source.PropertyGroups) { var group = new PropertyGroupDisplay { - Id = tab.Id, - Key = tab.Key, - ParentKey = tab.ParentKey, - Type = tab.Type, - Name = tab.Name, - SortOrder = tab.SortOrder, - Inherited = false, + Id = propertyGroup.Id, + Key = propertyGroup.Key, + Type = propertyGroup.Type, + Name = propertyGroup.Name, + Alias = propertyGroup.Alias, + SortOrder = propertyGroup.SortOrder, ContentTypeId = source.Id }; - group.Properties = MapProperties(tab.PropertyTypes, source, tab.Id, false); + group.Properties = MapProperties(propertyGroup.PropertyTypes, source, propertyGroup.Id, false); groups.Add(group); } // add groups inherited through composition var localGroupIds = groups.Select(x => x.Id).ToArray(); - foreach (var tab in source.CompositionPropertyGroups) + foreach (var propertyGroup in source.CompositionPropertyGroups) { // skip those that are local to this content type - if (localGroupIds.Contains(tab.Id)) continue; + if (localGroupIds.Contains(propertyGroup.Id)) continue; // get the content type that defines this group - var definingContentType = GetContentTypeForPropertyGroup(source, tab.Id); + var definingContentType = GetContentTypeForPropertyGroup(source, propertyGroup.Id); if (definingContentType == null) - throw new Exception("PropertyGroup with id=" + tab.Id + " was not found on any of the content type's compositions."); + throw new Exception("PropertyGroup with id=" + propertyGroup.Id + " was not found on any of the content type's compositions."); var group = new PropertyGroupDisplay { - Id = tab.Id, - Key = tab.Key, - ParentKey = tab.ParentKey, - Type = tab.Type, - Name = tab.Name, - SortOrder = tab.SortOrder, + Id = propertyGroup.Id, + Key = propertyGroup.Key, + Type = propertyGroup.Type, + Name = propertyGroup.Name, + Alias = propertyGroup.Alias, + SortOrder = propertyGroup.SortOrder, Inherited = true, ContentTypeId = definingContentType.Id, ParentTabContentTypes = new[] { definingContentType.Id }, ParentTabContentTypeNames = new[] { definingContentType.Name } }; - group.Properties = MapProperties(tab.PropertyTypes, definingContentType, tab.Id, true); + group.Properties = MapProperties(propertyGroup.PropertyTypes, definingContentType, propertyGroup.Id, true); groups.Add(group); } @@ -141,16 +140,20 @@ namespace Umbraco.Web.Models.Mapping // if there are any generic properties, add the corresponding tab if (genericProperties.Any()) { - var genericTab = new PropertyGroupDisplay + string name = "Generic properties", + alias = name.ToSafeAlias(); + + var genericGroup = new PropertyGroupDisplay { Id = PropertyGroupBasic.GenericPropertiesGroupId, - Name = "Generic properties", - ContentTypeId = source.Id, + Name = name, + Alias = alias, SortOrder = 999, - Inherited = false, + ContentTypeId = source.Id, Properties = genericProperties }; - groups.Add(genericTab); + + groups.Add(genericGroup); } // handle locked properties @@ -166,33 +169,33 @@ namespace Umbraco.Web.Models.Mapping property.Locked = lockedPropertyAliases.Contains(property.Alias); } - // now merge tabs based on names + // now merge tabs based on alias // as for one name, we might have one local tab, plus some inherited tabs - var groupsGroupsByHierarchy = groups.GroupBy(x => (x.Name, x.ParentKey, x.Type)).ToArray(); + var groupsGroupsByAlias = groups.GroupBy(x => x.Alias).ToArray(); groups = new List>(); // start with a fresh list - foreach (var groupsByHierarchy in groupsGroupsByHierarchy) + foreach (var groupsByAlias in groupsGroupsByAlias) { // single group, just use it - if (groupsByHierarchy.Count() == 1) + if (groupsByAlias.Count() == 1) { - groups.Add(groupsByHierarchy.First()); + groups.Add(groupsByAlias.First()); continue; } // multiple groups, merge - var group = groupsByHierarchy.FirstOrDefault(x => x.Inherited == false) // try local - ?? groupsByHierarchy.First(); // else pick one randomly + var group = groupsByAlias.FirstOrDefault(x => x.Inherited == false) // try local + ?? groupsByAlias.First(); // else pick one randomly groups.Add(group); // in case we use the local one, flag as inherited - group.Inherited = true; + group.Inherited = true; // TODO Remove to allow changing sort order of the local one (and use the inherited group order below) // merge (and sort) properties - var properties = groupsByHierarchy.SelectMany(x => x.Properties).OrderBy(x => x.SortOrder).ToArray(); + var properties = groupsByAlias.SelectMany(x => x.Properties).OrderBy(x => x.SortOrder).ToArray(); group.Properties = properties; // collect parent group info - var parentGroups = groupsByHierarchy.Where(x => x.ContentTypeId != source.Id).ToArray(); + var parentGroups = groupsByAlias.Where(x => x.ContentTypeId != source.Id).ToArray(); group.ParentTabContentTypes = parentGroups.SelectMany(x => x.ParentTabContentTypes).ToArray(); group.ParentTabContentTypeNames = parentGroups.SelectMany(x => x.ParentTabContentTypeNames).ToArray(); } diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs index 450a09c6cd..b2bdb272e6 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs @@ -130,13 +130,13 @@ namespace Umbraco.Web.Models.Mapping // and there might be duplicates (content does not work like contentType and there is no // content.CompositionPropertyGroups). var groups = contentType.CompositionPropertyGroups.OrderByHierarchy().ToArray(); - var parentKeys = groups.Where(x => x.ParentKey.HasValue).Select(x => x.ParentKey.Value).Distinct().ToArray(); - foreach (var groupsByHierarchy in groups.GroupBy(x => (x.Name, x.ParentKey, x.Type))) + var parentAliases = groups.Select(x => x.GetParentAlias()).Distinct().ToArray(); + foreach (var groupsByAlias in groups.GroupBy(x => x.Alias)) { var properties = new List(); - // merge properties for groups with the same name - foreach (var group in groupsByHierarchy) + // merge properties for groups with the same alias + foreach (var group in groupsByAlias) { var groupProperties = source.GetPropertiesForGroup(group) .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored @@ -144,7 +144,7 @@ namespace Umbraco.Web.Models.Mapping properties.AddRange(groupProperties); } - if (properties.Count == 0 && groupsByHierarchy.All(x => !parentKeys.Contains(x.Key))) + if (properties.Count == 0 && !parentAliases.Contains(groupsByAlias.Key)) continue; //map the properties @@ -152,19 +152,17 @@ namespace Umbraco.Web.Models.Mapping // add the tab // we need to pick an identifier... there is no "right" way... - var g = groupsByHierarchy.FirstOrDefault(x => x.Id == source.ContentTypeId) // try local - ?? groupsByHierarchy.First(); // else pick one randomly + var g = groupsByAlias.FirstOrDefault(x => x.Id == source.ContentTypeId) // try local + ?? groupsByAlias.First(); // else pick one randomly tabs.Add(new Tab { Id = g.Id, Key = g.Key, - ParentKey = g.ParentKey, Type = g.Type, - Alias = g.Name, Label = LocalizedTextService.UmbracoDictionaryTranslate(g.Name), - Properties = mappedProperties, - IsActive = false + Alias = g.Alias, + Properties = mappedProperties }); }