diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index f737a68f02..09915dc936 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 data type container + /// + public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; + /// /// 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/Exceptions/DataOperationException.cs b/src/Umbraco.Core/Exceptions/DataOperationException.cs new file mode 100644 index 0000000000..9a66e6a5be --- /dev/null +++ b/src/Umbraco.Core/Exceptions/DataOperationException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + internal class DataOperationException : Exception + { + public T Operation { get; private set; } + + public DataOperationException(T operation, string message) + :base(message) + { + Operation = operation; + } + + public DataOperationException(T operation) + : base("Data operation exception: " + operation) + { + Operation = operation; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 5d5b9490a5..a2ca15e46a 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -49,8 +49,6 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - [Obsolete("Don't use this, parentId is always -1 for data types")] - [EditorBrowsable(EditorBrowsableState.Never)] public DataTypeDefinition(int parentId, string propertyEditorAlias) { _parentId = parentId; diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs new file mode 100644 index 0000000000..5c4deef037 --- /dev/null +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -0,0 +1,23 @@ +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() + { + } + + 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/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/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 8f9acbd057..2be2010301 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.DataTypeContainer)] + [FriendlyName("Data Type Container")] + DataTypeContainer + + } } \ 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 c75c061452..2de5d0c641 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -6,6 +6,8 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -16,6 +18,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -28,72 +31,90 @@ namespace Umbraco.Core.Persistence.Repositories where TEntity : class, IContentTypeComposition { + 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); + } + 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; } + protected EntityContainerRepository ContainerRepository { get; private set; } private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo; - /// - /// The container object type - used for organizing content types - /// - protected abstract Guid ContainerObjectTypeId { get; } - - public Attempt CreateFolder(int parentId, string name, int userId) + public IEnumerable> Move(TEntity toMove, int parentId) { - name = name.Trim(); - - 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) + if (parentId > 0) { - return Attempt.Fail(exists.NodeId, new InvalidOperationException("A folder with the same name already exists")); - } + var container = ContainerRepository.Get(parentId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - 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) + // Check on paths + if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) { - return Attempt.Fail(0, new NullReferenceException("No content type container found with parent id " + parentId)); + throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); } - level = parent.Level; - path = parent.Path; } - var folder = new NodeDto + //used to track all the moved entities to be given to the event + var moveInfo = new List> { - 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 + new MoveEventInfo(toMove, toMove.Path, parentId) }; - Database.Save(folder); - //update the path - folder.Path = folder.Path + "," + folder.NodeId; - Database.Save(folder); + //do the move to a new parent + toMove.ParentId = parentId; + //schedule it for updating in the transaction + AddOrUpdate(toMove); - return Attempt.Succeed(folder.NodeId); + //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; + //schedule it for updating in the transaction + AddOrUpdate(descendant); + } + + return moveInfo; + } + + /// + /// Deletes a folder - this will move all contained entities into their parent + /// + /// + public void DeleteContainer(int containerId) + { + if (ContainerRepository == null) throw new NotSupportedException("The repository type " + GetType() + " does not support containers"); + + var found = ContainerRepository.Get(containerId); + ContainerRepository.Delete(found); + } + + 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, + Name = name, + CreatorId = userId + }; + ContainerRepository.AddOrUpdate(container); + return container; } /// @@ -105,15 +126,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..04b3e46e3a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.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; @@ -11,6 +13,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -22,13 +25,11 @@ 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; - } + } - #region Overrides of RepositoryBase - protected override IContentType PerformGet(int id) { var contentTypes = ContentTypeQueryMapper.GetContentTypes( @@ -46,7 +47,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.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } @@ -57,16 +58,14 @@ 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() ? GetAll(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query /// @@ -88,19 +87,17 @@ namespace Umbraco.Core.Persistence.Repositories { return Database.Fetch("SELECT DISTINCT Alias FROM cmsPropertyType ORDER BY Alias"); } - - #region Overrides of PetaPocoRepositoryBase - + protected override Sql GetBaseQuery(bool isCount) { 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; @@ -135,11 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.DocumentType); } } - - #endregion - - #region Unit of Work Implementation - + /// /// Deletes a content type /// @@ -239,16 +232,6 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - - #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/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index c1ad88af3d..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; @@ -27,6 +29,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 +38,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 containerId) + { + var found = _containerRepository.Get(containerId); + _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) @@ -331,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/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs new file mode 100644 index 0000000000..033b5dc6e0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -0,0 +1,192 @@ +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; + +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 a single get, 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; + } + + /// + /// Do not cache anything + /// + protected override IRuntimeCacheProvider RuntimeCache + { + get { return new NullCacheProvider(); } + } + + protected override EntityContainer PerformGet(int id) + { + 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) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + if (isCount) + { + sql.Select("COUNT(*)").From(SqlSyntax); + } + else + { + sql.Select("*").From(SqlSyntax); + } + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id and nodeObjectType = @NodeObjectType"; + } + + 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("parentID=@parentID AND (nodeObjectType=@entityObjectType OR nodeObjectType=@containerObjectType)", + new {parentID = entity.Id, entityObjectType = _entityObjectType, containerObjectType = _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 + }; + + 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(); + } + + protected override void PersistUpdatedItem(EntityContainer entity) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file 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/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index 245552e422..9a90af21d6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; @@ -20,6 +21,21 @@ namespace Umbraco.Core.Persistence.Repositories /// IEnumerable GetAllPropertyTypeAliases(); - Attempt CreateFolder(int parentId, string name, int userId); + /// + /// Creates a folder for content types + /// + /// + /// + /// + /// + EntityContainer CreateContainer(int parentId, string name, int userId); + + /// + /// Deletes a folder - this will move all contained content types into their parent + /// + /// + void DeleteContainer(int containerId); + + IEnumerable> Move(IContentType toMove, int parentId); } } \ 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..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; @@ -6,10 +7,14 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IDataTypeDefinitionRepository : IRepositoryQueryable { + EntityContainer CreateContainer(int parentId, string name, int userId); + void DeleteContainer(int containerId); + PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId); string GetPreValueAsString(int preValueId); 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/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index d28c59ac5b..17073d18b3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; @@ -13,5 +14,22 @@ namespace Umbraco.Core.Persistence.Repositories /// /// An enumerable list of objects IEnumerable GetByQuery(IQuery query); + + /// + /// Creates a folder for content types + /// + /// + /// + /// + /// + EntityContainer CreateContainer(int parentId, string name, int userId); + + /// + /// Deletes a folder - this will move all contained content types into their parent + /// + /// + 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 3bf70f1135..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 { @@ -20,12 +23,10 @@ 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)) { } - - #region Overrides of RepositoryBase - + protected override IMediaType PerformGet(int id) { var contentTypes = ContentTypeQueryMapper.GetMediaTypes( @@ -43,7 +44,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,17 +55,14 @@ 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() ? GetAll(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - - + /// /// Gets all entities of the specified query /// @@ -76,17 +74,15 @@ 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(); 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; } @@ -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,16 +155,6 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - - #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..5b4c1ff877 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -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..eccdae1ab8 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -41,6 +41,66 @@ namespace Umbraco.Core.Services _mediaService = mediaService; } + public Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateContentTypeRepository(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 Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateMediaTypeRepository(uow)) + { + try + { + var container = repo.CreateContainer(parentId, name, userId); + uow.Commit(); + return Attempt.Succeed(container.Id); + } + catch (System.Exception ex) + { + return Attempt.Fail(ex); + } + //TODO: Audit trail ? + } + } + + public void DeleteContentTypeContainer(int folderId, int userId = 0) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateContentTypeRepository(uow)) + { + repo.DeleteContainer(folderId); + uow.Commit(); + //TODO: Audit trail ? + } + } + + public void DeleteMediaTypeContainer(int folderId, int userId = 0) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateMediaTypeRepository(uow)) + { + repo.DeleteContainer(folderId); + uow.Commit(); + //TODO: Audit trail ? + } + } + /// /// Gets all property type aliases. /// @@ -652,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 /// @@ -890,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/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/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index f6dd53020c..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 { @@ -26,6 +27,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 containerId, int userId = 0) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repo = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + repo.DeleteContainer(containerId); + uow.Commit(); + //TODO: Audit trail ? + } + } + /// /// Gets a by its Name /// @@ -153,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 /// @@ -401,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/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index a68a5f7a6f..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 { @@ -17,7 +18,10 @@ namespace Umbraco.Core.Services /// Attempt ValidateComposition(IContentTypeComposition compo); - Attempt CreateFolder(int parentId, string name, 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. @@ -258,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/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index a31be9de5c..6a9137030b 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 containerId, int userId = 0); + /// /// Gets a by its Name /// @@ -151,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.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 d41b994b49..7f1caba3a3 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -336,6 +336,7 @@ + @@ -357,6 +358,7 @@ + @@ -444,6 +446,7 @@ + @@ -497,6 +500,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 37137bcaed..d5870810f8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -95,6 +95,151 @@ 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() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + Assert.That(container.Id, Is.GreaterThan(0)); + } + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.DocumentTypeContainer), new Guid(Constants.ObjectTypes.DocumentType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNotNull(found); + } + } + + [Test] + public void Can_Delete_Container() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + } + using (var repository = CreateRepository(unitOfWork)) + { + // Act + repository.DeleteContainer(container.Id); + unitOfWork.Commit(); + + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.DocumentTypeContainer), new Guid(Constants.ObjectTypes.DocumentType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNull(found); + } + } + } + + [Test] + public void Can_Create_Container_Containing_Media_Types() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test", propertyGroupName: "testGroup"); + contentType.ParentId = container.Id; + repository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + Assert.AreEqual(container.Id, contentType.ParentId); + } + } + + [Test] + public void Can_Delete_Container_Containing_Media_Types() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + IContentType contentType; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + contentType = MockedContentTypes.CreateSimpleContentType("test", "Test", propertyGroupName: "testGroup"); + contentType.ParentId = container.Id; + repository.AddOrUpdate(contentType); + unitOfWork.Commit(); + } + using (var repository = CreateRepository(unitOfWork)) + { + // Act + repository.DeleteContainer(container.Id); + unitOfWork.Commit(); + + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.DocumentTypeContainer), new Guid(Constants.ObjectTypes.DocumentType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNull(found); + } + + contentType = repository.Get(contentType.Id); + Assert.IsNotNull(contentType); + Assert.AreEqual(-1, contentType.ParentId); + } + } + [Test] public void Can_Perform_Add_On_ContentTypeRepository() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 74ec6f9dea..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,150 @@ 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() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + Assert.That(container.Id, Is.GreaterThan(0)); + } + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.DataTypeContainer), new Guid(Constants.ObjectTypes.DataType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNotNull(found); + } + } + + [Test] + public void Can_Delete_Container() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + } + using (var repository = CreateRepository(unitOfWork)) + { + // Act + repository.DeleteContainer(container.Id); + unitOfWork.Commit(); + + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.DataTypeContainer), new Guid(Constants.ObjectTypes.DataType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNull(found); + } + } + } + + [Test] + public void Can_Create_Container_Containing_Data_Types() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + var dataTypeDefinition = new DataTypeDefinition(container.Id, Constants.PropertyEditors.RadioButtonListAlias) { Name = "test" }; + repository.AddOrUpdate(dataTypeDefinition); + unitOfWork.Commit(); + + Assert.AreEqual(container.Id, dataTypeDefinition.ParentId); + } + } + + [Test] + public void Can_Delete_Container_Containing_Data_Types() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + IDataTypeDefinition dataType; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + dataType = new DataTypeDefinition(container.Id, Constants.PropertyEditors.RadioButtonListAlias) { Name = "test" }; + repository.AddOrUpdate(dataType); + unitOfWork.Commit(); + } + using (var repository = CreateRepository(unitOfWork)) + { + // Act + repository.DeleteContainer(container.Id); + unitOfWork.Commit(); + + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.DataTypeContainer), new Guid(Constants.ObjectTypes.DataType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNull(found); + } + + dataType = repository.Get(dataType.Id); + Assert.IsNotNull(dataType); + Assert.AreEqual(-1, dataType.ParentId); + } + } + [Test] public void Can_Create() { @@ -85,39 +230,6 @@ namespace Umbraco.Tests.Persistence.Repositories } } - [Test] - public void Cannot_Create_Duplicate_Name() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - var id = 0; - using (var repository = CreateRepository(unitOfWork)) - { - var dataTypeDefinition = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; - repository.AddOrUpdate(dataTypeDefinition); - unitOfWork.Commit(); - id = dataTypeDefinition.Id; - Assert.That(id, Is.GreaterThan(0)); - } - using (var repository = CreateRepository(unitOfWork)) - { - var dataTypeDefinition = repository.Get(id); - Assert.IsNotNull(dataTypeDefinition); - Assert.AreEqual("test", dataTypeDefinition.Name); - } - using (var repository = CreateRepository(unitOfWork)) - { - var dataTypeDefinition = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; - repository.AddOrUpdate(dataTypeDefinition); - - unitOfWork.Commit(); - Assert.AreNotEqual(0, dataTypeDefinition.Id); // has been saved - Assert.AreNotEqual(id, dataTypeDefinition.Id); // as a new one - Assert.AreEqual("test (1)", dataTypeDefinition.Name); // with a new name - } - } - - [Test] public void Can_Perform_Get_On_DataTypeDefinitionRepository() diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index d0d66c5479..82a6578ebb 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -31,6 +31,151 @@ 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() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + Assert.That(container.Id, Is.GreaterThan(0)); + } + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.MediaTypeContainer), new Guid(Constants.ObjectTypes.MediaType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNotNull(found); + } + } + + [Test] + public void Can_Delete_Container() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + } + using (var repository = CreateRepository(unitOfWork)) + { + // Act + repository.DeleteContainer(container.Id); + unitOfWork.Commit(); + + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.MediaTypeContainer), new Guid(Constants.ObjectTypes.MediaType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNull(found); + } + } + } + + [Test] + public void Can_Create_Container_Containing_Media_Types() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + var contentType = MockedContentTypes.CreateVideoMediaType(); + contentType.ParentId = container.Id; + repository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + Assert.AreEqual(container.Id, contentType.ParentId); + } + } + + [Test] + public void Can_Delete_Container_Containing_Media_Types() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + EntityContainer container; + IMediaType contentType; + using (var repository = CreateRepository(unitOfWork)) + { + container = repository.CreateContainer(-1, "blah", 0); + unitOfWork.Commit(); + + contentType = MockedContentTypes.CreateVideoMediaType(); + contentType.ParentId = container.Id; + repository.AddOrUpdate(contentType); + unitOfWork.Commit(); + } + using (var repository = CreateRepository(unitOfWork)) + { + // Act + repository.DeleteContainer(container.Id); + unitOfWork.Commit(); + + using (var entityRepo = new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, + new Guid(Constants.ObjectTypes.MediaTypeContainer), new Guid(Constants.ObjectTypes.MediaType))) + { + var found = entityRepo.Get(container.Id); + Assert.IsNull(found); + } + + contentType = repository.Get(contentType.Id); + Assert.IsNotNull(contentType); + Assert.AreEqual(-1, contentType.ParentId); + } + } + [Test] public void Can_Perform_Add_On_MediaTypeRepository() { 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..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 @@ -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,57 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to save data for content type id ' + contentType.id); }, - createFolder: function(parentId, name) { + /** + * @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", "PostCreateFolder", { parentId: parentId, name: name })), + $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + createContainer: function(parentId, name) { + + return umbRequestHelper.resourcePromise( + $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/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index a1263a6126..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 @@ -181,13 +181,13 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the data type scaffold. * */ - getScaffold: function () { + getScaffold: function (parentId) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "dataTypeApiBaseUrl", - "GetEmpty")), + "GetEmpty", { parentId: parentId })), "Failed to retrieve data for empty datatype"); }, /** @@ -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,60 @@ 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); + }, + + /** + * @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( + $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/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index 93e7fefa2b..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 @@ -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); @@ -88,13 +99,56 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to save data for content type id ' + contentType.id); }, - createFolder: function(parentId, name) { + /** + * @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( $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/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 11c62f1aec..7bc069ef44 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -108,7 +108,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @param {String} source The URL to load into the iframe */ loadLegacyIFrame: function (source) { - $location.path("/" + appState.getSectionState("currentSection") + "/framed/" + encodeURIComponent(source)); + $location.path("/" + appState.getSectionState("currentSection").toLowerCase() + "/framed/" + encodeURIComponent(source)); }, /** @@ -132,7 +132,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("currentSection", sectionAlias); this.showTree(sectionAlias); - $location.path(sectionAlias); + $location.path(sectionAlias.toLowerCase()); }, /** @@ -250,10 +250,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setMenuState("currentNode", args.node); //not legacy, lets just set the route value and clear the query string if there is one. - $location.path(n.routePath).search(""); + $location.path(n.routePath.toLowerCase()).search(""); } else if (args.element.section) { - $location.path(args.element.section).search(""); + $location.path(args.element.section.toLowerCase()).search(""); } service.hideNavigation(); @@ -446,7 +446,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo if (action.metaData && action.metaData["actionRoute"] && angular.isString(action.metaData["actionRoute"])) { //first check if the menu item simply navigates to a route var parts = action.metaData["actionRoute"].split("?"); - $location.path(parts[0]).search(parts.length > 1 ? parts[1] : ""); + $location.path(parts[0].toLowerCase()).search(parts.length > 1 ? parts[1] : ""); this.hideNavigation(); return; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 8d94a66793..4371c0d7a7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -537,7 +537,7 @@ function umbDataFormatter() { /** formats the display model used to display the data type to the model used to save the data type */ formatDataTypePostData: function(displayModel, preValues, action) { var saveModel = { - parentId: -1, + parentId: displayModel.parentId, id: displayModel.id, name: displayModel.name, selectedEditor: displayModel.selectedEditor, 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/datatypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.controller.js new file mode 100644 index 0000000000..d5d8128155 --- /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("/developer/datatypes/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..af60632f2b --- /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/datatypes/datatype.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js index 4fc5d3a935..101d74b16b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js @@ -25,6 +25,23 @@ function DataTypeDeleteController($scope, dataTypeResource, treeService, navigat }; + $scope.performContainerDelete = function () { + + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + + }; + $scope.cancel = function() { navigationService.hideDialog(); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index 7a6fdf0a9d..f0dff7e2f3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -52,7 +52,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig $scope.page.loading = true; //we are creating so get an empty data type item - dataTypeResource.getScaffold() + dataTypeResource.getScaffold($routeParams.id) .then(function(data) { $scope.preValuesLoaded = true; @@ -91,7 +91,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig // if there are any and then clear them so the collection no longer persists them. serverValidationManager.executeAndClearAllSubscriptions(); - navigationService.syncTree({ tree: "datatypes", path: [String(data.id)] }).then(function (syncArgs) { + navigationService.syncTree({ tree: "datatypes", path: data.path }).then(function (syncArgs) { $scope.page.menu.currentNode = syncArgs.node; }); @@ -152,7 +152,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig //share state editorState.set($scope.content); - navigationService.syncTree({ tree: "datatypes", path: [String(data.id)], forceReload: true }).then(function (syncArgs) { + navigationService.syncTree({ tree: "datatypes", path: data.path, forceReload: true }).then(function (syncArgs) { $scope.page.menu.currentNode = syncArgs.node; }); 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 b7bcae8e3d..fe75a54982 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html @@ -2,11 +2,33 @@

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

- - + +
+ + +
+ +
+

+ All property types & property data + using this data type will be deleted permanently, please confirm you want to delete these as well. +

+ +
+ + + + + +
+
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.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 0c9d027646..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 @@ -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; @@ -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"; @@ -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..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 @@