diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index da4a736cd2..4b1ed8f9c1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -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; * }); * - * @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); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index dcdcb7de88..555889e5a3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -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 diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js index b2d1e8d7ed..d949844a6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js @@ -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) { diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index e33b592e76..1307b854f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -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); diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.controller.js index 71a9786995..061ca7eb2c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.controller.js @@ -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(); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html index 2b4dd8518c..bfd5e46f70 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html @@ -5,33 +5,20 @@ Are you sure you want to delete {{currentNode.name}} ?

- -
-

This action cannot be undone, click ok to delete.

+

+ All members + using this member type will be deleted permanently, please confirm you want to delete these as well. +

- - -
+
-
-

- All members - using this member type will be deleted permanently, please confirm you want to delete these as well. -

+ -
- - - - - -
-
+ + diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 8179edce2d..aea6690bf2 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -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. /// /// An API controller used for dealing with content types @@ -85,11 +86,24 @@ namespace Umbraco.Web.Editors /// Gets all user defined properties. /// /// + [UmbracoTreeAuthorize( + Constants.Trees.DocumentTypes, Constants.Trees.Content, + Constants.Trees.MediaTypes, Constants.Trees.Media, + Constants.Trees.MemberTypes, Constants.Trees.Members)] public IEnumerable GetAllPropertyTypeAliases() { return ApplicationContext.Services.ContentTypeService.GetAllPropertyTypeAliases(); } + public IEnumerable 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); diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index fd229e6c47..443b1dcbff 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -46,37 +46,129 @@ namespace Umbraco.Web.Editors { } - protected internal DataTypeBasic GetAssignedListViewDataType(int contentTypeId) + /// + /// Returns the available composite content types for a given content type + /// + /// + protected IEnumerable 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( - Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Member")) - : Mapper.Map(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( - Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Media")) - : Mapper.Map(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( - Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + "Content")) - : Mapper.Map(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().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().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().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(); + } + + // if it is not used then composition is possible + // hashset guarantees unicity on Id + var list = new HashSet(new DelegateEqualityComparer( + (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) + .Select(x => + { + x.Name = TranslateItem(x.Name); + return x; + }) + .ToList(); + } + + private static IEnumerable GetIndirect(IContentTypeComposition ctype) + { + // hashset guarantees unicity on Id + var all = new HashSet(new DelegateEqualityComparer( + (x, y) => x.Id == y.Id, + x => x.Id)); + + var stack = new Stack(); + + 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; + } /// /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index b212555ebe..d54981ff45 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -83,6 +83,11 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } + public IEnumerable GetAvailableCompositeMediaTypes(int contentTypeId) + { + return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MediaType); + } + public ContentTypeCompositionDisplay GetEmpty(int parentId) { var ct = new MediaType(parentId); diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index 19a89cd963..7716f6ea00 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -79,6 +79,11 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } + public IEnumerable GetAvailableCompositeMemberTypes(int contentTypeId) + { + return PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType); + } + public ContentTypeCompositionDisplay GetEmpty() { var ct = new MemberType(-1); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs index 7a3869d9b1..9a138dd828 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs @@ -18,8 +18,7 @@ namespace Umbraco.Web.Models.ContentEditing //initialize collections so at least their never null Groups = new List(); AllowedContentTypes = new List(); - CompositeContentTypes = new List(); - AvailableCompositeContentTypes = new List(); + CompositeContentTypes = new List(); Notifications = new List(); } @@ -44,11 +43,7 @@ namespace Umbraco.Web.Models.ContentEditing //Compositions [DataMember(Name = "compositeContentTypes")] public IEnumerable CompositeContentTypes { get; set; } - - [DataMember(Name = "availableCompositeContentTypes")] - [ReadOnly(true)] - public IEnumerable AvailableCompositeContentTypes { get; set; } - + [DataMember(Name = "allowAsRoot")] public bool AllowAsRoot { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/AvailableCompositeContentTypesResolver.cs b/src/Umbraco.Web/Models/Mapping/AvailableCompositeContentTypesResolver.cs deleted file mode 100644 index 9cc415d27a..0000000000 --- a/src/Umbraco.Web/Models/Mapping/AvailableCompositeContentTypesResolver.cs +++ /dev/null @@ -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> - { - private readonly ApplicationContext _context; - - internal AvailableCompositeContentTypesResolver(ApplicationContext context) - { - _context = context; - } - - protected override IEnumerable 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().ToArray(); - break; - - case UmbracoObjectTypes.MediaType: - allContentTypes = _context.Services.ContentTypeService.GetAllMediaTypes().Cast().ToArray(); - break; - - case UmbracoObjectTypes.MemberType: - allContentTypes = _context.Services.MemberTypeService.GetAll().Cast().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(); - } - - // if it is not used then composition is possible - // hashset guarantees unicity on Id - var list = new HashSet(new DelegateEqualityComparer( - (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) - .ToList(); - } - - - private IEnumerable GetIndirect(IContentTypeComposition ctype) - { - // hashset guarantees unicity on Id - var all = new HashSet(new DelegateEqualityComparer( - (x, y) => x.Id == y.Id, - x => x.Id)); - - var stack = new Stack(); - - 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; - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs index 9178d69d8f..e59a474afb 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs @@ -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)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9e340bd47f..0594a5c98e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -324,7 +324,6 @@ -