Merge branch 'temp-U4-7635' into 7.4.0
This commit is contained in:
@@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using AutoMapper;
|
||||||
using Umbraco.Core.Auditing;
|
using Umbraco.Core.Auditing;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Configuration;
|
||||||
using Umbraco.Core.Events;
|
using Umbraco.Core.Events;
|
||||||
|
|||||||
101
src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
Normal file
101
src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
|
||||||
|
namespace Umbraco.Core.Services
|
||||||
|
{
|
||||||
|
public static class ContentTypeServiceExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the available composite content types for a given content type
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IEnumerable<IContentTypeComposition> GetAvailableCompositeContentTypes(this IContentTypeService ctService,
|
||||||
|
IContentTypeComposition source,
|
||||||
|
IContentTypeComposition[] allContentTypes)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (source == null) throw new ArgumentNullException("source");
|
||||||
|
//below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
|
||||||
|
|
||||||
|
// note: there are many sanity checks missing here and there ;-((
|
||||||
|
// make sure once and for all
|
||||||
|
//if (allContentTypes.Any(x => x.ParentId > 0 && x.ContentTypeComposition.Any(y => y.Id == x.ParentId) == false))
|
||||||
|
// throw new Exception("A parent does not belong to a composition.");
|
||||||
|
|
||||||
|
// find out if any content type uses this content type
|
||||||
|
var isUsing = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == source.Id)).ToArray();
|
||||||
|
if (isUsing.Length > 0)
|
||||||
|
{
|
||||||
|
//if already in use a composition, do not allow any composited types
|
||||||
|
return new List<IContentTypeComposition>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is not used then composition is possible
|
||||||
|
// hashset guarantees unicity on Id
|
||||||
|
var list = new HashSet<IContentTypeComposition>(new DelegateEqualityComparer<IContentTypeComposition>(
|
||||||
|
(x, y) => x.Id == y.Id,
|
||||||
|
x => x.Id));
|
||||||
|
|
||||||
|
// usable types are those that are top-level
|
||||||
|
var usableContentTypes = allContentTypes
|
||||||
|
.Where(x => x.ContentTypeComposition.Any() == false).ToArray();
|
||||||
|
foreach (var x in usableContentTypes)
|
||||||
|
list.Add(x);
|
||||||
|
|
||||||
|
// indirect types are those that we use, directly or indirectly
|
||||||
|
var indirectContentTypes = GetDirectOrIndirect(source).ToArray();
|
||||||
|
foreach (var x in indirectContentTypes)
|
||||||
|
list.Add(x);
|
||||||
|
|
||||||
|
//// directContentTypes are those we use directly
|
||||||
|
//// they are already in indirectContentTypes, no need to add to the list
|
||||||
|
//var directContentTypes = source.ContentTypeComposition.ToArray();
|
||||||
|
|
||||||
|
//var enabled = usableContentTypes.Select(x => x.Id) // those we can use
|
||||||
|
// .Except(indirectContentTypes.Select(x => x.Id)) // except those that are indirectly used
|
||||||
|
// .Union(directContentTypes.Select(x => x.Id)) // but those that are directly used
|
||||||
|
// .Where(x => x != source.ParentId) // but not the parent
|
||||||
|
// .Distinct()
|
||||||
|
// .ToArray();
|
||||||
|
|
||||||
|
return list
|
||||||
|
.Where(x => x.Id != source.Id)
|
||||||
|
.OrderBy(x => x.Name)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get those that we use directly or indirectly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ctype"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static IEnumerable<IContentTypeComposition> GetDirectOrIndirect(IContentTypeComposition ctype)
|
||||||
|
{
|
||||||
|
// hashset guarantees unicity on Id
|
||||||
|
var all = new HashSet<IContentTypeComposition>(new DelegateEqualityComparer<IContentTypeComposition>(
|
||||||
|
(x, y) => x.Id == y.Id,
|
||||||
|
x => x.Id));
|
||||||
|
|
||||||
|
var stack = new Stack<IContentTypeComposition>();
|
||||||
|
|
||||||
|
if (ctype != null)
|
||||||
|
{
|
||||||
|
foreach (var x in ctype.ContentTypeComposition)
|
||||||
|
stack.Push(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (stack.Count > 0)
|
||||||
|
{
|
||||||
|
var x = stack.Pop();
|
||||||
|
all.Add(x);
|
||||||
|
foreach (var y in x.ContentTypeComposition)
|
||||||
|
stack.Push(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -496,6 +496,7 @@
|
|||||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\CreateCacheInstructionTable.cs" />
|
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\CreateCacheInstructionTable.cs" />
|
||||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\MigrateStylesheetDataToFile.cs" />
|
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\MigrateStylesheetDataToFile.cs" />
|
||||||
<Compile Include="Services\AuditService.cs" />
|
<Compile Include="Services\AuditService.cs" />
|
||||||
|
<Compile Include="Services\ContentTypeServiceExtensions.cs" />
|
||||||
<Compile Include="Services\DomainService.cs" />
|
<Compile Include="Services\DomainService.cs" />
|
||||||
<Compile Include="Services\ExternalLoginService.cs" />
|
<Compile Include="Services\ExternalLoginService.cs" />
|
||||||
<Compile Include="Services\IAuditService.cs" />
|
<Compile Include="Services\IAuditService.cs" />
|
||||||
|
|||||||
148
src/Umbraco.Tests/Services/ContentTypeServiceExtensionsTests.cs
Normal file
148
src/Umbraco.Tests/Services/ContentTypeServiceExtensionsTests.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Umbraco.Core.Services;
|
||||||
|
using Umbraco.Tests.TestHelpers;
|
||||||
|
using Umbraco.Tests.TestHelpers.Entities;
|
||||||
|
|
||||||
|
namespace Umbraco.Tests.Services
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ContentTypeServiceExtensionsTests : BaseUmbracoApplicationTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void GetAvailableCompositeContentTypes_Not_Itself()
|
||||||
|
{
|
||||||
|
var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1", null);
|
||||||
|
var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2", null);
|
||||||
|
var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3", null);
|
||||||
|
ct1.Id = 1;
|
||||||
|
ct2.Id = 2;
|
||||||
|
ct3.Id = 3;
|
||||||
|
|
||||||
|
var service = new Mock<IContentTypeService>();
|
||||||
|
|
||||||
|
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||||
|
ct1,
|
||||||
|
new[] {ct1, ct2, ct3});
|
||||||
|
|
||||||
|
Assert.AreEqual(2, availableTypes.Count());
|
||||||
|
Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id);
|
||||||
|
Assert.AreEqual(ct3.Id, availableTypes.ElementAt(1).Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This shows that a nested comp is not allowed
|
||||||
|
[Test]
|
||||||
|
public void GetAvailableCompositeContentTypes_No_Results_If_Already_A_Composition_By_Parent()
|
||||||
|
{
|
||||||
|
var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1");
|
||||||
|
var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2", ct1);
|
||||||
|
var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3");
|
||||||
|
ct1.Id = 1;
|
||||||
|
ct2.Id = 2;
|
||||||
|
ct3.Id = 3;
|
||||||
|
|
||||||
|
var service = new Mock<IContentTypeService>();
|
||||||
|
|
||||||
|
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||||
|
ct1,
|
||||||
|
new[] { ct1, ct2, ct3 });
|
||||||
|
|
||||||
|
Assert.AreEqual(0, availableTypes.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
//This shows that a nested comp is not allowed
|
||||||
|
[Test]
|
||||||
|
public void GetAvailableCompositeContentTypes_No_Results_If_Already_A_Composition()
|
||||||
|
{
|
||||||
|
var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1");
|
||||||
|
var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2");
|
||||||
|
var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3");
|
||||||
|
ct1.Id = 1;
|
||||||
|
ct2.Id = 2;
|
||||||
|
ct3.Id = 3;
|
||||||
|
|
||||||
|
ct2.AddContentType(ct1);
|
||||||
|
|
||||||
|
var service = new Mock<IContentTypeService>();
|
||||||
|
|
||||||
|
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||||
|
ct1,
|
||||||
|
new[] { ct1, ct2, ct3 });
|
||||||
|
|
||||||
|
Assert.AreEqual(0, availableTypes.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetAvailableCompositeContentTypes_Do_Not_Include_Other_Composed_Types()
|
||||||
|
{
|
||||||
|
var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1");
|
||||||
|
var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2");
|
||||||
|
var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3");
|
||||||
|
ct1.Id = 1;
|
||||||
|
ct2.Id = 2;
|
||||||
|
ct3.Id = 3;
|
||||||
|
|
||||||
|
ct2.AddContentType(ct3);
|
||||||
|
|
||||||
|
var service = new Mock<IContentTypeService>();
|
||||||
|
|
||||||
|
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||||
|
ct1,
|
||||||
|
new[] { ct1, ct2, ct3 });
|
||||||
|
|
||||||
|
Assert.AreEqual(1, availableTypes.Count());
|
||||||
|
Assert.AreEqual(ct3.Id, availableTypes.Single().Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetAvailableCompositeContentTypes_Include_Direct_Composed_Types()
|
||||||
|
{
|
||||||
|
var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1");
|
||||||
|
var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2");
|
||||||
|
var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3");
|
||||||
|
ct1.Id = 1;
|
||||||
|
ct2.Id = 2;
|
||||||
|
ct3.Id = 3;
|
||||||
|
|
||||||
|
ct1.AddContentType(ct3);
|
||||||
|
|
||||||
|
var service = new Mock<IContentTypeService>();
|
||||||
|
|
||||||
|
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||||
|
ct1,
|
||||||
|
new[] { ct1, ct2, ct3 });
|
||||||
|
|
||||||
|
Assert.AreEqual(2, availableTypes.Count());
|
||||||
|
Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id);
|
||||||
|
Assert.AreEqual(ct3.Id, availableTypes.ElementAt(1).Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetAvailableCompositeContentTypes_Include_Indirect_Composed_Types()
|
||||||
|
{
|
||||||
|
var ct1 = MockedContentTypes.CreateBasicContentType("ct1", "CT1");
|
||||||
|
var ct2 = MockedContentTypes.CreateBasicContentType("ct2", "CT2");
|
||||||
|
var ct3 = MockedContentTypes.CreateBasicContentType("ct3", "CT3");
|
||||||
|
var ct4 = MockedContentTypes.CreateBasicContentType("ct4", "CT4");
|
||||||
|
ct1.Id = 1;
|
||||||
|
ct2.Id = 2;
|
||||||
|
ct3.Id = 3;
|
||||||
|
ct4.Id = 4;
|
||||||
|
|
||||||
|
ct1.AddContentType(ct3); //ct3 is direct to ct1
|
||||||
|
ct3.AddContentType(ct4); //ct4 is indirect to ct1
|
||||||
|
|
||||||
|
var service = new Mock<IContentTypeService>();
|
||||||
|
|
||||||
|
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||||
|
ct1,
|
||||||
|
new[] { ct1, ct2, ct3 });
|
||||||
|
|
||||||
|
Assert.AreEqual(3, availableTypes.Count());
|
||||||
|
Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id);
|
||||||
|
Assert.AreEqual(ct3.Id, availableTypes.ElementAt(1).Id);
|
||||||
|
Assert.AreEqual(ct4.Id, availableTypes.ElementAt(2).Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -191,6 +191,7 @@
|
|||||||
<Compile Include="Resolvers\ResolverBaseTest.cs" />
|
<Compile Include="Resolvers\ResolverBaseTest.cs" />
|
||||||
<Compile Include="Routing\UrlRoutingTestBase.cs" />
|
<Compile Include="Routing\UrlRoutingTestBase.cs" />
|
||||||
<Compile Include="Security\UmbracoBackOfficeIdentityTests.cs" />
|
<Compile Include="Security\UmbracoBackOfficeIdentityTests.cs" />
|
||||||
|
<Compile Include="Services\ContentTypeServiceExtensionsTests.cs" />
|
||||||
<Compile Include="Services\PublicAccessServiceTests.cs" />
|
<Compile Include="Services\PublicAccessServiceTests.cs" />
|
||||||
<Compile Include="StringNewlineExtensions.cs" />
|
<Compile Include="StringNewlineExtensions.cs" />
|
||||||
<Compile Include="Strings\StylesheetHelperTests.cs" />
|
<Compile Include="Strings\StylesheetHelperTests.cs" />
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ namespace Umbraco.Web.Editors
|
|||||||
//below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
|
//below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
|
||||||
|
|
||||||
IContentTypeComposition[] allContentTypes;
|
IContentTypeComposition[] allContentTypes;
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case UmbracoObjectTypes.DocumentType:
|
case UmbracoObjectTypes.DocumentType:
|
||||||
@@ -89,52 +88,11 @@ namespace Umbraco.Web.Editors
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filtered = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes);
|
||||||
|
|
||||||
// note: there are many sanity checks missing here and there ;-((
|
return filtered
|
||||||
// make sure once and for all
|
|
||||||
//if (allContentTypes.Any(x => x.ParentId > 0 && x.ContentTypeComposition.Any(y => y.Id == x.ParentId) == false))
|
|
||||||
// throw new Exception("A parent does not belong to a composition.");
|
|
||||||
|
|
||||||
// find out if any content type uses this content type
|
|
||||||
var isUsing = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == contentTypeId)).ToArray();
|
|
||||||
if (isUsing.Length > 0)
|
|
||||||
{
|
|
||||||
//if already in use a composition, do not allow any composited types
|
|
||||||
return new List<EntityBasic>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it is not used then composition is possible
|
|
||||||
// hashset guarantees unicity on Id
|
|
||||||
var list = new HashSet<IContentTypeComposition>(new DelegateEqualityComparer<IContentTypeComposition>(
|
|
||||||
(x, y) => x.Id == y.Id,
|
|
||||||
x => x.Id));
|
|
||||||
|
|
||||||
// usable types are those that are top-level
|
|
||||||
var usableContentTypes = allContentTypes
|
|
||||||
.Where(x => x.ContentTypeComposition.Any() == false).ToArray();
|
|
||||||
foreach (var x in usableContentTypes)
|
|
||||||
list.Add(x);
|
|
||||||
|
|
||||||
// indirect types are those that we use, directly or indirectly
|
|
||||||
var indirectContentTypes = GetIndirect(source).ToArray();
|
|
||||||
foreach (var x in indirectContentTypes)
|
|
||||||
list.Add(x);
|
|
||||||
|
|
||||||
//// directContentTypes are those we use directly
|
|
||||||
//// they are already in indirectContentTypes, no need to add to the list
|
|
||||||
//var directContentTypes = source.ContentTypeComposition.ToArray();
|
|
||||||
|
|
||||||
//var enabled = usableContentTypes.Select(x => x.Id) // those we can use
|
|
||||||
// .Except(indirectContentTypes.Select(x => x.Id)) // except those that are indirectly used
|
|
||||||
// .Union(directContentTypes.Select(x => x.Id)) // but those that are directly used
|
|
||||||
// .Where(x => x != source.ParentId) // but not the parent
|
|
||||||
// .Distinct()
|
|
||||||
// .ToArray();
|
|
||||||
|
|
||||||
return list
|
|
||||||
.Where(x => x.Id != contentTypeId)
|
|
||||||
.OrderBy(x => x.Name)
|
|
||||||
.Select(Mapper.Map<IContentTypeComposition, EntityBasic>)
|
.Select(Mapper.Map<IContentTypeComposition, EntityBasic>)
|
||||||
.Select(x =>
|
.Select(x =>
|
||||||
{
|
{
|
||||||
@@ -143,32 +101,7 @@ namespace Umbraco.Web.Editors
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<IContentTypeComposition> GetIndirect(IContentTypeComposition ctype)
|
|
||||||
{
|
|
||||||
// hashset guarantees unicity on Id
|
|
||||||
var all = new HashSet<IContentTypeComposition>(new DelegateEqualityComparer<IContentTypeComposition>(
|
|
||||||
(x, y) => x.Id == y.Id,
|
|
||||||
x => x.Id));
|
|
||||||
|
|
||||||
var stack = new Stack<IContentTypeComposition>();
|
|
||||||
|
|
||||||
if (ctype != null)
|
|
||||||
{
|
|
||||||
foreach (var x in ctype.ContentTypeComposition)
|
|
||||||
stack.Push(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (stack.Count > 0)
|
|
||||||
{
|
|
||||||
var x = stack.Pop();
|
|
||||||
all.Add(x);
|
|
||||||
foreach (var y in x.ContentTypeComposition)
|
|
||||||
stack.Push(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
|
/// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
|
||||||
|
|||||||
Reference in New Issue
Block a user