diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs new file mode 100644 index 0000000000..81524807bc --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -0,0 +1,90 @@ +using System; +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class ContentTypeFactory : IEntityFactory + { + private readonly Guid _nodeObjectType; + + #region Implementation of IEntityFactory + + public ContentTypeFactory(Guid nodeObjectType) + { + _nodeObjectType = nodeObjectType; + } + + public IContentType BuildEntity(DocumentTypeDto dto) + { + var contentType = new ContentType + { + Id = dto.ContentTypeDto.NodeDto.NodeId, + Key = + dto.ContentTypeDto.NodeDto.UniqueId.HasValue + ? dto.ContentTypeDto.NodeDto.UniqueId.Value + : dto.ContentTypeDto.NodeDto.NodeId.ToGuid(), + Alias = dto.ContentTypeDto.Alias, + Name = dto.ContentTypeDto.NodeDto.Text, + Icon = dto.ContentTypeDto.Icon, + Thumbnail = dto.ContentTypeDto.Thumbnail, + SortOrder = dto.ContentTypeDto.NodeDto.SortOrder, + Description = dto.ContentTypeDto.Description, + CreateDate = dto.ContentTypeDto.NodeDto.CreateDate, + Path = dto.ContentTypeDto.NodeDto.Path, + Level = dto.ContentTypeDto.NodeDto.Level, + ParentId = dto.ContentTypeDto.NodeDto.ParentId, + UserId = + dto.ContentTypeDto.NodeDto.UserId.HasValue + ? dto.ContentTypeDto.NodeDto.UserId.Value + : 0, + Trashed = dto.ContentTypeDto.NodeDto.Trashed + }; + return contentType; + } + + public DocumentTypeDto BuildDto(IContentType entity) + { + var documentTypeDto = new DocumentTypeDto + {ContentTypeDto = BuildContentTypeDto(entity), ContentTypeNodeId = entity.Id}; + //NOTE TemplateId and IsDefault currently not added + return documentTypeDto; + } + + #endregion + + private ContentTypeDto BuildContentTypeDto(IContentType entity) + { + var contentTypeDto = new ContentTypeDto + { + Alias = entity.Alias, + Description = entity.Description, + Icon = entity.Icon, + Thumbnail = entity.Thumbnail, + NodeId = entity.Id, + NodeDto = BuildNodeDto(entity) + }; + return contentTypeDto; + } + + private NodeDto BuildNodeDto(IContentType entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectType, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = false, + UniqueId = entity.Key, + UserId = entity.UserId + }; + return nodeDto; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs new file mode 100644 index 0000000000..04312ce05a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class PropertyGroupFactory : IEntityFactory, IEnumerable> + { + private int _id; + + public PropertyGroupFactory(int id) + { + _id = id; + } + + #region Implementation of IEntityFactory,IEnumerable> + + public IEnumerable BuildEntity(IEnumerable dto) + { + var propertyGroups = new PropertyGroupCollection(); + foreach (var tabDto in dto) + { + var group = new PropertyGroup(); + group.Id = tabDto.Id; + group.Name = tabDto.Text; + group.SortOrder = tabDto.SortOrder; + group.PropertyTypes = new PropertyTypeCollection(); + + foreach (var typeDto in tabDto.PropertyTypeDtos) + { + group.PropertyTypes.Add(new PropertyType(typeDto.DataTypeDto.ControlId, + typeDto.DataTypeDto.DbType.EnumParse(true)) + { + Alias = typeDto.Alias, + DataTypeId = typeDto.DataTypeId, + Description = typeDto.Description, + Id = typeDto.Id, + Name = typeDto.Name, + HelpText = typeDto.HelpText, + Mandatory = typeDto.Mandatory, + SortOrder = typeDto.SortOrder + }); + + } + group.ResetDirtyProperties(); + propertyGroups.Add(group); + } + + return propertyGroups; + } + + public IEnumerable BuildDto(IEnumerable entity) + { + return entity.Select(propertyGroup => BuildTabDto(propertyGroup)).ToList(); + } + + #endregion + + internal TabDto BuildTabDto(PropertyGroup propertyGroup) + { + var tabDto = new TabDto + { + ContentTypeNodeId = _id, + SortOrder = propertyGroup.SortOrder, + Text = propertyGroup.Name + }; + + if (propertyGroup.HasIdentity) + tabDto.Id = propertyGroup.Id; + + tabDto.PropertyTypeDtos = propertyGroup.PropertyTypes.Select(propertyType => BuildPropertyTypeDto(propertyGroup.Id, propertyType)).ToList(); + + return tabDto; + } + + internal PropertyTypeDto BuildPropertyTypeDto(int tabId, PropertyType propertyType) + { + var propertyTypeDto = new PropertyTypeDto + { + Alias = propertyType.Alias, + ContentTypeId = _id, + DataTypeId = propertyType.DataTypeId, + Description = propertyType.Description, + HelpText = propertyType.HelpText, + Mandatory = propertyType.Mandatory, + Name = propertyType.Name, + SortOrder = propertyType.SortOrder, + TabId = tabId + }; + + if (propertyType.HasIdentity) + propertyTypeDto.Id = propertyType.Id; + + return propertyTypeDto; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs new file mode 100644 index 0000000000..72b9351f83 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Relators; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents a repository for doing CRUD operations for + /// + internal class ContentTypeRepository : PetaPocoRepositoryBase, IContentTypeRepository + { + public ContentTypeRepository(IUnitOfWork work) : base(work) + { + } + + public ContentTypeRepository(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + { + } + + #region Overrides of RepositoryBase + + protected override IContentType PerformGet(int id) + { + var contentTypeSql = GetBaseQuery(false); + contentTypeSql.Append(GetBaseWhereClause(id)); + + var documentTypeDto = Database.Query(contentTypeSql).FirstOrDefault(); + + if (documentTypeDto == null) + return null; + + //TODO Get ContentType composition according to new table + + var propertySql = new Sql(); + propertySql.Select("*"); + propertySql.From("cmsTab"); + propertySql.RightJoin("cmsPropertyType ON [cmsTab].[id] = [cmsPropertyType].[tabId]"); + propertySql.InnerJoin("cmsDataType ON [cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]"); + propertySql.Where("[cmsPropertyType].[contentTypeId] = @Id", new { Id = id }); + + var tabDtos = Database.Fetch(new TabPropertyTypeRelator().Map, propertySql); + + var factory = new ContentTypeFactory(NodeObjectTypeId); + var contentType = factory.BuildEntity(documentTypeDto); + + var propertyFactory = new PropertyGroupFactory(id); + var propertyGroups = propertyFactory.BuildEntity(tabDtos); + contentType.PropertyGroups = new PropertyGroupCollection(propertyGroups); + + ((ContentType)contentType).ResetDirtyProperties(); + return contentType; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + if (ids.Any()) + { + foreach (var id in ids) + { + yield return Get(id); + } + } + else + { + var nodeDtos = Database.Fetch("WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = NodeObjectTypeId }); + foreach (var nodeDto in nodeDtos) + { + yield return Get(nodeDto.NodeId); + } + } + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + var documentTypeDtos = Database.Fetch(sql); + + foreach (var dto in documentTypeDtos) + { + yield return Get(dto.ContentTypeNodeId); + } + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + //NOTE: If IsDefault=true we won't get ContentTypes like File, Folder etc. but only DocumentTypes. + //Which is why "AND cmsDocumentType.IsDefault = @IsDefault" has been removed from sql below. + //But might need to add it if we create a MediaTypeRepository + sql.Select(isCount ? "COUNT(*)" : "*"); + sql.From("cmsDocumentType"); + sql.RightJoin("cmsContentType ON ([cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId])"); + sql.InnerJoin("umbracoNode ON ([cmsContentType].[nodeId] = [umbracoNode].[id])"); + sql.Where("[umbracoNode].[nodeObjectType] = @NodeObjectType", new { NodeObjectType = NodeObjectTypeId }); + return sql; + } + + protected override Sql GetBaseWhereClause(object id) + { + var sql = new Sql(); + sql.Where("[umbracoNode].[id] = @Id", new { Id = id }); + return sql; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + string.Format("DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id"), + string.Format("DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id"), + string.Format("DELETE FROM cmsTagRelationship WHERE nodeId = @Id"), + string.Format("DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id"), + string.Format("DELETE FROM cmsPropertyType WHERE contentTypeId = @Id"), + string.Format("DELETE FROM cmsTab WHERE contenttypeNodeId = @Id"), + string.Format("DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id"), + string.Format("DELETE FROM cmsContentType WHERE NodeId = @Id"), + string.Format("DELETE FROM umbracoNode WHERE id = @Id") + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"); } + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IContentType entity) + { + ((ContentType)entity).AddingEntity(); + + var factory = new ContentTypeFactory(NodeObjectTypeId); + var dto = factory.BuildDto(entity); + + //Logic for setting Path, Level and SortOrder + var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + int level = parent.Level + 1; + int sortOrder = + Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); + + //Create the (base) node data - umbracoNode + var nodeDto = dto.ContentTypeDto.NodeDto; + nodeDto.Path = parent.Path; + nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); + nodeDto.SortOrder = sortOrder; + var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + //Update with new correct path + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + Database.Update(nodeDto); + + //Update entity with correct values + entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set + entity.Path = nodeDto.Path; + entity.SortOrder = sortOrder; + entity.Level = level; + + //Insert new ContentType entry + var contentTypeDto = dto.ContentTypeDto; + Database.Insert(contentTypeDto); + + //TODO Insert new DocumentType entries - NOTE only seems relevant as long as Templates resides in the DB + //TODO Insert allowed Templates and DocumentTypes + //TODO Insert ContentType composition in new table + + var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId); + + //Insert Tabs + foreach (var propertyGroup in entity.PropertyGroups) + { + var tabDto = propertyFactory.BuildTabDto(propertyGroup); + var primaryKey = Convert.ToInt32(Database.Insert(tabDto)); + propertyGroup.Id = primaryKey;//Set Id on PropertyGroup + } + + //Insert PropertyTypes + foreach (var propertyGroup in entity.PropertyGroups) + { + foreach (var propertyType in propertyGroup.PropertyTypes) + { + var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(propertyGroup.Id, propertyType); + var primaryKey = Convert.ToInt32(Database.Insert(propertyTypeDto)); + propertyType.Id = primaryKey;//Set Id on PropertyType + } + } + + ((ContentType)entity).ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IContentType entity) + { + //Updates Modified date + ((ContentType)entity).UpdatingEntity(); + + var propertyFactory = new PropertyGroupFactory(entity.Id); + var factory = new ContentTypeFactory(NodeObjectTypeId); + var dto = factory.BuildDto(entity); + var nodeDto = dto.ContentTypeDto.NodeDto; + var o = Database.Update(nodeDto); + + //Look up ContentType entry to get PrimaryKey for updating the DTO + var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); + var contentTypeDto = dto.ContentTypeDto; + contentTypeDto.PrimaryKey = dtoPk.PrimaryKey; + Database.Update(contentTypeDto); + + //Look up DocumentType entries for updating - this could possibly be a "remove all, insert all"-approach + + //Check Dirty properties for Tabs/Groups and PropertyTypes - insert and delete accordingly + if (((ICanBeDirty)entity).IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) + { + //Delete PropertyTypes by excepting entries from db with entries from collections + var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }).Select(x => x.Alias); + var entityPropertyTypes = entity.PropertyTypes.Select(x => x.Alias); + var aliases = dbPropertyTypes.Except(entityPropertyTypes); + foreach (var alias in aliases) + { + Database.Delete("WHERE contentTypeId = @Id AND Alias = @Alias", new { Id = entity.Id, Alias = alias }); + } + //Delete Tabs/Groups by excepting entries from db with entries from collections + var dbPropertyGroups = Database.Fetch("WHERE contenttypeNodeId = @Id", new { Id = entity.Id }).Select(x => x.Text); + var entityPropertyGroups = entity.PropertyGroups.Select(x => x.Name); + var tabs = dbPropertyGroups.Except(entityPropertyGroups); + foreach (var tabName in tabs) + { + Database.Delete("WHERE contenttypeNodeId = @Id AND text = @Name", new { Id = entity.Id, Name = tabName }); + } + + //Run through all groups and types to insert or update entries + foreach (var propertyGroup in entity.PropertyGroups) + { + var tabDto = propertyFactory.BuildTabDto(propertyGroup); + int groupPrimaryKey = propertyGroup.HasIdentity + ? Database.Update(tabDto) + : Convert.ToInt32(Database.Insert(tabDto)); + if (!propertyGroup.HasIdentity) + propertyGroup.Id = groupPrimaryKey;//Set Id on new PropertyGroup + + //This should indicate that neither group nor property types has been touched, but this implies a deeper 'Dirty'-lookup + //if(!propertyGroup.IsDirty()) continue; + + foreach (var propertyType in propertyGroup.PropertyTypes) + { + var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(propertyGroup.Id, propertyType); + int typePrimaryKey = propertyType.HasIdentity + ? Database.Update(propertyTypeDto) + : Convert.ToInt32(Database.Insert(propertyTypeDto)); + if (!propertyType.HasIdentity) + propertyType.Id = typePrimaryKey;//Set Id on new PropertyType + } + } + } + + ((ContentType)entity).ResetDirtyProperties(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 836a520a3b..cc90a29413 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -99,15 +99,18 @@ + + +