Ensures you cannot move a child doc type, changes the result of GetAvailableCompositions to be a model instead of a tuple, the result also includes the current ancestor list which we can use to disable items in the list, makes these models/methods internal since they'll most likely be changing,
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Used when determining available compositions for a given content type
|
||||
/// </summary>
|
||||
internal class ContentTypeAvailableCompositionsResult
|
||||
{
|
||||
public ContentTypeAvailableCompositionsResult(IContentTypeComposition composition, bool allowed)
|
||||
{
|
||||
Composition = composition;
|
||||
Allowed = allowed;
|
||||
}
|
||||
|
||||
public IContentTypeComposition Composition { get; private set; }
|
||||
public bool Allowed { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Used when determining available compositions for a given content type
|
||||
/// </summary>
|
||||
internal class ContentTypeAvailableCompositionsResults
|
||||
{
|
||||
public ContentTypeAvailableCompositionsResults()
|
||||
{
|
||||
Ancestors = Enumerable.Empty<IContentTypeComposition>();
|
||||
Results = Enumerable.Empty<ContentTypeAvailableCompositionsResult>();
|
||||
}
|
||||
|
||||
public ContentTypeAvailableCompositionsResults(IEnumerable<IContentTypeComposition> ancestors, IEnumerable<ContentTypeAvailableCompositionsResult> results)
|
||||
{
|
||||
Ancestors = ancestors;
|
||||
Results = results;
|
||||
}
|
||||
|
||||
public IEnumerable<IContentTypeComposition> Ancestors { get; private set; }
|
||||
public IEnumerable<ContentTypeAvailableCompositionsResult> Results { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ namespace Umbraco.Core.Models
|
||||
|
||||
protected ContentTypeCompositionBase(IContentTypeComposition parent)
|
||||
: this(parent, null)
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias)
|
||||
: base(parent, alias)
|
||||
@@ -122,10 +122,10 @@ namespace Umbraco.Core.Models
|
||||
return false;
|
||||
|
||||
RemovedContentTypeKeyTracker.Add(contentTypeComposition.Id);
|
||||
|
||||
|
||||
//If the ContentType we are removing has Compositions of its own these needs to be removed as well
|
||||
var compositionIdsToRemove = contentTypeComposition.CompositionIds().ToList();
|
||||
if(compositionIdsToRemove.Any())
|
||||
if (compositionIdsToRemove.Any())
|
||||
RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove);
|
||||
|
||||
OnPropertyChanged(ContentTypeCompositionSelector);
|
||||
@@ -218,8 +218,8 @@ namespace Umbraco.Core.Models
|
||||
return false;
|
||||
|
||||
// get and ensure a group local to this content type
|
||||
var group = PropertyGroups.Contains(propertyGroupName)
|
||||
? PropertyGroups[propertyGroupName]
|
||||
var group = PropertyGroups.Contains(propertyGroupName)
|
||||
? PropertyGroups[propertyGroupName]
|
||||
: AddAndReturnPropertyGroup(propertyGroupName);
|
||||
if (group == null)
|
||||
return false;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Services
|
||||
/// be looked up via the db, they need to be passed in.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Tuple<IContentTypeComposition, bool>> GetAvailableCompositeContentTypes(this IContentTypeService ctService,
|
||||
internal static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(this IContentTypeService ctService,
|
||||
IContentTypeComposition source,
|
||||
IContentTypeComposition[] allContentTypes,
|
||||
string[] filterContentTypes = null,
|
||||
@@ -54,7 +54,7 @@ namespace Umbraco.Core.Services
|
||||
if (isUsing.Length > 0)
|
||||
{
|
||||
//if already in use a composition, do not allow any composited types
|
||||
return new List<Tuple<IContentTypeComposition, bool>>();
|
||||
return new ContentTypeAvailableCompositionsResults();
|
||||
}
|
||||
|
||||
// if it is not used then composition is possible
|
||||
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Services
|
||||
.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)
|
||||
@@ -93,21 +93,46 @@ namespace Umbraco.Core.Services
|
||||
})
|
||||
.OrderBy(x => x.Name)
|
||||
.ToList();
|
||||
|
||||
//now we can create our result based on what is still available
|
||||
|
||||
//get ancestor ids - we will filter all ancestors
|
||||
var ancestors = GetAncestors(source, allContentTypes);
|
||||
var ancestorIds = ancestors.Select(x => x.Id).ToArray();
|
||||
|
||||
//now we can create our result based on what is still available and the ancestors
|
||||
var result = list
|
||||
//not itself
|
||||
.Where(x => x.Id != sourceId)
|
||||
.OrderBy(x => x.Name)
|
||||
.Select(composition => filtered.Contains(composition)
|
||||
? new Tuple<IContentTypeComposition, bool>(composition, true)
|
||||
: new Tuple<IContentTypeComposition, bool>(composition, false)).ToList();
|
||||
? new ContentTypeAvailableCompositionsResult(composition, ancestorIds.Contains(composition.Id) == false)
|
||||
: new ContentTypeAvailableCompositionsResult(composition, false)).ToList();
|
||||
|
||||
return result;
|
||||
return new ContentTypeAvailableCompositionsResults(ancestors, result);
|
||||
}
|
||||
|
||||
private static IContentTypeComposition[] GetAncestors(IContentTypeComposition ctype, IContentTypeComposition[] allContentTypes)
|
||||
{
|
||||
if (ctype == null) return new IContentTypeComposition[] {};
|
||||
var ancestors = new List<IContentTypeComposition>();
|
||||
var parentId = ctype.ParentId;
|
||||
while (parentId > 0)
|
||||
{
|
||||
var parent = allContentTypes.FirstOrDefault(x => x.Id == parentId);
|
||||
if (parent != null)
|
||||
{
|
||||
ancestors.Add(parent);
|
||||
parentId = parent.ParentId;
|
||||
}
|
||||
else
|
||||
{
|
||||
parentId = -1;
|
||||
}
|
||||
}
|
||||
return ancestors.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get those that we use directly or indirectly
|
||||
/// Get those that we use directly
|
||||
/// </summary>
|
||||
/// <param name="ctype"></param>
|
||||
/// <returns></returns>
|
||||
@@ -121,12 +146,9 @@ namespace Umbraco.Core.Services
|
||||
x => x.Id));
|
||||
|
||||
var stack = new Stack<IContentTypeComposition>();
|
||||
|
||||
if (ctype != null)
|
||||
{
|
||||
foreach (var x in ctype.ContentTypeComposition)
|
||||
stack.Push(x);
|
||||
}
|
||||
|
||||
foreach (var x in ctype.ContentTypeComposition)
|
||||
stack.Push(x);
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
<Compile Include="Cache\RepositoryCachePolicyOptions.cs" />
|
||||
<Compile Include="Cache\StaticCacheProvider.cs" />
|
||||
<Compile Include="Cache\TypedCacheRefresherBase.cs" />
|
||||
<Compile Include="Cache\DeepCloneRuntimeCacheProvider.cs" />
|
||||
<Compile Include="Cache\DeepCloneRuntimeCacheProvider.cs" />
|
||||
<Compile Include="CodeAnnotations\FriendlyNameAttribute.cs" />
|
||||
<Compile Include="CodeAnnotations\UmbracoObjectTypeAttribute.cs" />
|
||||
<Compile Include="CodeAnnotations\UmbracoWillObsoleteAttribute.cs" />
|
||||
@@ -370,6 +370,8 @@
|
||||
<Compile Include="Manifest\GridEditorConverter.cs" />
|
||||
<Compile Include="Models\AuditItem.cs" />
|
||||
<Compile Include="Models\AuditType.cs" />
|
||||
<Compile Include="Models\ContentTypeAvailableCompositionsResult.cs" />
|
||||
<Compile Include="Models\ContentTypeAvailableCompositionsResults.cs" />
|
||||
<Compile Include="Models\EntityContainer.cs" />
|
||||
<Compile Include="Models\Identity\IdentityModelMappings.cs" />
|
||||
<Compile Include="Models\Identity\IdentityUser.cs" />
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Umbraco.Tests.Services
|
||||
new[] { ct1, ct2, ct3, ct4, ct5 },
|
||||
new[] { ct2.Alias },
|
||||
new[] { "blah" })
|
||||
.Where(x => x.Item2).Select(x => x.Item1).ToArray();
|
||||
.Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray();
|
||||
|
||||
Assert.AreEqual(1, availableTypes.Count());
|
||||
Assert.AreEqual(ct4.Id, availableTypes.ElementAt(0).Id);
|
||||
@@ -84,7 +84,7 @@ namespace Umbraco.Tests.Services
|
||||
new[] { ct1, ct2, ct3, ct4 },
|
||||
new string[] { },
|
||||
new[] { "title" })
|
||||
.Where(x => x.Item2).Select(x => x.Item1).ToArray();
|
||||
.Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray();
|
||||
|
||||
Assert.AreEqual(1, availableTypes.Count());
|
||||
Assert.AreEqual(ct4.Id, availableTypes.ElementAt(0).Id);
|
||||
@@ -120,7 +120,7 @@ namespace Umbraco.Tests.Services
|
||||
ct1,
|
||||
new[] { ct1, ct2, ct3, ct4 },
|
||||
new [] {ct2.Alias})
|
||||
.Where(x => x.Item2).Select(x => x.Item1).ToArray();
|
||||
.Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray();
|
||||
|
||||
Assert.AreEqual(1, availableTypes.Count());
|
||||
Assert.AreEqual(ct4.Id, availableTypes.ElementAt(0).Id);
|
||||
@@ -141,7 +141,7 @@ namespace Umbraco.Tests.Services
|
||||
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||
ct1,
|
||||
new[] {ct1, ct2, ct3})
|
||||
.Where(x => x.Item2).Select(x => x.Item1).ToArray();
|
||||
.Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray();
|
||||
|
||||
Assert.AreEqual(2, availableTypes.Count());
|
||||
Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id);
|
||||
@@ -163,7 +163,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||
ct1,
|
||||
new[] { ct1, ct2, ct3 });
|
||||
new[] { ct1, ct2, ct3 }).Results;
|
||||
|
||||
Assert.AreEqual(0, availableTypes.Count());
|
||||
}
|
||||
@@ -185,7 +185,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||
ct1,
|
||||
new[] { ct1, ct2, ct3 });
|
||||
new[] { ct1, ct2, ct3 }).Results;
|
||||
|
||||
Assert.AreEqual(0, availableTypes.Count());
|
||||
}
|
||||
@@ -207,7 +207,7 @@ namespace Umbraco.Tests.Services
|
||||
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||
ct1,
|
||||
new[] { ct1, ct2, ct3 })
|
||||
.Where(x => x.Item2).Select(x => x.Item1).ToArray();
|
||||
.Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray();
|
||||
|
||||
Assert.AreEqual(1, availableTypes.Count());
|
||||
Assert.AreEqual(ct3.Id, availableTypes.Single().Id);
|
||||
@@ -230,7 +230,7 @@ namespace Umbraco.Tests.Services
|
||||
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||
ct1,
|
||||
new[] { ct1, ct2, ct3 })
|
||||
.Where(x => x.Item2).Select(x => x.Item1).ToArray();
|
||||
.Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray();
|
||||
|
||||
Assert.AreEqual(2, availableTypes.Count());
|
||||
Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id);
|
||||
@@ -257,7 +257,7 @@ namespace Umbraco.Tests.Services
|
||||
var availableTypes = service.Object.GetAvailableCompositeContentTypes(
|
||||
ct1,
|
||||
new[] { ct1, ct2, ct3 })
|
||||
.Where(x => x.Item2).Select(x => x.Item1).ToArray();
|
||||
.Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray();
|
||||
|
||||
Assert.AreEqual(3, availableTypes.Count());
|
||||
Assert.AreEqual(ct2.Id, availableTypes.ElementAt(0).Id);
|
||||
|
||||
@@ -31,22 +31,22 @@
|
||||
<li class="umb-checkbox-list__item"
|
||||
|
||||
ng-repeat="compositeContentType in model.availableCompositeContentTypes | filter:searchTerm"
|
||||
ng-class="{'-disabled': vm.isDisabled(compositeContentType.alias), '-selected': vm.isSelected(compositeContentType.alias)}">
|
||||
ng-class="{'-disabled': vm.isDisabled(compositeContentType.contentType.alias), '-selected': vm.isSelected(compositeContentType.contentType.alias)}">
|
||||
|
||||
<div class="umb-checkbox-list__item-checkbox"
|
||||
ng-class="{ '-selected': model.compositeContentTypes.indexOf(compositeContentType.alias)+1 }">
|
||||
ng-class="{ '-selected': model.compositeContentTypes.indexOf(compositeContentType.contentType.alias)+1 }">
|
||||
<input type="checkbox"
|
||||
id="umb-overlay-comp-{{compositeContentType.key}}"
|
||||
id="umb-overlay-comp-{{compositeContentType.contentType.key}}"
|
||||
checklist-model="model.compositeContentTypes"
|
||||
checklist-value="compositeContentType.contentType.alias"
|
||||
ng-change="model.selectCompositeContentType(compositeContentType.contentType)"
|
||||
ng-disabled="compositeContentType.allowed===false" />
|
||||
</div>
|
||||
|
||||
<label for="umb-overlay-comp-{{compositeContentType.key}}" class="umb-checkbox-list__item-text" ng-class="{'-faded': vm.isDisabled(compositeContentType.alias)}">
|
||||
<label for="umb-overlay-comp-{{compositeContentType.contentType.key}}" class="umb-checkbox-list__item-text" ng-class="{'-faded': vm.isDisabled(compositeContentType.contentType.alias)}">
|
||||
<i class="{{ compositeContentType.contentType.icon }} umb-checkbox-list__item-icon"></i>
|
||||
{{ compositeContentType.contentType.name }}
|
||||
<span class="umb-checkbox-list__item-caption" ng-if="vm.isDisabled(compositeContentType.alias)">(inherited)</span>
|
||||
<span class="umb-checkbox-list__item-caption" ng-if="vm.isDisabled(compositeContentType.contentType.alias)">(inherited)</span>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
@@ -106,19 +106,22 @@ namespace Umbraco.Web.Editors
|
||||
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
||||
}
|
||||
|
||||
var filtered = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes);
|
||||
var availableCompositions = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes);
|
||||
|
||||
var currCompositions = source == null ? new string[] { } : source.ContentTypeComposition.Select(x => x.Alias).ToArray();
|
||||
var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray();
|
||||
var compAliases = currCompositions.Select(x => x.Alias).ToArray();
|
||||
var ancestors = availableCompositions.Ancestors.Select(x => x.Alias);
|
||||
|
||||
return filtered
|
||||
.Select(x => new Tuple<EntityBasic, bool>(Mapper.Map<IContentTypeComposition, EntityBasic>(x.Item1), x.Item2))
|
||||
return availableCompositions.Results
|
||||
.Select(x => new Tuple<EntityBasic, bool>(Mapper.Map<IContentTypeComposition, EntityBasic>(x.Composition), x.Allowed))
|
||||
.Select(x =>
|
||||
{
|
||||
//translate the name
|
||||
x.Item1.Name = TranslateItem(x.Item1.Name);
|
||||
|
||||
//we need to ensure that the item is enabled if it is already selected
|
||||
if (currCompositions.Contains(x.Item1.Alias))
|
||||
// but do not allow it if it is any of the ancestors
|
||||
if (compAliases.Contains(x.Item1.Alias) && ancestors.Contains(x.Item1.Alias) == false)
|
||||
{
|
||||
//re-set x to be allowed (NOTE: I didn't know you could set an enumerable item in a lambda!)
|
||||
x = new Tuple<EntityBasic, bool>(x.Item1, true);
|
||||
|
||||
@@ -100,14 +100,28 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
else
|
||||
{
|
||||
var ct = Services.ContentTypeService.GetContentType(int.Parse(id));
|
||||
IContentType parent = null;
|
||||
parent = ct == null ? null : Services.ContentTypeService.GetContentType(ct.ParentId);
|
||||
|
||||
if (enableInheritedDocumentTypes)
|
||||
{
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
|
||||
menu.Items.Add<ActionMove>(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true);
|
||||
|
||||
//no move action if this is a child doc type
|
||||
if (parent == null)
|
||||
{
|
||||
menu.Items.Add<ActionMove>(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.Items.Add<ActionMove>(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)));
|
||||
//no move action if this is a child doc type
|
||||
if (parent == null)
|
||||
{
|
||||
menu.Items.Add<ActionMove>(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true);
|
||||
}
|
||||
}
|
||||
menu.Items.Add<ActionExport>(Services.TextService.Localize(string.Format("actions/{0}", ActionExport.Instance.Alias)), true).ConvertLegacyMenuItem(new UmbracoEntity
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user