U4-6837 New content type editor: availableCompositeContentTypes could be moved to service.

Allows for assigning compositions to doc types that are being created.
Removes unused getAssignedListViewDataType from contentTypeResource.
Removes unused getPropertyTypeScaffold from memberTypeResource.
Removes unused performContainerDelete from memberTypeResource - since member types don't support folders.
Ensures correct security for the ContentTypeController.GetAllPropertyTypeAliases and ContentTypeController.GetAvailableCompositeContentTypes.
Removes unused ContentTypeControllerBase.GetAssignedListViewDataType, this is no longer used because the list view is configured using the DataTypeController.
This commit is contained in:
Shannon
2015-12-15 12:25:42 +01:00
parent 198ba0db83
commit 050109edd7
15 changed files with 199 additions and 225 deletions

View File

@@ -7,15 +7,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
return {
getAssignedListViewDataType: function (contentTypeId) {
getAvailableCompositeContentTypes: function (contentTypeId) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentTypeApiBaseUrl",
"GetAssignedListViewDataType",
"GetAvailableCompositeContentTypes",
[{ contentTypeId: contentTypeId }])),
'Failed to retrieve data for content id ' + contentTypeId);
'Failed to retrieve data for content type id ' + contentTypeId);
},
@@ -34,19 +33,19 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
* $scope.type = type;
* });
* </pre>
* @param {Int} contentId id of the content item to retrive allowed child types for
* @param {Int} contentTypeId id of the content item to retrive allowed child types for
* @returns {Promise} resourcePromise object.
*
*/
getAllowedTypes: function (contentId) {
getAllowedTypes: function (contentTypeId) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentTypeApiBaseUrl",
"GetAllowedChildren",
[{ contentId: contentId }])),
'Failed to retrieve data for content id ' + contentId);
[{ contentId: contentTypeId }])),
'Failed to retrieve data for content id ' + contentTypeId);
},

View File

@@ -7,6 +7,16 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
return {
getAvailableCompositeContentTypes: function (contentTypeId) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"mediaTypeApiBaseUrl",
"GetAvailableCompositeMediaTypes",
[{ contentTypeId: contentTypeId }])),
'Failed to retrieve data for content type id ' + contentTypeId);
},
/**
* @ngdoc method
* @name umbraco.resources.mediaTypeResource#getAllowedTypes

View File

@@ -7,6 +7,16 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
return {
getAvailableCompositeContentTypes: function (contentTypeId) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"memberTypeApiBaseUrl",
"GetAvailableCompositeMemberTypes",
[{ contentTypeId: contentTypeId }])),
'Failed to retrieve data for content type id ' + contentTypeId);
},
//return all member types
getTypes: function () {
@@ -16,17 +26,7 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
"memberTypeApiBaseUrl",
"GetAllTypes")),
'Failed to retrieve data for member types id');
},
getPropertyTypeScaffold : function (id) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"memberTypeApiBaseUrl",
"GetPropertyTypeScaffold",
[{ id: id }])),
'Failed to retrieve property type scaffold');
},
},
getById: function (id) {

View File

@@ -218,6 +218,13 @@
function init(contentType) {
//get available composite types
contentTypeResource.getAvailableCompositeContentTypes(contentType.id).then(function (result) {
contentType.availableCompositeContentTypes = result;
// convert legacy icons
convertLegacyIcons(contentType);
});
// set all tab to inactive
if (contentType.groups.length !== 0) {
angular.forEach(contentType.groups, function (group) {
@@ -230,8 +237,7 @@
});
}
// convert legacy icons
convertLegacyIcons(contentType);
// sort properties after sort order
angular.forEach(contentType.groups, function (group) {
@@ -244,6 +250,8 @@
contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates);
}
//set a shared state
editorState.set(contentType);

View File

@@ -6,7 +6,7 @@
* @description
* The controller for deleting content
*/
function MemberTypesDeleteController($scope, memberTypeResource, contentTypeResource, treeService, navigationService) {
function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService) {
$scope.performDelete = function() {
@@ -25,23 +25,6 @@ function MemberTypesDeleteController($scope, memberTypeResource, contentTypeReso
};
$scope.performContainerDelete = function() {
//mark it for deletion (used in the UI)
$scope.currentNode.loading = true;
contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
$scope.currentNode.loading = false;
//get the root node before we remove it
var rootNode = treeService.getTreeRoot($scope.currentNode);
//TODO: Need to sync tree, etc...
treeService.removeNode($scope.currentNode);
navigationService.hideMenu();
});
};
$scope.cancel = function() {
navigationService.hideDialog();
};

View File

@@ -5,33 +5,20 @@
Are you sure you want to delete <strong>{{currentNode.name}}</strong> ?
</p>
<ng-switch on="currentNode.nodeType">
<div ng-switch-when="container">
<p>This action cannot be undone, click ok to delete.</p>
<p>
<i class="icon-alert red"></i> <strong class="red">All members</strong>
using this member type will be deleted permanently, please confirm you want to delete these as well.
</p>
<umb-confirm
on-confirm="performContainerDelete"
on-cancel="cancel">
</umb-confirm>
</div>
<hr />
<div ng-switch-default>
<p>
<i class="icon-alert red"></i> <strong class="red">All members</strong>
using this member type will be deleted permanently, please confirm you want to delete these as well.
</p>
<label class="checkbox">
<input type="checkbox" ng-model="confirmed" />
Yes, delete {{currentNode.name}} and all members using this type
</label>
<hr />
<label class="checkbox">
<input type="checkbox" ng-model="confirmed" />
Yes, delete {{currentNode.name}} and all members using this type
</label>
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel">
</umb-confirm>
</div>
</ng-switch>
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel">
</umb-confirm>
</div>

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Web.UI.Umbraco.Controls
base.OnLoad(e);
DataTypeControllerUrl = Url.GetUmbracoApiServiceBaseUrl<DataTypeController>(x => x.GetById(0));
ContentTypeControllerUrl = Url.GetUmbracoApiServiceBaseUrl<ContentTypeController>(x => x.GetAssignedListViewDataType(0));
//ContentTypeControllerUrl = Url.GetUmbracoApiServiceBaseUrl<ContentTypeController>(x => x.GetAssignedListViewDataType(0));
}
protected void dgTabs_PreRender(object sender, EventArgs e)

View File

@@ -22,7 +22,8 @@ namespace Umbraco.Web.Editors
{
//TODO: We'll need to be careful about the security on this controller, when we start implementing
// methods to modify content types we'll need to enforce security on the individual methods, we
// cannot put security on the whole controller because things like GetAllowedChildren are required for content editing.
// cannot put security on the whole controller because things like
// GetAllowedChildren, GetPropertyTypeScaffold, GetAllPropertyTypeAliases are required for content editing.
/// <summary>
/// An API controller used for dealing with content types
@@ -85,11 +86,24 @@ namespace Umbraco.Web.Editors
/// Gets all user defined properties.
/// </summary>
/// <returns></returns>
[UmbracoTreeAuthorize(
Constants.Trees.DocumentTypes, Constants.Trees.Content,
Constants.Trees.MediaTypes, Constants.Trees.Media,
Constants.Trees.MemberTypes, Constants.Trees.Members)]
public IEnumerable<string> GetAllPropertyTypeAliases()
{
return ApplicationContext.Services.ContentTypeService.GetAllPropertyTypeAliases();
}
public IEnumerable<EntityBasic> GetAvailableCompositeContentTypes(int contentTypeId)
{
return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.DocumentType);
}
[UmbracoTreeAuthorize(
Constants.Trees.DocumentTypes, Constants.Trees.Content,
Constants.Trees.MediaTypes, Constants.Trees.Media,
Constants.Trees.MemberTypes, Constants.Trees.Members)]
public ContentPropertyDisplay GetPropertyTypeScaffold(int id)
{
var dataTypeDiff = Services.DataTypeService.GetDataTypeDefinitionById(id);

View File

@@ -46,37 +46,129 @@ namespace Umbraco.Web.Editors
{
}
protected internal DataTypeBasic GetAssignedListViewDataType(int contentTypeId)
/// <summary>
/// Returns the available composite content types for a given content type
/// </summary>
/// <returns></returns>
protected IEnumerable<EntityBasic> PerformGetAvailableCompositeContentTypes(int contentTypeId, UmbracoObjectTypes type)
{
var objectType = Services.EntityService.GetObjectType(contentTypeId);
IContentTypeComposition source = null;
switch (objectType)
{
case UmbracoObjectTypes.MemberType:
var memberType = Services.MemberTypeService.Get(contentTypeId);
var dtMember = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + memberType.Alias);
return dtMember == null
? Mapper.Map<IDataTypeDefinition, DataTypeBasic>(
Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Member"))
: Mapper.Map<IDataTypeDefinition, DataTypeBasic>(dtMember);
case UmbracoObjectTypes.MediaType:
var mediaType = Services.ContentTypeService.GetMediaType(contentTypeId);
var dtMedia = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + mediaType.Alias);
return dtMedia == null
? Mapper.Map<IDataTypeDefinition, DataTypeBasic>(
Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Media"))
: Mapper.Map<IDataTypeDefinition, DataTypeBasic>(dtMedia);
//below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
IContentTypeComposition[] allContentTypes;
switch (type)
{
case UmbracoObjectTypes.DocumentType:
var docType = Services.ContentTypeService.GetContentType(contentTypeId);
var dtDoc = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + docType.Alias);
return dtDoc == null
? Mapper.Map<IDataTypeDefinition, DataTypeBasic>(
Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Content"))
: Mapper.Map<IDataTypeDefinition, DataTypeBasic>(dtDoc);
default:
throw new ArgumentOutOfRangeException();
}
}
if (contentTypeId > 0)
{
source = Services.ContentTypeService.GetContentType(contentTypeId);
if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
allContentTypes = Services.ContentTypeService.GetAllContentTypes().Cast<IContentTypeComposition>().ToArray();
break;
case UmbracoObjectTypes.MediaType:
if (contentTypeId > 0)
{
source = Services.ContentTypeService.GetMediaType(contentTypeId);
if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
allContentTypes = Services.ContentTypeService.GetAllMediaTypes().Cast<IContentTypeComposition>().ToArray();
break;
case UmbracoObjectTypes.MemberType:
if (contentTypeId > 0)
{
source = Services.MemberTypeService.Get(contentTypeId);
if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
allContentTypes = Services.MemberTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
break;
default:
throw new ArgumentOutOfRangeException("The entity type was not a content type");
}
// 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 == 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(x =>
{
x.Name = TranslateItem(x.Name);
return x;
})
.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>
/// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors

View File

@@ -83,6 +83,11 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK);
}
public IEnumerable<EntityBasic> GetAvailableCompositeMediaTypes(int contentTypeId)
{
return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MediaType);
}
public ContentTypeCompositionDisplay GetEmpty(int parentId)
{
var ct = new MediaType(parentId);

View File

@@ -79,6 +79,11 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK);
}
public IEnumerable<EntityBasic> GetAvailableCompositeMemberTypes(int contentTypeId)
{
return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType);
}
public ContentTypeCompositionDisplay GetEmpty()
{
var ct = new MemberType(-1);

View File

@@ -18,8 +18,7 @@ namespace Umbraco.Web.Models.ContentEditing
//initialize collections so at least their never null
Groups = new List<PropertyGroupDisplay>();
AllowedContentTypes = new List<int>();
CompositeContentTypes = new List<string>();
AvailableCompositeContentTypes = new List<EntityBasic>();
CompositeContentTypes = new List<string>();
Notifications = new List<Notification>();
}
@@ -44,11 +43,7 @@ namespace Umbraco.Web.Models.ContentEditing
//Compositions
[DataMember(Name = "compositeContentTypes")]
public IEnumerable<string> CompositeContentTypes { get; set; }
[DataMember(Name = "availableCompositeContentTypes")]
[ReadOnly(true)]
public IEnumerable<EntityBasic> AvailableCompositeContentTypes { get; set; }
[DataMember(Name = "allowAsRoot")]
public bool AllowAsRoot { get; set; }

View File

@@ -1,118 +0,0 @@
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal class AvailableCompositeContentTypesResolver : ValueResolver<IContentTypeComposition, IEnumerable<EntityBasic>>
{
private readonly ApplicationContext _context;
internal AvailableCompositeContentTypesResolver(ApplicationContext context)
{
_context = context;
}
protected override IEnumerable<EntityBasic> ResolveCore(IContentTypeComposition source)
{
//below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
var s = source;
var type = _context.Services.EntityService.GetObjectType(source.Id);
var allContentTypes = new IContentTypeComposition[0];
switch (type)
{
case UmbracoObjectTypes.DocumentType:
allContentTypes = _context.Services.ContentTypeService.GetAllContentTypes().Cast<IContentTypeComposition>().ToArray();
break;
case UmbracoObjectTypes.MediaType:
allContentTypes = _context.Services.ContentTypeService.GetAllMediaTypes().Cast<IContentTypeComposition>().ToArray();
break;
case UmbracoObjectTypes.MemberType:
allContentTypes = _context.Services.MemberTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
break;
}
// 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<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 != source.Id)
.OrderBy(x => x.Name)
.Select(Mapper.Map<IContentTypeComposition, EntityBasic>)
.ToList();
}
private 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>();
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;
}
}
}

View File

@@ -56,7 +56,6 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dto => dto.CreateDate, expression => expression.Ignore())
.ForMember(dto => dto.UpdateDate, expression => expression.Ignore())
.ForMember(dto => dto.ListViewEditorName, expression => expression.Ignore())
.ForMember(dto => dto.AvailableCompositeContentTypes, expression => expression.Ignore())
.ForMember(dto => dto.Notifications, expression => expression.Ignore())
.ForMember(dto => dto.Errors, expression => expression.Ignore());
}
@@ -77,11 +76,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(
dto => dto.AllowedContentTypes,
expression => expression.MapFrom(dto => dto.AllowedContentTypes.Select(x => x.Id.Value)))
.ForMember(
dto => dto.AvailableCompositeContentTypes,
expression => expression.ResolveUsing(new AvailableCompositeContentTypesResolver(applicationContext)))
.ForMember(
dto => dto.CompositeContentTypes,
expression => expression.MapFrom(dto => dto.ContentTypeComposition))

View File

@@ -324,7 +324,6 @@
<Compile Include="Security\Identity\GetUserSecondsMiddleWare.cs" />
<Compile Include="Models\ContentEditing\PropertyTypeDisplay.cs" />
<Compile Include="Models\ContentEditing\PropertyGroupDisplay.cs" />
<Compile Include="Models\Mapping\AvailableCompositeContentTypesResolver.cs" />
<Compile Include="Media\EmbedProviders\OEmbedJson.cs" />
<Compile Include="Media\EmbedProviders\OEmbedResponse.cs" />
<Compile Include="Media\EmbedProviders\OEmbedPhoto.cs" />