From 4ce0e6bac3f394c7d6ab141fecf0ad6ea7091b90 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 9 Nov 2015 18:34:14 +0100 Subject: [PATCH 01/11] Starts fixing up the doc type folders stuff, fixes permissions settings on the trees, adds error messaging for duplicating folders, removes the click handler for folder nodes --- .../views/documenttypes/create.controller.js | 18 ++++++---- .../src/views/documenttypes/create.html | 5 +++ src/Umbraco.Web/Editors/ContentController.cs | 34 ++++++------------- .../Editors/ContentTypeController.cs | 6 ++-- .../Trees/ContentTypeTreeController.cs | 11 ++++-- .../Trees/MediaTypeTreeController.cs | 2 +- .../Trees/MemberTypeTreeController.cs | 2 +- .../WebApi/HttpRequestMessageExtensions.cs | 17 ++++++++++ 8 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 0c9d027646..4897ad3023 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -6,11 +6,11 @@ * @description * The controller for the doc type creation dialog */ -function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState) { +function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService) { $scope.model = { folderName: "", - creatingFolder: false + creatingFolder: false, }; var node = $scope.dialogOptions.currentNode; @@ -29,12 +29,18 @@ function DocumentTypesCreateController($scope, $location, navigationService, con formHelper.resetForm({ scope: $scope }); - var section = appState.getSectionState("currentSection"); - $location.path("/" + section + "/documenttypes/list/" + folderId); - + var section = appState.getSectionState("currentSection"); + }, function(err) { - //TODO: Handle errors + $scope.error = err; + + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } }); }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 59c8a3d7ec..a8e4086352 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -35,6 +35,11 @@
+ +
+
{{error.errorMsg}}
+

{{error.data.message}}

+
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index f745cd3d06..192b49e2cf 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -602,13 +602,9 @@ namespace Umbraco.Web.Editors //cannot move if the content item is not allowed at the root if (toMove.ContentType.AllowedAsRoot == false) { - var msg = Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"); - var notificationModel = new SimpleNotificationModel - { - Message = msg - }; - notificationModel.AddErrorNotification(msg, ""); - throw new HttpResponseException( Request.CreateValidationErrorResponse(notificationModel)); + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); } } else @@ -619,31 +615,21 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - - //check if the item is allowed under this one if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() .Any(x => x.Value == toMove.ContentType.Id) == false) { - var msg = Services.TextService.Localize("moveOrCopy/notAllowedByContentType"); - var notificationModel = new SimpleNotificationModel - { - Message = msg - }; - notificationModel.AddErrorNotification(msg, ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); } // Check on paths if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - var msg = Services.TextService.Localize("moveOrCopy/notAllowedByPath"); - var notificationModel = new SimpleNotificationModel - { - Message = msg - }; - notificationModel.AddErrorNotification(msg, ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); } } diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index aac243f727..6ca5eedff7 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -137,18 +137,16 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } - + public HttpResponseMessage PostCreateFolder(int parentId, string name) { var result = Services.ContentTypeService.CreateFolder(parentId, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id - : Request.CreateValidationErrorResponse(result.Exception.Message); + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); } - - public ContentTypeDisplay PostSave(ContentTypeSave contentTypeSave) { var savedCt = PerformPostSave( diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 2bb8df94c2..9a048471b8 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -15,7 +15,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.Trees { - [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] + [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 6)] [Umbraco.Web.Mvc.PluginController("UmbracoTrees")] [CoreTree] @@ -36,7 +36,8 @@ namespace Umbraco.Web.Trees var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); node.Path = dt.Path; node.NodeType = "container"; - + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; return node; })); @@ -45,7 +46,11 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false); + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", + //NOTE: This is legacy now but we need to support upgrades. From 7.4+ we don't allow 'child' creations since + // this is an organiational thing and we do that with folders now. + dt.HasChildren()); + node.Path = dt.Path; return node; })); diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 4b721d8e56..1ee5db6458 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -15,7 +15,7 @@ using Umbraco.Core.Services; namespace Umbraco.Web.Trees { - [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:5)] [Umbraco.Web.Mvc.PluginController("UmbracoTrees")] [CoreTree] diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 24f3722c15..a3422f367a 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -12,7 +12,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { - [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] + [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] [Tree(Constants.Applications.Members, Constants.Trees.MemberTypes, null, sortOrder:2 )] [Umbraco.Web.Mvc.PluginController("UmbracoTrees")] [CoreTree] diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index bd2290ce17..04035c0017 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -11,6 +11,7 @@ using System.Web.Http; using System.Web.Http.ModelBinding; using Microsoft.Owin; using Umbraco.Core; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi { @@ -107,6 +108,22 @@ namespace Umbraco.Web.WebApi return msg; } + /// + /// Creates an error response with notifications in the result to be displayed in the UI + /// + /// + /// + /// + public static HttpResponseMessage CreateNotificationValidationErrorResponse(this HttpRequestMessage request, string errorMessage) + { + var notificationModel = new SimpleNotificationModel + { + Message = errorMessage + }; + notificationModel.AddErrorNotification(errorMessage, string.Empty); + return request.CreateValidationErrorResponse(notificationModel); + } + /// /// Create a 400 response message indicating that a validation error occurred /// From f54cde050a968e728dd0487e3c99e0a8e91cc29f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Nov 2015 12:30:22 +0100 Subject: [PATCH 02/11] WIP - updated repositories to better support creating/deleting entity containers in a transaction, this is done by a child repository and a new EntityContainer type. Updated the controller to use this logic. --- src/Umbraco.Core/Constants-ObjectTypes.cs | 7 + src/Umbraco.Core/Models/EntityContainer.cs | 17 ++ src/Umbraco.Core/Models/UmbracoEntity.cs | 2 +- .../Repositories/ContentTypeBaseRepository.cs | 87 +++------- .../Repositories/ContentTypeRepository.cs | 21 +-- .../Repositories/EntityContainerRepository.cs | 152 ++++++++++++++++++ .../Interfaces/IContentTypeRepository.cs | 15 +- .../Interfaces/IMediaTypeRepository.cs | 15 ++ .../Repositories/MediaTypeRepository.cs | 21 +-- .../Repositories/MemberTypeRepository.cs | 14 +- .../Services/ContentTypeService.cs | 52 ++++++ .../Services/ContentTypeServiceBase.cs | 10 +- .../Services/IContentTypeService.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../common/resources/contenttype.resource.js | 8 +- .../common/resources/mediatype.resource.js | 2 +- .../views/documenttypes/create.controller.js | 4 +- .../src/views/mediatypes/create.controller.js | 4 +- .../views/membertypes/create.controller.js | 4 +- .../Editors/ContentTypeController.cs | 21 +-- 20 files changed, 322 insertions(+), 141 deletions(-) create mode 100644 src/Umbraco.Core/Models/EntityContainer.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index f737a68f02..272c7b6215 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -9,6 +9,11 @@ namespace Umbraco.Core /// public static class ObjectTypes { + /// + /// Guid for a member type container + /// + public const string MemberTypeContainer = "02348110-FC53-4565-9B01-0E186B6B9E7C"; + /// /// Guid for a doc type container /// @@ -102,6 +107,8 @@ namespace Umbraco.Core /// Guid for a Lock object. /// public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; + + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs new file mode 100644 index 0000000000..9c890fd594 --- /dev/null +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a folder for organizing entities such as content types and data types + /// + public sealed class EntityContainer : UmbracoEntity, IAggregateRoot + { + public EntityContainer(int parentId, string name, int userId) + { + ParentId = parentId; + Name = name; + CreatorId = userId; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 4975ad3bd2..70ef056068 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models /// /// Implementation of the for internal use. /// - internal class UmbracoEntity : Entity, IUmbracoEntity + public class UmbracoEntity : Entity, IUmbracoEntity { private int _creatorId; private int _level; diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index c75c061452..d25fab2cb7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -28,72 +28,35 @@ namespace Umbraco.Core.Persistence.Repositories where TEntity : class, IContentTypeComposition { - protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, + Guid containerType) : base(work, cache, logger, sqlSyntax) { _guidRepo = new GuidReadOnlyContentTypeBaseRepository(this, work, cache, logger, sqlSyntax); + _containerRepository = new EntityContainerRepository(work, cache, logger, sqlSyntax, containerType, NodeObjectTypeId); } + private readonly EntityContainerRepository _containerRepository; private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo; - + /// - /// The container object type - used for organizing content types + /// Deletes a folder - this will move all contained content types into their parent /// - protected abstract Guid ContainerObjectTypeId { get; } - - public Attempt CreateFolder(int parentId, string name, int userId) + /// + /// + /// Returns the content types moved + /// + public void DeleteFolder(int folderId) { - name = name.Trim(); + var found = _containerRepository.Get(folderId); + _containerRepository.Delete(found); + } - Mandate.ParameterNotNullOrEmpty(name, "name"); - - var exists = Database.FirstOrDefault( - new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.ParentId == parentId && dto.Text == name && dto.NodeObjectType == ContainerObjectTypeId)); - - if (exists != null) - { - return Attempt.Fail(exists.NodeId, new InvalidOperationException("A folder with the same name already exists")); - } - - var level = 0; - var path = "-1"; - if (parentId > -1) - { - var parent = Database.FirstOrDefault( - new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.NodeId == parentId && dto.NodeObjectType == ContainerObjectTypeId)); - - if (parent == null) - { - return Attempt.Fail(0, new NullReferenceException("No content type container found with parent id " + parentId)); - } - level = parent.Level; - path = parent.Path; - } - - var folder = new NodeDto - { - CreateDate = DateTime.Now, - Level = Convert.ToInt16(level + 1), - NodeObjectType = ContainerObjectTypeId, - ParentId = parentId, - Path = path, - SortOrder = 0, - Text = name, - Trashed = false, - UniqueId = Guid.NewGuid(), - UserId = userId - }; - - Database.Save(folder); - //update the path - folder.Path = folder.Path + "," + folder.NodeId; - Database.Save(folder); - - return Attempt.Succeed(folder.NodeId); + public EntityContainer CreateFolder(int parentId, string name, int userId) + { + var container = new EntityContainer(parentId, name, userId); + _containerRepository.AddOrUpdate(container); + return container; } /// @@ -105,15 +68,15 @@ namespace Umbraco.Core.Persistence.Repositories { var sqlClause = new Sql(); sqlClause.Select("*") - .From() - .RightJoin() - .On(left => left.Id, right => right.PropertyTypeGroupId) - .InnerJoin() - .On(left => left.DataTypeId, right => right.DataTypeId); + .From(SqlSyntax) + .RightJoin(SqlSyntax) + .On(SqlSyntax, left => left.Id, right => right.PropertyTypeGroupId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(x => x.PropertyTypeGroupId); + .OrderBy(x => x.PropertyTypeGroupId, SqlSyntax); var dtos = Database.Fetch(new GroupPropertyTypeRelator().Map, sql); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index ab22b66679..4145cb89b4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ITemplateRepository _templateRepository; public ContentTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, ITemplateRepository templateRepository) - : base(work, cache, logger, sqlSyntax) + : base(work, cache, logger, sqlSyntax, new Guid(Constants.ObjectTypes.DocumentTypeContainer)) { _templateRepository = templateRepository; } @@ -96,11 +96,11 @@ namespace Umbraco.Core.Persistence.Repositories var sql = new Sql(); sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .LeftJoin() - .On(left => left.ContentTypeNodeId, right => right.NodeId) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .LeftJoin(SqlSyntax) + .On(SqlSyntax ,left => left.ContentTypeNodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; @@ -241,14 +241,7 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion - - /// - /// The container object type - used for organizing content types - /// - protected override Guid ContainerObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.DocumentTypeContainer); } - } + protected override IContentType PerformGet(Guid id) { diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs new file mode 100644 index 0000000000..e1dbc3ef8f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// An internal repository for managing entity containers such as doc type, media type, data type containers + /// + /// + /// All we're supporting here is creating and deleting + /// + internal class EntityContainerRepository : PetaPocoRepositoryBase + { + private readonly Guid _containerObjectType; + private readonly Guid _entityObjectType; + + public EntityContainerRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, + Guid containerObjectType, Guid entityObjectType) + : base(work, cache, logger, sqlSyntax) + { + _containerObjectType = containerObjectType; + _entityObjectType = entityObjectType; + } + + protected override EntityContainer PerformGet(int id) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override Sql GetBaseQuery(bool isCount) + { + throw new NotImplementedException(); + } + + protected override string GetBaseWhereClause() + { + throw new NotImplementedException(); + } + + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + + protected override Guid NodeObjectTypeId + { + get { return _containerObjectType; } + } + + protected override void PersistDeletedItem(EntityContainer entity) + { + var exists = Database.FirstOrDefault( + new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == _containerObjectType)); + + if (exists == null) return; + + //We need to move the content types and folders that exist under this folder to it's parent folder + var children = Database.Fetch( + new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.ParentId == entity.Id && (dto.NodeObjectType == _entityObjectType || dto.NodeObjectType == _containerObjectType))); + + foreach (var childDto in children) + { + childDto.ParentId = exists.ParentId; + Database.Update(childDto); + } + + //now that everything is moved up a level, we need to delete the container + Database.Delete(exists); + } + + protected override void PersistNewItem(EntityContainer entity) + { + entity.Name = entity.Name.Trim(); + + Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); + + var exists = Database.FirstOrDefault( + new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == _containerObjectType)); + + if (exists != null) + { + throw new InvalidOperationException("A folder with the same name already exists"); + } + + var level = 0; + var path = "-1"; + if (entity.ParentId > -1) + { + var parent = Database.FirstOrDefault( + new Sql().Select("*") + .From(SqlSyntax) + .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == _containerObjectType)); + + if (parent == null) + { + throw new NullReferenceException("No content type container found with parent id " + entity.ParentId); + } + level = parent.Level; + path = parent.Path; + } + + var nodeDto = new NodeDto + { + CreateDate = DateTime.Now, + Level = Convert.ToInt16(level + 1), + NodeObjectType = _containerObjectType, + ParentId = entity.ParentId, + Path = path, + SortOrder = 0, + Text = entity.Name, + Trashed = false, + UniqueId = Guid.NewGuid(), + UserId = entity.CreatorId + }; + + Database.Save(nodeDto); + //update the path + nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; + Database.Save(nodeDto); + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(EntityContainer entity) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index 245552e422..3ea5a2a20d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -20,6 +20,19 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetAllPropertyTypeAliases(); - Attempt CreateFolder(int parentId, string name, int userId); + /// + /// Creates a folder for content types + /// + /// + /// + /// + /// + EntityContainer CreateFolder(int parentId, string name, int userId); + + /// + /// Deletes a folder - this will move all contained content types into their parent + /// + /// + void DeleteFolder(int folderId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index d28c59ac5b..f2b7593c5e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -13,5 +13,20 @@ namespace Umbraco.Core.Persistence.Repositories /// /// An enumerable list of objects IEnumerable GetByQuery(IQuery query); + + /// + /// Creates a folder for content types + /// + /// + /// + /// + /// + EntityContainer CreateFolder(int parentId, string name, int userId); + + /// + /// Deletes a folder - this will move all contained content types into their parent + /// + /// + void DeleteFolder(int folderId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 3bf70f1135..70423bb509 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories { public MediaTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) + : base(work, cache, logger, sqlSyntax, new Guid(Constants.ObjectTypes.MediaTypeContainer)) { } @@ -43,7 +43,7 @@ namespace Umbraco.Core.Persistence.Repositories } else { - var sql = new Sql().Select("id").From().Where(dto => dto.NodeObjectType == NodeObjectTypeId); + var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); var allIds = Database.Fetch(sql).ToArray(); return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); } @@ -54,7 +54,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(x => x.Text); + .OrderBy(x => x.Text, SqlSyntax); var dtos = Database.Fetch(sql); return dtos.Any() @@ -84,9 +84,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -165,14 +165,7 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion - - /// - /// The container object type - used for organizing content types - /// - protected override Guid ContainerObjectTypeId - { - get { throw new NotImplementedException(); } - } + protected override IMediaType PerformGet(Guid id) { diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 4d2fbe30de..275f976baa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories { public MemberTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) + : base(work, cache, logger, sqlSyntax, new Guid(Constants.ObjectTypes.MemberTypeContainer)) { } @@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.NodeId); + sql.OrderByDescending(x => x.NodeId, SqlSyntax); var dtos = Database.Fetch( @@ -240,15 +240,7 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion - - /// - /// The container object type - used for organizing content types - /// - protected override Guid ContainerObjectTypeId - { - get { throw new NotImplementedException(); } - } - + /// /// Override so we can specify explicit db type's on any property types that are built-in. /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index fde2b06d8d..2da2375d7f 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -41,6 +41,58 @@ namespace Umbraco.Core.Services _mediaService = mediaService; } + public Attempt CreateContentTypeFolder(int parentId, string name, int userId = 0) + { + using (var repo = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + { + try + { + var container = repo.CreateFolder(parentId, name, userId); + return Attempt.Succeed(container.Id); + } + catch (Exception ex) + { + return Attempt.Fail(ex); + } + //TODO: Audit trail ? + } + } + + public Attempt CreateMediaTypeFolder(int parentId, string name, int userId = 0) + { + using (var repo = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + { + try + { + var container = repo.CreateFolder(parentId, name, userId); + return Attempt.Succeed(container.Id); + } + catch (System.Exception ex) + { + return Attempt.Fail(ex); + } + //TODO: Audit trail ? + } + } + + public void DeleteContentTypeFolder(int folderId, int userId = 0) + { + using (var repo = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + { + repo.DeleteFolder(folderId); + //TODO: Audit trail ? + } + } + + public void DeleteMediaTypeFolder(int folderId, int userId = 0) + { + using (var repo = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + { + repo.DeleteFolder(folderId); + //TODO: Audit trail ? + } + } + /// /// Gets all property type aliases. /// diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs index c0f4a8cdf6..df29012b90 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -15,15 +15,7 @@ namespace Umbraco.Core.Services : base(provider, repositoryFactory, logger, eventMessagesFactory) { } - - public Attempt CreateFolder(int parentId, string name, int userId = 0) - { - using (var repo = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) - { - return repo.CreateFolder(parentId, name, userId); - } - } - + /// /// This is called after an content type is saved and is used to update the content xml structures in the database /// if they are required to be updated. diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index a68a5f7a6f..597dc5ac65 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -17,7 +17,10 @@ namespace Umbraco.Core.Services /// Attempt ValidateComposition(IContentTypeComposition compo); - Attempt CreateFolder(int parentId, string name, int userId = 0); + Attempt CreateContentTypeFolder(int parentId, string name, int userId = 0); + Attempt CreateMediaTypeFolder(int parentId, string name, int userId = 0); + void DeleteMediaTypeFolder(int folderId, int userId = 0); + void DeleteContentTypeFolder(int folderId, int userId = 0); /// /// Gets all property type aliases. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 499106c9d3..a1682e1449 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -357,6 +357,7 @@ + @@ -443,6 +444,7 @@ + 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 e8b2a2e623..0ba60042bf 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 @@ -109,7 +109,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { $http.post( umbRequestHelper.getApiUrl( "contentTypeApiBaseUrl", - "DeleteContainerById", + "DeleteContainer", [{ id: id }])), 'Failed to delete content type contaier'); }, @@ -166,14 +166,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to save data for content type id ' + contentType.id); }, - createFolder: function(parentId, name) { + createContainer: function(parentId, name) { return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateFolder", { parentId: parentId, name: name })), + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateContainer", { parentId: parentId, name: name })), 'Failed to create a folder under parent id ' + parentId); } - + }; } angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource); 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 93e7fefa2b..d1c2f0888b 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 @@ -88,7 +88,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to save data for content type id ' + contentType.id); }, - createFolder: function(parentId, name) { + createContainer: function(parentId, name) { return umbRequestHelper.resourcePromise( $http.post( diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 4897ad3023..a62024e704 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -19,9 +19,9 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.model.creatingFolder = true; } - $scope.createFolder = function () { + $scope.createContainer = function () { if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { - contentTypeResource.createFolder(node.id, $scope.model.folderName).then(function (folderId) { + contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { navigationService.hideMenu(); var currPath = node.path ? node.path : "-1"; diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.controller.js index d6303ea264..44f615ac7f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.controller.js @@ -19,9 +19,9 @@ function MediaTypesCreateController($scope, $location, navigationService, mediaT $scope.model.creatingFolder = true; } - $scope.createFolder = function () { + $scope.createContainer = function () { if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { - mediaTypeResource.createFolder(node.id, $scope.model.folderName).then(function (folderId) { + mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { navigationService.hideMenu(); var currPath = node.path ? node.path : "-1"; diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/create.controller.js index e696bd45d6..2e2d274fe1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/create.controller.js @@ -19,9 +19,9 @@ function MemberTypesCreateController($scope, $location, navigationService, membe $scope.model.creatingFolder = true; } - $scope.createFolder = function () { + $scope.createContainer = function () { if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { - memberTypeResource.createFolder(node.id, $scope.model.folderName).then(function (folderId) { + memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { navigationService.hideMenu(); var currPath = node.path ? node.path : "-1"; diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 6ca5eedff7..d09cfea42c 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -118,29 +118,16 @@ namespace Umbraco.Web.Editors /// [HttpDelete] [HttpPost] - public HttpResponseMessage DeleteContainerById(int id) + public HttpResponseMessage DeleteContainer(int id) { - //TODO: This needs to be implemented correctly - - var foundType = Services.EntityService.Get(id); - if (foundType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - if (foundType.HasChildren()) - { - throw new HttpResponseException(HttpStatusCode.Forbidden); - } - - //TODO: what service to use to delete? + Services.ContentTypeService.DeleteContentTypeFolder(id, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } - public HttpResponseMessage PostCreateFolder(int parentId, string name) + public HttpResponseMessage PostCreateContainer(int parentId, string name) { - var result = Services.ContentTypeService.CreateFolder(parentId, name, Security.CurrentUser.Id); + var result = Services.ContentTypeService.CreateContentTypeFolder(parentId, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id From b4755fc5c490109a3c3e44425afae22c93eb0ca0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Nov 2015 16:14:03 +0100 Subject: [PATCH 03/11] Gets more doc type folders working, can create and delete, now to get doc types to be moved. --- src/Umbraco.Core/Models/EntityContainer.cs | 8 ++- .../Repositories/ContentTypeBaseRepository.cs | 7 ++- .../Repositories/EntityContainerRepository.cs | 52 ++++++++++++++++--- .../Repositories/ExternalLoginRepository.cs | 2 +- .../Services/ContentTypeService.cs | 16 ++++-- .../src/views/documenttypes/create.html | 2 +- .../src/views/documenttypes/delete.html | 5 +- 7 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 9c890fd594..5c4deef037 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -7,11 +7,17 @@ namespace Umbraco.Core.Models /// public sealed class EntityContainer : UmbracoEntity, IAggregateRoot { - public EntityContainer(int parentId, string name, int userId) + public EntityContainer() + { + } + + public EntityContainer(int id, int parentId, string name, int userId, string path) { + Id = id; ParentId = parentId; Name = name; CreatorId = userId; + Path = path; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index d25fab2cb7..126510ec0d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -54,7 +54,12 @@ namespace Umbraco.Core.Persistence.Repositories public EntityContainer CreateFolder(int parentId, string name, int userId) { - var container = new EntityContainer(parentId, name, userId); + var container = new EntityContainer + { + ParentId = parentId, + Name = name, + CreatorId = userId + }; _containerRepository.AddOrUpdate(container); return container; } diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index e1dbc3ef8f..2ac407e7ba 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -14,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories /// An internal repository for managing entity containers such as doc type, media type, data type containers /// /// - /// All we're supporting here is creating and deleting + /// All we're supporting here is a single get, creating and deleting /// internal class EntityContainerRepository : PetaPocoRepositoryBase { @@ -29,9 +32,32 @@ namespace Umbraco.Core.Persistence.Repositories _entityObjectType = entityObjectType; } + /// + /// Do not cache anything + /// + protected override IRuntimeCacheProvider RuntimeCache + { + get { return new NullCacheProvider(); } + } + protected override EntityContainer PerformGet(int id) { - throw new NotImplementedException(); + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id, NodeObjectType = _containerObjectType }); + + var containerDto = Database.Fetch(sql).FirstOrDefault(); + if (containerDto == null) + return null; + + var entity = new EntityContainer(containerDto.NodeId, + containerDto.ParentId, containerDto.Text, containerDto.UserId ?? 0, + containerDto.Path); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + + return entity; } protected override IEnumerable PerformGetAll(params int[] ids) @@ -46,12 +72,21 @@ namespace Umbraco.Core.Persistence.Repositories protected override Sql GetBaseQuery(bool isCount) { - throw new NotImplementedException(); + var sql = new Sql(); + if (isCount) + { + sql.Select("COUNT(*)").From(SqlSyntax); + } + else + { + sql.Select("*").From(SqlSyntax); + } + return sql; } protected override string GetBaseWhereClause() { - throw new NotImplementedException(); + return "umbracoNode.id = @Id and nodeObjectType = @NodeObjectType"; } protected override IEnumerable GetDeleteClauses() @@ -77,7 +112,8 @@ namespace Umbraco.Core.Persistence.Repositories var children = Database.Fetch( new Sql().Select("*") .From(SqlSyntax) - .Where(dto => dto.ParentId == entity.Id && (dto.NodeObjectType == _entityObjectType || dto.NodeObjectType == _containerObjectType))); + .Where("parentID=@parentID AND (nodeObjectType=@entityObjectType OR nodeObjectType=@containerObjectType)", + new {parentID = entity.ParentId, entityObjectType = _entityObjectType, containerObjectType = _containerObjectType})); foreach (var childDto in children) { @@ -136,11 +172,15 @@ namespace Umbraco.Core.Persistence.Repositories UserId = entity.CreatorId }; - Database.Save(nodeDto); + var id = Convert.ToInt32(Database.Insert(nodeDto)); + //update the path nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; Database.Save(nodeDto); + entity.Id = id; + entity.Path = nodeDto.Path; + entity.ResetDirtyProperties(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 97c6e4fcf5..276f4b0f89 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -67,7 +67,7 @@ namespace Umbraco.Core.Persistence.Repositories //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); + entity.ResetDirtyProperties(false); return entity; } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 2da2375d7f..38acfbe4ff 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -43,11 +43,13 @@ namespace Umbraco.Core.Services public Attempt CreateContentTypeFolder(int parentId, string name, int userId = 0) { - using (var repo = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateContentTypeRepository(uow)) { try { var container = repo.CreateFolder(parentId, name, userId); + uow.Commit(); return Attempt.Succeed(container.Id); } catch (Exception ex) @@ -60,11 +62,13 @@ namespace Umbraco.Core.Services public Attempt CreateMediaTypeFolder(int parentId, string name, int userId = 0) { - using (var repo = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateMediaTypeRepository(uow)) { try { var container = repo.CreateFolder(parentId, name, userId); + uow.Commit(); return Attempt.Succeed(container.Id); } catch (System.Exception ex) @@ -77,18 +81,22 @@ namespace Umbraco.Core.Services public void DeleteContentTypeFolder(int folderId, int userId = 0) { - using (var repo = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateContentTypeRepository(uow)) { repo.DeleteFolder(folderId); + uow.Commit(); //TODO: Audit trail ? } } public void DeleteMediaTypeFolder(int folderId, int userId = 0) { - using (var repo = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateMediaTypeRepository(uow)) { repo.DeleteFolder(folderId); + uow.Commit(); //TODO: Audit trail ? } } diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index a8e4086352..5f147a08ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -33,7 +33,7 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html index 5c522dcbec..175e3a1fda 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html @@ -3,12 +3,13 @@

- Are you sure you want to delete {{currentNode.name}} ? + Are you sure you want to delete + {{currentNode.name}} ?

-

This action cannot be undone, click ok to delete.

+

Any item that exists in this folder will be moved to the parent folder

Date: Wed, 11 Nov 2015 13:25:02 +0100 Subject: [PATCH 04/11] Gets creating/deleting media type folders working. --- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 50 ++++++++++++------- .../common/resources/mediatype.resource.js | 13 ++++- .../src/views/mediatypes/create.html | 28 +++++------ .../src/views/mediatypes/delete.controller.js | 4 +- .../Editors/MediaTypeController.cs | 23 +++++++++ .../Trees/ContentTypeTreeController.cs | 4 +- .../Trees/MediaTypeTreeController.cs | 14 +++--- .../Trees/MemberTypeTreeController.cs | 3 +- 8 files changed, 96 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 8f9acbd057..3c159555e1 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -15,49 +15,49 @@ namespace Umbraco.Core.Models /// /// Content Item Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.ContentItemType)] + [UmbracoObjectType(Constants.ObjectTypes.ContentItemType)] [FriendlyName("Content Item Type")] ContentItemType, /// /// Root /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.SystemRoot)] + [UmbracoObjectType(Constants.ObjectTypes.SystemRoot)] [FriendlyName("Root")] ROOT, /// /// Document /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Document, typeof(IContent))] + [UmbracoObjectType(Constants.ObjectTypes.Document, typeof(IContent))] [FriendlyName("Document")] Document, /// /// Media /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Media, typeof(IMedia))] + [UmbracoObjectType(Constants.ObjectTypes.Media, typeof(IMedia))] [FriendlyName("Media")] Media, /// /// Member Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.MemberType, typeof(IMemberType))] + [UmbracoObjectType(Constants.ObjectTypes.MemberType, typeof(IMemberType))] [FriendlyName("Member Type")] MemberType, /// /// Template /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Template, typeof(ITemplate))] + [UmbracoObjectType(Constants.ObjectTypes.Template, typeof(ITemplate))] [FriendlyName("Template")] Template, /// /// Member Group /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.MemberGroup)] + [UmbracoObjectType(Constants.ObjectTypes.MemberGroup)] [FriendlyName("Member Group")] MemberGroup, @@ -65,57 +65,73 @@ namespace Umbraco.Core.Models /// /// Content Item /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.ContentItem)] + [UmbracoObjectType(Constants.ObjectTypes.ContentItem)] [FriendlyName("Content Item")] ContentItem, /// /// "Media Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.MediaType, typeof(IMediaType))] + [UmbracoObjectType(Constants.ObjectTypes.MediaType, typeof(IMediaType))] [FriendlyName("Media Type")] MediaType, /// /// Document Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.DocumentType, typeof(IContentType))] + [UmbracoObjectType(Constants.ObjectTypes.DocumentType, typeof(IContentType))] [FriendlyName("Document Type")] DocumentType, /// /// Recycle Bin /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.ContentRecycleBin)] + [UmbracoObjectType(Constants.ObjectTypes.ContentRecycleBin)] [FriendlyName("Recycle Bin")] RecycleBin, /// /// Stylesheet /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Stylesheet)] + [UmbracoObjectType(Constants.ObjectTypes.Stylesheet)] [FriendlyName("Stylesheet")] Stylesheet, /// /// Member /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.Member, typeof(IMember))] + [UmbracoObjectType(Constants.ObjectTypes.Member, typeof(IMember))] [FriendlyName("Member")] Member, /// /// Data Type /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.DataType, typeof(IDataTypeDefinition))] + [UmbracoObjectType(Constants.ObjectTypes.DataType, typeof(IDataTypeDefinition))] [FriendlyName("Data Type")] DataType, /// - /// Entity Container + /// Document type container /// - [UmbracoObjectTypeAttribute(Constants.ObjectTypes.DocumentTypeContainer)] + [UmbracoObjectType(Constants.ObjectTypes.DocumentTypeContainer)] [FriendlyName("Document Type Container")] - DocumentTypeContainer + DocumentTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.MediaTypeContainer)] + [FriendlyName("Media Type Container")] + MediaTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.MemberTypeContainer)] + [FriendlyName("Member Type Container")] + MemberTypeContainer + + } } \ No newline at end of file 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 d1c2f0888b..d483777250 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 @@ -79,6 +79,17 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to retrieve content type'); }, + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + save: function (contentType) { var saveModel = umbDataFormatter.formatContentTypePostData(contentType); @@ -94,7 +105,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { $http.post( umbRequestHelper.getApiUrl( "mediaTypeApiBaseUrl", - "PostCreateFolder", + "PostCreateContainer", { parentId: parentId, name: name })), 'Failed to create a folder under parent id ' + parentId); } diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html index dfef9e315a..f0b526f2e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html @@ -3,7 +3,19 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.controller.js index f8cf19a6ac..785550684b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the media type delete dialog */ -function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, contentTypeResource, treeService, navigationService) { +function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, treeService, navigationService) { $scope.performDelete = function() { @@ -29,7 +29,7 @@ function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, //mark it for deletion (used in the UI) $scope.currentNode.loading = true; - contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () { $scope.currentNode.loading = false; //get the root node before we remove it diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index fbdaa77e5f..9a8a2214d4 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -102,6 +102,29 @@ namespace Umbraco.Web.Editors .Select(Mapper.Map); } + /// + /// Deletes a document type container wth a given ID + /// + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteContainer(int id) + { + Services.ContentTypeService.DeleteMediaTypeFolder(id, Security.CurrentUser.Id); + + return Request.CreateResponse(HttpStatusCode.OK); + } + + public HttpResponseMessage PostCreateContainer(int parentId, string name) + { + var result = Services.ContentTypeService.CreateMediaTypeFolder(parentId, name, Security.CurrentUser.Id); + + return result + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id + : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); + } + public ContentTypeCompositionDisplay PostSave(ContentTypeSave contentTypeSave) { var savedCt = PerformPostSave( diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 9a048471b8..159bc76d0e 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -17,8 +17,9 @@ namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 6)] - [Umbraco.Web.Mvc.PluginController("UmbracoTrees")] + [Mvc.PluginController("UmbracoTrees")] [CoreTree] + [LegacyBaseTree(typeof(loadNodeTypes))] public class ContentTypeTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -83,7 +84,6 @@ namespace Umbraco.Web.Trees // root actions menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - if (container.HasChildren() == false) { //can delete doc type diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 1ee5db6458..68605f3e75 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -17,8 +17,9 @@ namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:5)] - [Umbraco.Web.Mvc.PluginController("UmbracoTrees")] + [Mvc.PluginController("UmbracoTrees")] [CoreTree] + [LegacyBaseTree(typeof(loadMediaTypes))] public class MediaTypeTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -28,15 +29,16 @@ namespace Umbraco.Web.Trees var nodes = new TreeNodeCollection(); - //TODO: MediaTypeContainers nodes.AddRange( - Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DocumentTypeContainer) + Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.MediaTypeContainer) .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), - queryStrings.GetValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + dt.Id); + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); node.Path = dt.Path; + node.NodeType = "container"; + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; return node; })); @@ -71,7 +73,7 @@ namespace Umbraco.Web.Trees } else { - var container = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DocumentTypeContainer); + var container = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.MediaTypeContainer); if (container != null) { //set the default to create diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index a3422f367a..d739c2a661 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -14,8 +14,9 @@ namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] [Tree(Constants.Applications.Members, Constants.Trees.MemberTypes, null, sortOrder:2 )] - [Umbraco.Web.Mvc.PluginController("UmbracoTrees")] + [Mvc.PluginController("UmbracoTrees")] [CoreTree] + [LegacyBaseTree(typeof(loadMemberTypes))] public class MemberTypeTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) From 42a0697d608f4ad38fc0ca4140f0c82f7ce89dd0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 11 Nov 2015 15:46:29 +0100 Subject: [PATCH 05/11] Nearly got data type folders creating --- src/Umbraco.Core/Constants-ObjectTypes.cs | 4 +- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 6 +- .../Repositories/ContentTypeBaseRepository.cs | 24 ++++--- .../DataTypeDefinitionRepository.cs | 26 +++++++- .../Interfaces/IContentTypeRepository.cs | 4 +- .../IDataTypeDefinitionRepository.cs | 3 + .../Interfaces/IMediaTypeRepository.cs | 4 +- .../Repositories/MemberTypeRepository.cs | 2 +- .../Services/ContentTypeService.cs | 16 ++--- src/Umbraco.Core/Services/DataTypeService.cs | 30 +++++++++ .../Services/IContentTypeService.cs | 8 +-- src/Umbraco.Core/Services/IDataTypeService.cs | 3 + .../src/common/resources/datatype.resource.js | 22 +++++++ .../src/views/datatypes/create.controller.js | 48 ++++++++++++++ .../src/views/datatypes/create.html | 50 ++++++++++++++ .../src/views/documenttypes/create.html | 2 +- .../src/views/mediatypes/create.html | 2 +- .../Editors/ContentTypeController.cs | 4 +- src/Umbraco.Web/Editors/DataTypeController.cs | 23 ++++++- .../Editors/MediaTypeController.cs | 4 +- .../Trees/ContentTypeTreeController.cs | 42 ++++++------ .../Trees/DataTypeTreeController.cs | 66 ++++++++++++++----- .../Trees/MediaTypeTreeController.cs | 32 +++++---- 23 files changed, 332 insertions(+), 93 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/datatypes/create.html diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 272c7b6215..09915dc936 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -10,9 +10,9 @@ namespace Umbraco.Core public static class ObjectTypes { /// - /// Guid for a member type container + /// Guid for a data type container /// - public const string MemberTypeContainer = "02348110-FC53-4565-9B01-0E186B6B9E7C"; + public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; /// /// Guid for a doc type container diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 3c159555e1..2be2010301 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -128,9 +128,9 @@ namespace Umbraco.Core.Models /// /// Media type container /// - [UmbracoObjectType(Constants.ObjectTypes.MemberTypeContainer)] - [FriendlyName("Member Type Container")] - MemberTypeContainer + [UmbracoObjectType(Constants.ObjectTypes.DataTypeContainer)] + [FriendlyName("Data Type Container")] + DataTypeContainer } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 126510ec0d..00c3419741 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -36,24 +36,32 @@ namespace Umbraco.Core.Persistence.Repositories _containerRepository = new EntityContainerRepository(work, cache, logger, sqlSyntax, containerType, NodeObjectTypeId); } + protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _guidRepo = new GuidReadOnlyContentTypeBaseRepository(this, work, cache, logger, sqlSyntax); + _containerRepository = null; + } + private readonly EntityContainerRepository _containerRepository; private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo; - + /// - /// Deletes a folder - this will move all contained content types into their parent + /// Deletes a folder - this will move all contained entities into their parent /// - /// - /// - /// Returns the content types moved - /// - public void DeleteFolder(int folderId) + /// + public void DeleteContainer(int folderId) { + if (_containerRepository == null) throw new NotSupportedException("The repository type " + GetType() + " does not support containers"); + var found = _containerRepository.Get(folderId); _containerRepository.Delete(found); } - public EntityContainer CreateFolder(int parentId, string name, int userId) + public EntityContainer CreateContainer(int parentId, string name, int userId) { + if (_containerRepository == null) throw new NotSupportedException("The repository type " + GetType() + " does not support containers"); + var container = new EntityContainer { ParentId = parentId, diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index c1ad88af3d..1e123cc4dd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -27,6 +27,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly CacheHelper _cacheHelper; private readonly IContentTypeRepository _contentTypeRepository; private readonly DataTypePreValueRepository _preValRepository; + private readonly EntityContainerRepository _containerRepository; public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cache, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentTypeRepository contentTypeRepository) @@ -35,10 +36,31 @@ namespace Umbraco.Core.Persistence.Repositories _cacheHelper = cacheHelper; _contentTypeRepository = contentTypeRepository; _preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - + _containerRepository = new EntityContainerRepository(work, cache, logger, sqlSyntax, new Guid(Constants.ObjectTypes.DataTypeContainer), NodeObjectTypeId); + } + + /// + /// Deletes a folder - this will move all contained entities into their parent + /// + /// + public void DeleteContainer(int folderId) + { + var found = _containerRepository.Get(folderId); + _containerRepository.Delete(found); + } + + public EntityContainer CreateContainer(int parentId, string name, int userId) + { + var container = new EntityContainer + { + ParentId = parentId, + Name = name, + CreatorId = userId + }; + _containerRepository.AddOrUpdate(container); + return container; } - #region Overrides of RepositoryBase protected override IDataTypeDefinition PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index 3ea5a2a20d..20bf82151c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -27,12 +27,12 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - EntityContainer CreateFolder(int parentId, string name, int userId); + EntityContainer CreateContainer(int parentId, string name, int userId); /// /// Deletes a folder - this will move all contained content types into their parent /// /// - void DeleteFolder(int folderId); + void DeleteContainer(int folderId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs index f04b90c2e0..e357bc11f3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs @@ -6,6 +6,9 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IDataTypeDefinitionRepository : IRepositoryQueryable { + EntityContainer CreateContainer(int parentId, string name, int userId); + void DeleteContainer(int folderId); + PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId); string GetPreValueAsString(int preValueId); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index f2b7593c5e..c39494d606 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -21,12 +21,12 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - EntityContainer CreateFolder(int parentId, string name, int userId); + EntityContainer CreateContainer(int parentId, string name, int userId); /// /// Deletes a folder - this will move all contained content types into their parent /// /// - void DeleteFolder(int folderId); + void DeleteContainer(int folderId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 275f976baa..5b4c1ff877 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories { public MemberTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax, new Guid(Constants.ObjectTypes.MemberTypeContainer)) + : base(work, cache, logger, sqlSyntax) { } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 38acfbe4ff..89b4fc8324 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -41,14 +41,14 @@ namespace Umbraco.Core.Services _mediaService = mediaService; } - public Attempt CreateContentTypeFolder(int parentId, string name, int userId = 0) + public Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0) { var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateContentTypeRepository(uow)) { try { - var container = repo.CreateFolder(parentId, name, userId); + var container = repo.CreateContainer(parentId, name, userId); uow.Commit(); return Attempt.Succeed(container.Id); } @@ -60,14 +60,14 @@ namespace Umbraco.Core.Services } } - public Attempt CreateMediaTypeFolder(int parentId, string name, int userId = 0) + public Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0) { var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateMediaTypeRepository(uow)) { try { - var container = repo.CreateFolder(parentId, name, userId); + var container = repo.CreateContainer(parentId, name, userId); uow.Commit(); return Attempt.Succeed(container.Id); } @@ -79,23 +79,23 @@ namespace Umbraco.Core.Services } } - public void DeleteContentTypeFolder(int folderId, int userId = 0) + public void DeleteContentTypeContainer(int folderId, int userId = 0) { var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateContentTypeRepository(uow)) { - repo.DeleteFolder(folderId); + repo.DeleteContainer(folderId); uow.Commit(); //TODO: Audit trail ? } } - public void DeleteMediaTypeFolder(int folderId, int userId = 0) + public void DeleteMediaTypeContainer(int folderId, int userId = 0) { var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateMediaTypeRepository(uow)) { - repo.DeleteFolder(folderId); + repo.DeleteContainer(folderId); uow.Commit(); //TODO: Audit trail ? } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index f6dd53020c..78e03c8a78 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -26,6 +26,36 @@ namespace Umbraco.Core.Services { } + public Attempt CreateContainer(int parentId, string name, int userId = 0) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + try + { + var container = repo.CreateContainer(parentId, name, userId); + uow.Commit(); + return Attempt.Succeed(container.Id); + } + catch (Exception ex) + { + return Attempt.Fail(ex); + } + //TODO: Audit trail ? + } + } + + public void DeleteContainer(int folderId, int userId = 0) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + repo.DeleteContainer(folderId); + uow.Commit(); + //TODO: Audit trail ? + } + } + /// /// Gets a by its Name /// diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 597dc5ac65..4807765562 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -17,10 +17,10 @@ namespace Umbraco.Core.Services /// Attempt ValidateComposition(IContentTypeComposition compo); - Attempt CreateContentTypeFolder(int parentId, string name, int userId = 0); - Attempt CreateMediaTypeFolder(int parentId, string name, int userId = 0); - void DeleteMediaTypeFolder(int folderId, int userId = 0); - void DeleteContentTypeFolder(int folderId, int userId = 0); + Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0); + Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0); + void DeleteMediaTypeContainer(int folderId, int userId = 0); + void DeleteContentTypeContainer(int folderId, int userId = 0); /// /// Gets all property type aliases. diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index a31be9de5c..29173fd0d0 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -10,6 +10,9 @@ namespace Umbraco.Core.Services /// public interface IDataTypeService : IService { + Attempt CreateContainer(int parentId, string name, int userId = 0); + void DeleteContainer(int folderId, int userId = 0); + /// /// Gets a by its Name /// diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index a1263a6126..48abd8443e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -220,6 +220,17 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { "Failed to delete item " + id); }, + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete content type contaier'); + }, + /** @@ -303,6 +314,17 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), "Failed to save data for data type id " + dataType.id); + }, + + createContainer: function (parentId, name) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "dataTypeApiBaseUrl", + "PostCreateContainer", + { parentId: parentId, name: name })), + 'Failed to create a folder under parent id ' + parentId); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js new file mode 100644 index 0000000000..e57deb6145 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js @@ -0,0 +1,48 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DataType.CreateController + * @function + * + * @description + * The controller for the data type creation dialog + */ +function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) { + + $scope.model = { + folderName: "", + creatingFolder: false + }; + + var node = $scope.dialogOptions.currentNode; + + $scope.showCreateFolder = function() { + $scope.model.creatingFolder = true; + } + + $scope.createContainer = function () { + if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { + dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + + navigationService.hideMenu(); + var currPath = node.path ? node.path : "-1"; + navigationService.syncTree({ tree: "datatypes", path: currPath + "," + folderId, forceReload: true, activate: true }); + + formHelper.resetForm({ scope: $scope }); + + var section = appState.getSectionState("currentSection"); + + }, function(err) { + + //TODO: Handle errors + }); + }; + } + + $scope.createDataType = function() { + $location.search('create', null); + $location.path("/settings/datatype/edit/" + node.id).search("create", "true"); + navigationService.hideMenu(); + } +} + +angular.module('umbraco').controller("Umbraco.Editors.DataType.CreateController", DataTypeCreateController); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html new file mode 100644 index 0000000000..677fd5aa89 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html @@ -0,0 +1,50 @@ + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 5f147a08ca..3539c84cbe 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -1,7 +1,7 @@ /// void DeleteContainer(int folderId); + + IEnumerable> Move(IMediaType toMove, int parentId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 70423bb509..c4fa80a7d5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -10,6 +12,7 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -23,9 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories : base(work, cache, logger, sqlSyntax, new Guid(Constants.ObjectTypes.MediaTypeContainer)) { } - - #region Overrides of RepositoryBase - + protected override IMediaType PerformGet(int id) { var contentTypes = ContentTypeQueryMapper.GetMediaTypes( @@ -61,10 +62,7 @@ namespace Umbraco.Core.Persistence.Repositories ? GetAll(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - - + /// /// Gets all entities of the specified query /// @@ -76,10 +74,8 @@ namespace Umbraco.Core.Persistence.Repositories return ints.Any() ? GetAll(ints) : Enumerable.Empty(); - } - - #region Overrides of PetaPocoRepositoryBase - + } + protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); @@ -119,11 +115,7 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.MediaType); } } - - #endregion - - #region Unit of Work Implementation - + protected override void PersistNewItem(IMediaType entity) { ((MediaType)entity).AddingEntity(); @@ -163,9 +155,6 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - - #endregion - protected override IMediaType PerformGet(Guid id) { diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 89b4fc8324..eccdae1ab8 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -712,6 +712,76 @@ namespace Umbraco.Core.Services } } + public Attempt> MoveMediaType(IMediaType toMove, int parentId) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (MovingMediaType.IsRaisedEventCancelled( + new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, parentId)), + this)) + { + return Attempt.Fail( + new OperationStatus( + MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var moveInfo = new List>(); + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + { + try + { + moveInfo.AddRange(repository.Move(toMove, parentId)); + } + catch (DataOperationException ex) + { + return Attempt.Fail( + new OperationStatus(ex.Operation, evtMsgs)); + } + uow.Commit(); + } + + MovedMediaType.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); + + return Attempt.Succeed( + new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); + } + + public Attempt> MoveContentType(IContentType toMove, int parentId) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (MovingContentType.IsRaisedEventCancelled( + new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, parentId)), + this)) + { + return Attempt.Fail( + new OperationStatus( + MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var moveInfo = new List>(); + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentTypeRepository(uow)) + { + try + { + moveInfo.AddRange(repository.Move(toMove, parentId)); + } + catch (DataOperationException ex) + { + return Attempt.Fail( + new OperationStatus(ex.Operation, evtMsgs)); + } + uow.Commit(); + } + + MovedContentType.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); + + return Attempt.Succeed( + new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); + } + /// /// Saves a single object /// @@ -950,6 +1020,26 @@ namespace Umbraco.Core.Services ///
public static event TypedEventHandler> SavedMediaType; + /// + /// Occurs before Move + /// + public static event TypedEventHandler> MovingMediaType; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> MovedMediaType; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> MovingContentType; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> MovedContentType; + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 4807765562..6be2174fdf 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Services { @@ -261,5 +262,8 @@ namespace Umbraco.Core.Services /// Id of the /// True if the media type has any children otherwise False bool MediaTypeHasChildren(Guid id); + + Attempt> MoveMediaType(IMediaType toMove, int parentId); + Attempt> MoveContentType(IContentType toMove, int parentId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MoveOperationStatusType.cs b/src/Umbraco.Core/Services/MoveOperationStatusType.cs new file mode 100644 index 0000000000..d1a3439cab --- /dev/null +++ b/src/Umbraco.Core/Services/MoveOperationStatusType.cs @@ -0,0 +1,32 @@ +namespace Umbraco.Core.Services +{ + + /// + /// A status type of the result of moving an item + /// + /// + /// Anything less than 10 = Success! + /// + public enum MoveOperationStatusType + { + /// + /// The move was successful. + /// + Success = 0, + + /// + /// The parent being moved to doesn't exist + /// + FailedParentNotFound = 13, + + /// + /// The move action has been cancelled by an event handler + /// + FailedCancelledByEvent = 14, + + /// + /// Trying to move an item to an invalid path (i.e. a child of itself) + /// + FailedNotAllowedByPath = 15, + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a1682e1449..2340d4d0ee 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -336,6 +336,7 @@ + @@ -498,6 +499,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index e20e2867da..d5870810f8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -95,6 +95,49 @@ namespace Umbraco.Tests.Persistence.Repositories } + [Test] + public void Can_Move() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + var container2 = repository.CreateContainer(container.Id, "blah2", 0); + unitOfWork.Commit(); + + var contentType = (IContentType) MockedContentTypes.CreateBasicContentType("asdfasdf"); + contentType.ParentId = container2.Id; + repository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + //create a + var contentType2 = (IContentType)new ContentType(contentType, "hello") + { + Name = "Blahasdfsadf" + }; + contentType.ParentId = contentType.Id; + repository.AddOrUpdate(contentType2); + unitOfWork.Commit(); + + var result = repository.Move(contentType, container.Id).ToArray(); + unitOfWork.Commit(); + + Assert.AreEqual(2, result.Count()); + + //re-get + contentType = repository.Get(contentType.Id); + contentType2 = repository.Get(contentType2.Id); + + Assert.AreEqual(container.Id, contentType.ParentId); + Assert.AreNotEqual(result.Single(x => x.Entity.Id == contentType.Id).OriginalPath, contentType.Path); + Assert.AreNotEqual(result.Single(x => x.Entity.Id == contentType2.Id).OriginalPath, contentType2.Path); + } + + } + [Test] public void Can_Create_Container() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index 97b3db96f8..82a6578ebb 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -31,6 +31,49 @@ namespace Umbraco.Tests.Persistence.Repositories return new MediaTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); } + [Test] + public void Can_Move() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + var container2 = repository.CreateContainer(container.Id, "blah2", 0); + unitOfWork.Commit(); + + var contentType = (IMediaType)MockedContentTypes.CreateVideoMediaType(); + contentType.ParentId = container2.Id; + repository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + //create a + var contentType2 = (IMediaType)new MediaType(contentType, "hello") + { + Name = "Blahasdfsadf" + }; + contentType.ParentId = contentType.Id; + repository.AddOrUpdate(contentType2); + unitOfWork.Commit(); + + var result = repository.Move(contentType, container.Id).ToArray(); + unitOfWork.Commit(); + + Assert.AreEqual(2, result.Count()); + + //re-get + contentType = repository.Get(contentType.Id); + contentType2 = repository.Get(contentType2.Id); + + Assert.AreEqual(container.Id, contentType.ParentId); + Assert.AreNotEqual(result.Single(x => x.Entity.Id == contentType.Id).OriginalPath, contentType.Path); + Assert.AreNotEqual(result.Single(x => x.Entity.Id == contentType2.Id).OriginalPath, contentType2.Path); + } + + } + [Test] public void Can_Create_Container() { 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 0ba60042bf..da4a736cd2 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 @@ -166,6 +166,49 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to save data for content type id ' + contentType.id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#move + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+         * contentTypeResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
+         *      alert("node didnt move:" + err.data.Message); 
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + createContainer: function(parentId, name) { return umbRequestHelper.resourcePromise( 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 d483777250..dcdcb7de88 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 @@ -99,6 +99,49 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to save data for content type id ' + contentType.id); }, + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#move + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+         * mediaTypeResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
+         *      alert("node didnt move:" + err.data.Message); 
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + createContainer: function(parentId, name) { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/views/content/move.html b/src/Umbraco.Web.UI.Client/src/views/content/move.html index 96e76d4dc4..b64511ec22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/move.html @@ -3,7 +3,7 @@

- Choose where to move {{currentNode.name}} to in the tree structure below + Choose where to move {{currentNode.name}} to in the tree structure below

@@ -16,7 +16,7 @@
-
{{currentNode.name}} was moved underneath{{target.name}}
+
{{currentNode.name}} was moved underneath {{target.name}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.controller.js index 99af24e051..3cf3634371 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.controller.js @@ -1,5 +1,66 @@ angular.module("umbraco") .controller("Umbraco.Editors.DocumentTypes.MoveController", - function($scope){ + function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + contentTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "documentTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "documentTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html index 7ddde2f2fd..506db13d06 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html @@ -1,11 +1,50 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.controller.js index f18a5c1b33..a8ce1e461a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.controller.js @@ -1,5 +1,67 @@ angular.module("umbraco") .controller("Umbraco.Editors.MediaTypes.MoveController", - function($scope){ + function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + mediaTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "mediaTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "mediaTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html index d6a4dc49b4..22b6d14d62 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html @@ -1,11 +1,51 @@
- +
+
-

- Select the folder to move {{currentNode.name}} to. -

+

+ Select the folder to move {{currentNode.name}} to in the tree structure below +

+
+
+
+
+
{{error.errorMsg}}
+

{{error.data.message}}

+
+ +
+
{{currentNode.name}} was moved underneath {{target.name}}
+ +
+ +
+ +
+ + +
+ +
+
+ + +
diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 8b84dbc450..8179edce2d 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -243,6 +243,19 @@ namespace Umbraco.Web.Editors return basics; } + /// + /// Move the media type + /// + /// + /// + public HttpResponseMessage PostMove(MoveOrCopy move) + { + return PerformMove( + move, + getContentType: i => Services.ContentTypeService.GetContentType(i), + doMove: (type, i) => Services.ContentTypeService.MoveContentType(type, i)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 6f3f96d365..fd229e6c47 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Text; using System.Text.RegularExpressions; using System.Web.Http; using AutoMapper; @@ -215,6 +216,50 @@ namespace Umbraco.Web.Editors } return newCt; } + } + + /// + /// Change the sort order for media + /// + /// + /// + /// + /// + protected HttpResponseMessage PerformMove( + MoveOrCopy move, + Func getContentType, + Func>> doMove) + where TContentType : IContentTypeComposition + { + var toMove = getContentType(move.Id); + if (toMove == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + var result = doMove(toMove, move.ParentId); + if (result.Success) + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + switch (result.Result.StatusType) + { + case MoveOperationStatusType.FailedParentNotFound: + return Request.CreateResponse(HttpStatusCode.NotFound); + case MoveOperationStatusType.FailedCancelledByEvent: + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + case MoveOperationStatusType.FailedNotAllowedByPath: + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + return Request.CreateValidationErrorResponse(notificationModel); + default: + throw new ArgumentOutOfRangeException(); + } } private ICultureDictionary CultureDictionary diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 05e861cce2..b212555ebe 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -14,6 +14,7 @@ using System.Net; using Umbraco.Core.PropertyEditors; using System; using System.Net.Http; +using System.Text; using Umbraco.Web.WebApi; using ContentType = System.Net.Mime.ContentType; using Umbraco.Core.Services; @@ -186,5 +187,19 @@ namespace Umbraco.Web.Editors return basics; } + + /// + /// Move the media type + /// + /// + /// + public HttpResponseMessage PostMove(MoveOrCopy move) + { + return PerformMove( + move, + getContentType: i => Services.ContentTypeService.GetMediaType(i), + doMove: (type, i) => Services.ContentTypeService.MoveMediaType(type, i)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 3bb6c1dfd2..43cf60a178 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -42,6 +42,9 @@ namespace Umbraco.Web.Trees return node; })); + //if the request is for folders only then just return + if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + nodes.AddRange( Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DocumentType) .OrderBy(entity => entity.Name) @@ -80,25 +83,19 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - // root actions menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - + if (container.HasChildren() == false) { //can delete doc type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); } - - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias))); - - + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); } else { - //delete doc type - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias))); menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); } return menu; diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index ee12562fc5..00640f8410 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -97,15 +97,14 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - // root actions menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias))); if (container.HasChildren() == false) { //can delete data type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - } + } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); } else { @@ -113,8 +112,8 @@ namespace Umbraco.Web.Trees if (sysIds.Contains(int.Parse(id)) == false) { - //only have delete for each node - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); } } diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 77414a1b36..a7da33898d 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -42,6 +42,9 @@ namespace Umbraco.Web.Trees return node; })); + //if the request is for folders only then just return + if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + nodes.AddRange( Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.MediaType) .OrderBy(entity => entity.Name) @@ -55,8 +58,6 @@ namespace Umbraco.Web.Trees return nodes; } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { var menu = new MenuItemCollection(); @@ -78,20 +79,19 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - // root actions menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias))); - + if (container.HasChildren() == false) { //can delete doc type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - } + } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); } else - { - //delete doc type + { menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); } return menu; From 50e2bd5d7e61f23006e9256c5dd3a7d4307669d7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 12 Nov 2015 18:58:29 +0100 Subject: [PATCH 09/11] Gets data type folders working, this includes updating the data type repo + unit test, the move dialog/views, the data type tree to just show folders. --- .../DataTypeDefinitionRepository.cs | 45 +++++++++++++ .../IDataTypeDefinitionRepository.cs | 2 + src/Umbraco.Core/Services/DataTypeService.cs | 48 +++++++++++++- src/Umbraco.Core/Services/IDataTypeService.cs | 2 + .../DataTypeDefinitionRepositoryTest.cs | 45 +++++++++++++ .../src/common/resources/datatype.resource.js | 43 ++++++++++++ .../src/views/datatypes/move.controller.js | 66 +++++++++++++++++++ .../src/views/datatypes/move.html | 50 ++++++++++++++ src/Umbraco.Web/Editors/DataTypeController.cs | 39 +++++++++++ .../Trees/DataTypeTreeController.cs | 3 + 10 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/datatypes/move.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/datatypes/move.html diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index f826a55354..a72582acae 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -353,6 +355,49 @@ AND umbracoNode.id <> @id", AddOrUpdatePreValues(dtd, values); } + public IEnumerable> Move(IDataTypeDefinition toMove, int parentId) + { + if (parentId > 0) + { + var container = _containerRepository.Get(parentId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + + // Check on paths + if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); + } + } + + //used to track all the moved entities to be given to the event + var moveInfo = new List> + { + new MoveEventInfo(toMove, toMove.Path, parentId) + }; + + //do the move to a new parent + toMove.ParentId = parentId; + //schedule it for updating in the transaction + AddOrUpdate(toMove); + + //update all descendants + var descendants = this.GetByQuery( + new Query().Where(type => type.Path.StartsWith(toMove.Path + ","))); + foreach (var descendant in descendants) + { + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + + //all we're doing here is setting the parent Id to be dirty so that it resets the path/level/etc... + descendant.ParentId = descendant.ParentId + 1; + descendant.ParentId = descendant.ParentId - 1; + //schedule it for updating in the transaction + AddOrUpdate(descendant); + } + + return moveInfo; + } + public void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values) { var currentVals = new DataTypePreValueDto[] { }; diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs index 219f97f0c5..2ae9dfe551 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Persistence.UnitOfWork; @@ -14,5 +15,6 @@ namespace Umbraco.Core.Persistence.Repositories void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values); void AddOrUpdatePreValues(int dataTypeId, IDictionary values); + IEnumerable> Move(IDataTypeDefinition toMove, int parentId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 810276f9c3..4def86836a 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.PropertyEditors; using umbraco.interfaces; +using Umbraco.Core.Exceptions; namespace Umbraco.Core.Services { @@ -183,6 +184,41 @@ namespace Umbraco.Core.Services } } + public Attempt> Move(IDataTypeDefinition toMove, int parentId) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (Moving.IsRaisedEventCancelled( + new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, parentId)), + this)) + { + return Attempt.Fail( + new OperationStatus( + MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var moveInfo = new List>(); + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + try + { + moveInfo.AddRange(repository.Move(toMove, parentId)); + } + catch (DataOperationException ex) + { + return Attempt.Fail( + new OperationStatus(ex.Operation, evtMsgs)); + } + uow.Commit(); + } + + Moved.RaiseEvent(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); + + return Attempt.Succeed( + new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); + } + /// /// Saves an /// @@ -431,8 +467,18 @@ namespace Umbraco.Core.Services /// Occurs after Save /// public static event TypedEventHandler> Saved; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; #endregion - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index fbbb4f9066..6a9137030b 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -154,5 +154,7 @@ namespace Umbraco.Core.Services /// Id of the PreValue to retrieve the value from /// PreValue as a string string GetPreValueAsString(int id); + + Attempt> Move(IDataTypeDefinition toMove, int parentId); } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 01db3a3d2c..850a25fd32 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Persistence.Repositories { @@ -58,6 +59,50 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(outcome, Regex.IsMatch(cacheKey, DataTypeDefinitionRepository.GetCacheKeyRegex(preValueId))); } + [Test] + public void Can_Move() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + var container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + var container2 = repository.CreateContainer(container.Id, "blah2", 0); + unitOfWork.Commit(); + + var dataType = (IDataTypeDefinition) new DataTypeDefinition(container2.Id, Constants.PropertyEditors.RadioButtonListAlias) + { + Name = "dt1" + }; + repository.AddOrUpdate(dataType); + unitOfWork.Commit(); + + //create a + var dataType2 = (IDataTypeDefinition)new DataTypeDefinition(dataType.Id, Constants.PropertyEditors.RadioButtonListAlias) + { + Name = "dt2" + }; + repository.AddOrUpdate(dataType2); + unitOfWork.Commit(); + + var result = repository.Move(dataType, container.Id).ToArray(); + unitOfWork.Commit(); + + Assert.AreEqual(2, result.Count()); + + //re-get + dataType = repository.Get(dataType.Id); + dataType2 = repository.Get(dataType2.Id); + + Assert.AreEqual(container.Id, dataType.ParentId); + Assert.AreNotEqual(result.Single(x => x.Entity.Id == dataType.Id).OriginalPath, dataType.Path); + Assert.AreNotEqual(result.Single(x => x.Entity.Id == dataType2.Id).OriginalPath, dataType2.Path); + } + + } + [Test] public void Can_Create_Container() { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 5cc1e4fec4..678e8547d5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -316,6 +316,49 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { "Failed to save data for data type id " + dataType.id); }, + /** + * @ngdoc method + * @name umbraco.resources.dataTypeResource#move + * @methodOf umbraco.resources.dataTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+         * dataTypeResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
+         *      alert("node didnt move:" + err.data.Message); 
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + createContainer: function (parentId, name) { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.controller.js new file mode 100644 index 0000000000..580c08fd1a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.controller.js @@ -0,0 +1,66 @@ +angular.module("umbraco") +.controller("Umbraco.Editors.DataType.MoveController", + function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + dataTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "dataTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "dataTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html new file mode 100644 index 0000000000..9725c7ec5f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html @@ -0,0 +1,50 @@ +
+ +
+
+ +

+ Select the folder to move {{currentNode.name}} to in the tree structure below +

+ +
+
+
+ +
+
{{error.errorMsg}}
+

{{error.data.message}}

+
+ +
+
{{currentNode.name}} was moved underneath {{target.name}}
+ +
+ +
+ +
+ + +
+ +
+
+
+ + +
diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 25f5994c06..f0554501b5 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -19,6 +19,7 @@ using Umbraco.Web.WebApi.Filters; using umbraco; using Constants = Umbraco.Core.Constants; using System.Net.Http; +using System.Text; namespace Umbraco.Web.Editors { @@ -228,6 +229,44 @@ namespace Umbraco.Web.Editors return display; } + /// + /// Move the media type + /// + /// + /// + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = Services.DataTypeService.GetDataTypeDefinitionById(move.Id); + if (toMove == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + var result = Services.DataTypeService.Move(toMove, move.ParentId); + if (result.Success) + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + switch (result.Result.StatusType) + { + case MoveOperationStatusType.FailedParentNotFound: + return Request.CreateResponse(HttpStatusCode.NotFound); + case MoveOperationStatusType.FailedCancelledByEvent: + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + case MoveOperationStatusType.FailedNotAllowedByPath: + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + return Request.CreateValidationErrorResponse(notificationModel); + default: + throw new ArgumentOutOfRangeException(); + } + } + #region ReadOnly actions to return basic data - allow access for: content ,media, members, settings, developer /// /// Gets the content json for all data types diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 00640f8410..4d8f74a74a 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -45,6 +45,9 @@ namespace Umbraco.Web.Trees return node; })); + //if the request is for folders only then just return + if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + //Normal nodes var sysIds = GetSystemIds(); From 8653da010ec02be85cc7c74a796438a38c3311af Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 13 Nov 2015 15:03:57 +0100 Subject: [PATCH 10/11] Remove warning on container delete, cannot delete a non-empty container --- src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html | 2 -- src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html | 2 -- src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html index d303f8aab4..fe75a54982 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html @@ -7,8 +7,6 @@
-

Any item that exists in this folder will be moved to the parent folder

- diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html index 175e3a1fda..68a193c3ce 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html @@ -9,8 +9,6 @@
-

Any item that exists in this folder will be moved to the parent folder

- diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html index 700775dbe7..c051cb5a1e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html @@ -7,8 +7,6 @@
-

Any item that exists in this folder will be moved to the parent folder

- From 9654a0aa3acf359f6c898b66dc91bc896d44c031 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 13 Nov 2015 15:04:14 +0100 Subject: [PATCH 11/11] Enable moving system datatypes (but not deleting) in tree --- src/Umbraco.Web/Trees/DataTypeTreeController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 4d8f74a74a..67da0cf355 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -116,8 +116,9 @@ namespace Umbraco.Web.Trees if (sysIds.Contains(int.Parse(id)) == false) { menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); } + + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); } return menu;