Skip to content

Commit

Permalink
Merge pull request #11373 from umbraco/v8/bugfix/AB14159-add-more-war…
Browse files Browse the repository at this point in the history
…nings-when-invalid-composition

Add validation errors when invalid composition due to duplicate property group aliases
  • Loading branch information
ronaldbarendse authored Oct 14, 2021
2 parents d02440d + b73ab25 commit a284d52
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 28 deletions.
35 changes: 23 additions & 12 deletions src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Runtime.Serialization;
using System.Text;

namespace Umbraco.Core.Exceptions
{
Expand Down Expand Up @@ -85,18 +86,28 @@ public InvalidCompositionException(string contentTypeAlias, string addedComposit

private static string FormatMessage(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases)
{
// TODO Add property group aliases to message
return addedCompositionAlias.IsNullOrWhiteSpace()
? string.Format(
"ContentType with alias '{0}' has an invalid composition " +
"and there was a conflict on the following PropertyTypes: '{1}'. " +
"PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
contentTypeAlias, string.Join(", ", propertyTypeAliases))
: string.Format(
"ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " +
"but there was a conflict on the following PropertyTypes: '{2}'. " +
"PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
addedCompositionAlias, contentTypeAlias, string.Join(", ", propertyTypeAliases));
var sb = new StringBuilder();

if (addedCompositionAlias.IsNullOrWhiteSpace())
{
sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias);
}
else
{
sb.AppendFormat("Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", addedCompositionAlias, contentTypeAlias);
}

if (propertyTypeAliases.Length > 0)
{
sb.AppendFormat(" Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", string.Join(", ", propertyTypeAliases));
}

if (propertyGroupAliases.Length > 0)
{
sb.AppendFormat(" Property groups with the same alias must also have the same type across all compositions, these aliases have different types: {0}.", string.Join(", ", propertyGroupAliases));
}

return sb.ToString();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,22 @@ protected void ValidateLocked(TItem compositionContentType)
stack.Push(c);
}

var duplicatePropertyTypeAliases = new List<string>();
var invalidPropertyGroupAliases = new List<string>();

foreach (var dependency in dependencies)
{
if (dependency.Id == compositionContentType.Id) continue;
var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase));
if (contentTypeDependency == null) continue;

var duplicatePropertyTypeAliases = contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase).ToArray();
var invalidPropertyGroupAliases = contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias).ToArray();

if (duplicatePropertyTypeAliases.Length == 0 && invalidPropertyGroupAliases.Length == 0) continue;
duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase));
invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias));
}

throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
{
throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
}
}

Expand Down
34 changes: 23 additions & 11 deletions src/Umbraco.Web/Editors/ContentTypeControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,10 +454,12 @@ private HttpResponseException CreateCompositionValidationExceptionIfInvalid<TCon
var validateAttempt = service.ValidateComposition(composition);
if (validateAttempt == false)
{
//if it's not successful then we need to return some model state for the property aliases that
// are duplicated
var invalidPropertyAliases = validateAttempt.Result.Distinct();
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidPropertyAliases);
// if it's not successful then we need to return some model state for the property type and property group
// aliases that are duplicated
var duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
var invalidPropertyGroupAliases = (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ?? Array.Empty<string>();

AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);

var display = Mapper.Map<TContentTypeDisplay>(composition);
//map the 'save' data on top
Expand All @@ -472,22 +474,32 @@ private HttpResponseException CreateCompositionValidationExceptionIfInvalid<TCon
/// 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="invalidPropertyAliases"></param>
/// <param name="duplicatePropertyTypeAliases"></param>
/// <param name="invalidPropertyGroupAliases"></param>
/// <returns></returns>
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> invalidPropertyAliases)
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> duplicatePropertyTypeAliases, IEnumerable<string> invalidPropertyGroupAliases)
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TPropertyType : PropertyTypeBasic
{
foreach (var propertyAlias in invalidPropertyAliases)
foreach (var propertyTypeAlias in duplicatePropertyTypeAliases)
{
// Find the property relating to these
var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
// Find the property type relating to these
var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyTypeAlias);
var 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 not allowed between compositions");
ModelState.AddModelError(key, "Duplicate property aliases aren't allowed between compositions");
}

foreach (var propertyGroupAlias in invalidPropertyGroupAliases)
{
// Find the property group relating to these
var 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");
}
}

Expand Down Expand Up @@ -519,7 +531,7 @@ private HttpResponseException CreateInvalidCompositionResponseException<TContent
}
if (invalidCompositionException != null)
{
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidCompositionException.PropertyTypeAliases);
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
return CreateModelStateValidationException<TContentTypeSave, TContentTypeDisplay>(ctId, contentTypeSave, ct);
}
return null;
Expand Down

0 comments on commit a284d52

Please sign in to comment.