diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index b87dfa1ac1..ca3261f868 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -8,11 +8,13 @@ using System.Threading; using Umbraco.Core.Auditing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services @@ -273,6 +275,60 @@ namespace Umbraco.Core.Services } + public void Validate(IContentTypeComposition compo) + { + using (new WriteLock(Locker)) + { + ValidateLocked(compo); + } + } + + private void ValidateLocked(IContentTypeComposition compo) + { + // performs business-level validation of the composition + // should ensure that it is absolutely safe to save the composition + + // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors) + // but that cannot be used (conflict with descendants) + + var contentType = compo as IContentType; + var mediaType = compo as IMediaType; + + IContentTypeComposition[] allContentTypes; + if (contentType != null) + allContentTypes = GetAllContentTypes().Cast().ToArray(); + else if (mediaType != null) + allContentTypes = GetAllMediaTypes().Cast().ToArray(); + else + throw new Exception("Composition is neither IContentType nor IMediaType?"); + + // recursively find all descendants + var comparer = new DelegateEqualityComparer((x, y) => x.Id == y.Id, x => x.Id); + var descendants = new HashSet(comparer); + var stack = new Stack(); + foreach (var z in allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compo.Id))) + stack.Push(z); + while (stack.Count > 0) + { + var c = stack.Pop(); + descendants.Add(c); + foreach (var z in allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == c.Id))) + stack.Push(z); + } + + // ensure that no descendant has a property with an alias that is used by content type + var aliases = compo.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).ToArray(); + foreach (var d in descendants) + { + var intersect = d.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).Intersect(aliases).ToArray(); + if (intersect.Length == 0) continue; + + var message = string.Format("The following property aliases conflict with descendants : {0}.", + string.Join(", ", intersect)); + throw new Exception(message); + } + } + /// /// Saves a single object /// @@ -288,6 +344,7 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) { + ValidateLocked(contentType); // throws if invalid contentType.CreatorId = userId; repository.AddOrUpdate(contentType); @@ -317,6 +374,11 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) { + // all-or-nothing, validate them all first + foreach (var contentType in asArray) + { + ValidateLocked(contentType); // throws if invalid + } foreach (var contentType in asArray) { contentType.CreatorId = userId; @@ -487,6 +549,7 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMediaTypeRepository(uow)) { + ValidateLocked(mediaType); // throws if invalid mediaType.CreatorId = userId; repository.AddOrUpdate(mediaType); uow.Commit(); @@ -517,7 +580,11 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMediaTypeRepository(uow)) { - + // all-or-nothing, validate them all first + foreach (var mediaType in asArray) + { + ValidateLocked(mediaType); // throws if invalid + } foreach (var mediaType in asArray) { mediaType.CreatorId = userId; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 3d4cf04cce..a1f4762aa6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -426,6 +426,11 @@ namespace umbraco.controls state.SaveArgs.Message = ex.Message; return; } + catch (Exception ex) + { + state.SaveArgs.IconType = BasePage.speechBubbleIcon.error; + state.SaveArgs.Message = ex.Message; + } Trace.Write("ContentTypeControlNew", "task completing"); };