private static PropertyGroup MapSaveGroup <TPropertyType>(PropertyGroupBasic <TPropertyType> sourceGroup, IEnumerable <PropertyGroup> destOrigGroups, MapperContext context) where TPropertyType : PropertyTypeBasic { PropertyGroup destGroup; if (sourceGroup.Id > 0) { // update an existing group // ensure it is still there, then map/update destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); if (destGroup != null) { context.Map(sourceGroup, destGroup); return(destGroup); } // force-clear the ID as it does not match anything sourceGroup.Id = 0; } // insert a new group, or update an existing group that has // been deleted in the meantime and we need to re-create // map/create destGroup = context.Map <PropertyGroup>(sourceGroup); return(destGroup); }
/// <summary> /// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors /// </summary> /// <param name="contentTypeSave"></param> /// <param name="duplicatePropertyTypeAliases"></param> /// <param name="invalidPropertyGroupAliases"></param> /// <returns></returns> private void AddCompositionValidationErrors <TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable <string>?duplicatePropertyTypeAliases, IEnumerable <string>?invalidPropertyGroupAliases) where TContentTypeSave : ContentTypeSave <TPropertyType> where TPropertyType : PropertyTypeBasic { if (duplicatePropertyTypeAliases is not null) { foreach (var propertyTypeAlias in duplicatePropertyTypeAliases) { // Find the property type relating to these TPropertyType property = contentTypeSave.Groups.SelectMany(x => x.Properties) .Single(x => x.Alias == propertyTypeAlias); PropertyGroupBasic <TPropertyType> group = contentTypeSave.Groups.Single(x => x.Properties.Contains(property)); var propertyIndex = group.Properties.IndexOf(property); var groupIndex = contentTypeSave.Groups.IndexOf(group); var key = $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias"; ModelState.AddModelError(key, "Duplicate property aliases aren't allowed between compositions"); } } if (invalidPropertyGroupAliases is not null) { foreach (var propertyGroupAlias in invalidPropertyGroupAliases) { // Find the property group relating to these PropertyGroupBasic <TPropertyType> group = contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias); var groupIndex = contentTypeSave.Groups.IndexOf(group); var key = $"Groups[{groupIndex}].Name"; ModelState.AddModelError(key, "Different group types aren't allowed between compositions"); } } }
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes private static void Map(PropertyGroupBasic <MemberPropertyTypeBasic> source, PropertyGroup target, MapperContext context) { if (source.Id > 0) { target.Id = source.Id; } target.Name = source.Name; target.SortOrder = source.SortOrder; }
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes private static void Map(PropertyGroupBasic <PropertyTypeBasic> source, PropertyGroup target, MapperContext context) { if (source.Id > 0) { target.Id = source.Id; } target.Key = source.Key; target.Type = source.Type; target.Name = source.Name; target.Alias = source.Alias; target.SortOrder = source.SortOrder; }
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames private static void Map(PropertyGroupBasic <MemberPropertyTypeBasic> source, PropertyGroupDisplay <MemberPropertyTypeDisplay> target, MapperContext context) { if (source.Id > 0) { target.Id = source.Id; } target.Inherited = source.Inherited; target.Name = source.Name; target.SortOrder = source.SortOrder; target.Properties = context.MapEnumerable <MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(source.Properties); }
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames private static void Map(PropertyGroupBasic <PropertyTypeBasic> source, PropertyGroupDisplay <PropertyTypeDisplay> target, MapperContext context) { target.Inherited = source.Inherited; if (source.Id > 0) { target.Id = source.Id; } target.Key = source.Key; target.Type = source.Type; target.Name = source.Name; target.Alias = source.Alias; target.SortOrder = source.SortOrder; target.Properties = context.MapEnumerable <PropertyTypeBasic, PropertyTypeDisplay>(source.Properties); }
// Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) private static void MapSaveToTypeBase <TSource, TSourcePropertyType>(TSource source, IContentTypeComposition target, MapperContext context) where TSource : ContentTypeSave <TSourcePropertyType> where TSourcePropertyType : PropertyTypeBasic { // TODO: not so clean really var isPublishing = target is IContentType; var id = Convert.ToInt32(source.Id); if (id > 0) { target.Id = id; } target.Alias = source.Alias; target.Description = source.Description; target.Icon = source.Icon; target.IsContainer = source.IsContainer; target.IsElement = source.IsElement; target.Key = source.Key; target.Name = source.Name; target.ParentId = source.ParentId; target.Path = source.Path; target.Thumbnail = source.Thumbnail; target.AllowedAsRoot = source.AllowAsRoot; target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); if (!(target is IMemberType)) { target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); } // handle property groups and property types // note that ContentTypeSave has // - all groups, inherited and local; only *one* occurrence per group *name* // - potentially including the generic properties group // - all properties, inherited and local // // also, see PropertyTypeGroupResolver.ResolveCore: // - if a group is local *and* inherited, then Inherited is true // and the identifier is the identifier of the *local* group // // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some // unique-alias-checking, etc that is *not* compatible with re-mapping everything // the way we do it here, so we should exclusively do it by // - managing a property group's PropertyTypes collection // - managing the content type's PropertyTypes collection (for generic properties) // handle actual groups (non-generic-properties) PropertyGroup[] destOrigGroups = target.PropertyGroups.ToArray(); // local groups IPropertyType[] destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not var destGroups = new List <PropertyGroup>(); PropertyGroupBasic <TSourcePropertyType>[] sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); var sourceGroupParentAliases = sourceGroups.Select(x => x.GetParentAlias()).Distinct().ToArray(); foreach (PropertyGroupBasic <TSourcePropertyType> sourceGroup in sourceGroups) { // get the dest group PropertyGroup destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context); // handle local properties IPropertyType[] destProperties = sourceGroup.Properties .Where(x => x.Inherited == false) .Select(x => MapSaveProperty(x, destOrigProperties, context)) .ToArray(); // 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 && !sourceGroupParentAliases.Contains(sourceGroup.Alias)) { continue; } // ensure no duplicate alias, then assign the group properties collection EnsureUniqueAliases(destProperties); destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); destGroups.Add(destGroup); } // ensure no duplicate name, then assign the groups collection EnsureUniqueAliases(destGroups); target.PropertyGroups = new PropertyGroupCollection(destGroups); // because the property groups collection was rebuilt, there is no need to remove // the old groups - they are just gone and will be cleared by the repository // handle non-grouped (ie generic) properties PropertyGroupBasic <TSourcePropertyType> genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties); if (genericPropertiesGroup != null) { // handle local properties IPropertyType[] destProperties = genericPropertiesGroup.Properties .Where(x => x.Inherited == false) .Select(x => MapSaveProperty(x, destOrigProperties, context)) .ToArray(); // ensure no duplicate alias, then assign the generic properties collection EnsureUniqueAliases(destProperties); target.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); } // because all property collections were rebuilt, there is no need to remove // some old properties, they are just gone and will be cleared by the repository }
public void Can_Perform_Update_On_ContentTypeRepository_After_Model_Mapping() { // Arrange IScopeProvider provider = ScopeProvider; using (IScope scope = provider.CreateScope()) { ContentTypeRepository repository = ContentTypeRepository; // Act IContentType contentType = repository.Get(_textpageContentType.Id); // there is NO mapping from display to contentType, but only from save // to contentType, so if we want to test, let's to it properly! DocumentTypeDisplay display = Mapper.Map <DocumentTypeDisplay>(contentType); DocumentTypeSave save = MapToContentTypeSave(display); // modify... save.Thumbnail = "Doc2.png"; PropertyGroupBasic <PropertyTypeBasic> contentGroup = save.Groups.Single(x => x.Name == "Content"); contentGroup.Properties = contentGroup.Properties.Concat(new[] { new PropertyTypeBasic { Alias = "subtitle", Label = "Subtitle", Description = "Optional Subtitle", Validation = new PropertyTypeValidation { Mandatory = false, Pattern = string.Empty }, SortOrder = 1, DataTypeId = -88, LabelOnTop = true } }); IContentType mapped = Mapper.Map(save, contentType); // just making sure Assert.AreEqual(mapped.Thumbnail, "Doc2.png"); Assert.IsTrue(mapped.PropertyTypes.Any(x => x.Alias == "subtitle")); Assert.IsTrue(mapped.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop); repository.Save(mapped); bool dirty = mapped.IsDirty(); // re-get contentType = repository.Get(_textpageContentType.Id); // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(dirty, Is.False); Assert.That(contentType.Thumbnail, Is.EqualTo("Doc2.png")); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); Assert.That(contentType.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop, Is.True); foreach (IPropertyType propertyType in contentType.PropertyTypes) { Assert.IsTrue(propertyType.HasIdentity); Assert.Greater(propertyType.Id, 0); } } }
public static string?GetParentAlias(this PropertyGroupBasic propertyGroup) => PropertyGroupExtensions.GetParentAlias(propertyGroup.Alias);
public void PropertyGroupBasic_To_PropertyGroup() { var dataType = new DataType(Services.GetRequiredService <LabelPropertyEditor>(), _serializer) { Name = "TODO" }; _dataTypeService.Save(dataType); var basic = new PropertyGroupBasic <PropertyTypeBasic> { Id = 222, Alias = "group1", Name = "Group 1", SortOrder = 1, Properties = new[] { new PropertyTypeBasic { Id = 33, SortOrder = 1, Alias = "prop1", Description = "property 1", DataTypeId = dataType.Id, GroupId = 222, Label = "Prop 1", Validation = new PropertyTypeValidation { Mandatory = true, Pattern = null } }, new PropertyTypeBasic { Id = 34, SortOrder = 2, Alias = "prop2", Description = "property 2", DataTypeId = dataType.Id, GroupId = 222, Label = "Prop 2", Validation = new PropertyTypeValidation { Mandatory = false, Pattern = null } } } }; var contentType = new DocumentTypeSave { Id = 0, ParentId = -1, Alias = "alias", AllowedTemplates = Enumerable.Empty <string>(), Groups = new[] { basic } }; // proper group properties mapping takes place when mapping the content type, // not when mapping the group - because of inherited properties and such // var result = Mapper.Map<PropertyGroup>(basic); var result = _sut.Map <IContentType>(contentType).PropertyGroups[0]; Assert.AreEqual(basic.Name, result.Name); Assert.AreEqual(basic.Id, result.Id); Assert.AreEqual(basic.SortOrder, result.SortOrder); Assert.AreEqual(basic.Properties.Count(), result.PropertyTypes.Count()); }