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:
Shannon
2016-01-13 16:25:35 +01:00
parent 6d5435c2e8
commit 54669ee2bc
9 changed files with 126 additions and 42 deletions

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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" />

View File

@@ -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);

View File

@@ -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>

View File

@@ -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);

View File

@@ -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
{