diff --git a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs index 54d47cca17..226b74266b 100644 --- a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs @@ -5,6 +5,7 @@ namespace Umbraco.Core.CodeAnnotations /// /// Attribute to add a Friendly Name string with an UmbracoObjectType enum value /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] internal class FriendlyNameAttribute : Attribute { /// diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs index e6f887a2c8..8a44ba1589 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs @@ -5,6 +5,7 @@ namespace Umbraco.Core.CodeAnnotations /// /// Attribute to associate a GUID string and Type with an UmbracoObjectType Enum value /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] internal class UmbracoObjectTypeAttribute : Attribute { public UmbracoObjectTypeAttribute(string objectId) diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index ebb4477557..4791606d2c 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -18,6 +18,8 @@ namespace Umbraco.Core.Models private bool _trashed; private bool _hasChildren; private bool _isPublished; + private bool _isDraft; + private bool _hasPendingChanges; private Guid _nodeObjectTypeId; private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); @@ -29,6 +31,8 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); private static readonly PropertyInfo HasChildrenSelector = ExpressionHelper.GetPropertyInfo(x => x.HasChildren); private static readonly PropertyInfo IsPublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsPublished); + private static readonly PropertyInfo IsDraftSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDraft); + private static readonly PropertyInfo HasPendingChangesSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPendingChanges); private static readonly PropertyInfo NodeObjectTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeObjectTypeId); public UmbracoEntity() @@ -157,6 +161,32 @@ namespace Umbraco.Core.Models } } + public bool IsDraft + { + get { return _isPublished; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _isDraft = value; + return _isDraft; + }, _isDraft, IsDraftSelector); + } + } + + public bool HasPendingChanges + { + get { return _hasPendingChanges; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _hasPendingChanges = value; + return _hasPendingChanges; + }, _hasPendingChanges, HasPendingChangesSelector); + } + } + public Guid NodeObjectTypeId { get { return _nodeObjectTypeId; } diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs index 84067fabd7..a97f1fe976 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs @@ -51,7 +51,15 @@ namespace Umbraco.Core.Models if (UmbracoObjectTypeCache.ContainsKey(umbracoObjectType)) return UmbracoObjectTypeCache[umbracoObjectType]; - var attribute = umbracoObjectType.GetType().FirstAttribute(); + var type = typeof(UmbracoObjectTypes); + var memInfo = type.GetMember(umbracoObjectType.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), + false); + + if (attributes.Length == 0) + return Guid.Empty; + + var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); if (attribute == null) return Guid.Empty; @@ -77,7 +85,15 @@ namespace Umbraco.Core.Models /// a string of the FriendlyName public static string GetFriendlyName(this UmbracoObjectTypes umbracoObjectType) { - var attribute = umbracoObjectType.GetType().FirstAttribute(); + var type = typeof(UmbracoObjectTypes); + var memInfo = type.GetMember(umbracoObjectType.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), + false); + + if (attributes.Length == 0) + return string.Empty; + + var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); if (attribute == null) return string.Empty; diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index e07110809c..e3fec16cf4 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; @@ -20,9 +21,16 @@ namespace Umbraco.Core.Persistence.Factories ParentId = dto.ParentId, Path = dto.Path, SortOrder = dto.SortOrder, - HasChildren = dto.Children > 0, - IsPublished = dto.HasPublishedVersion.HasValue && dto.HasPublishedVersion.Value > 0 + HasChildren = dto.Children > 0 }; + + entity.IsPublished = dto.PublishedVersion != default(Guid) || + (dto.NewestVersion != default(Guid) && dto.PublishedVersion == dto.NewestVersion); + entity.IsDraft = dto.NewestVersion != default(Guid) && + (dto.PublishedVersion == default(Guid) || dto.PublishedVersion != dto.NewestVersion); + entity.HasPendingChanges = dto.PublishedVersion != default(Guid) && dto.NewestVersion != default(Guid) && + dto.PublishedVersion != dto.NewestVersion; + return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index a7b221916c..a4a0e08e5d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -59,7 +59,7 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id, Guid objectTypeId) { bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var sql = GetBaseWhere(GetBase, isContent, objectTypeId, id).Append(GetGroupBy()); + var sql = GetBaseWhere(GetBase, isContent, objectTypeId, id).Append(GetGroupBy(isContent)); var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -82,7 +82,7 @@ namespace Umbraco.Core.Persistence.Repositories else { bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var sql = GetBaseWhere(GetBase, isContent, objectTypeId).Append(GetGroupBy()); + var sql = GetBaseWhere(GetBase, isContent, objectTypeId).Append(GetGroupBy(isContent)); var dtos = _work.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); @@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sqlClause = GetBase(false); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate().Append(GetGroupBy()); + var sql = translator.Translate().Append(GetGroupBy(false)); var dtos = _work.Database.Fetch(sql); @@ -114,7 +114,7 @@ namespace Umbraco.Core.Persistence.Repositories bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); var sqlClause = GetBaseWhere(GetBase, isContent, objectTypeId); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate().Append(GetGroupBy()); + var sql = translator.Translate().Append(GetGroupBy(isContent)); var dtos = _work.Database.Fetch(sql); @@ -143,22 +143,26 @@ namespace Umbraco.Core.Persistence.Repositories "main.text", "main.nodeObjectType", "main.createDate", - "COUNT(parent.parentID) as children", - isContent - ? "SUM(CONVERT(int, document.published)) as published" - : "SUM(0) as published" + "COUNT(parent.parentID) as children" }; + if (isContent) + { + columns.Add("published.versionId as publishedVerison"); + columns.Add("latest.versionId as newestVersion"); + } + var sql = new Sql() .Select(columns.ToArray()) - .From("FROM umbracoNode main") + .From("umbracoNode main") .LeftJoin("umbracoNode parent").On("parent.parentID = main.id"); - //NOTE Should this account for newest = 1 ? Scenarios: unsaved, saved not published, published - if (isContent) - sql.LeftJoin("cmsDocument document").On("document.nodeId = main.id"); + { + sql.LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published").On("main.id = published.nodeId"); + sql.LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE newest = 1 GROUP BY nodeId, versionId) as latest").On("main.id = latest.nodeId"); + } return sql; } @@ -174,7 +178,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = baseQuery(isContent) .Where("main.id = @Id", new {Id = id}) - .Append(GetGroupBy()); + .Append(GetGroupBy(isContent)); return sql; } @@ -186,12 +190,31 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - protected virtual Sql GetGroupBy() + protected virtual Sql GetGroupBy(bool isContent) { + var columns = new List + { + "main.id", + "main.trashed", + "main.parentID", + "main.nodeUser", + "main.level", + "main.path", + "main.sortOrder", + "main.uniqueID", + "main.text", + "main.nodeObjectType", + "main.createDate" + }; + + if (isContent) + { + columns.Add("published.versionId"); + columns.Add("latest.versionId"); + } + var sql = new Sql() - .GroupBy("main.id", "main.trashed", "main.parentID", "main.nodeUser", "main.level", - "main.path", "main.sortOrder", "main.uniqueID", "main.text", - "main.nodeObjectType", "main.createDate") + .GroupBy(columns.ToArray()) .OrderBy("main.sortOrder"); return sql; } @@ -218,8 +241,11 @@ namespace Umbraco.Core.Persistence.Repositories [Column("children")] public int Children { get; set; } - [Column("published")] - public int? HasPublishedVersion { get; set; } + [Column("publishedVerison")] + public Guid PublishedVersion { get; set; } + + [Column("newestVerison")] + public Guid NewestVersion { get; set; } } #endregion } diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index bfc0b2dd9f..5e0b68b239 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -14,20 +14,29 @@ namespace Umbraco.Core.Services { private readonly IDatabaseUnitOfWorkProvider _uowProvider; private readonly RepositoryFactory _repositoryFactory; - private readonly Dictionary _supportedObjectTypes = new Dictionary - { - {typeof(IDataTypeDefinition).FullName, UmbracoObjectTypes.DataType}, - {typeof(IContent).FullName, UmbracoObjectTypes.Document}, - {typeof(IContentType).FullName, UmbracoObjectTypes.DocumentType}, - {typeof(IMedia).FullName, UmbracoObjectTypes.Media}, - {typeof(IMediaType).FullName, UmbracoObjectTypes.MediaType}, - {typeof(ITemplate).FullName, UmbracoObjectTypes.Template} - }; + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly IMediaService _mediaService; + private readonly IDataTypeService _dataTypeService; + private readonly Dictionary>> _supportedObjectTypes; - public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) + public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService) { _uowProvider = provider; _repositoryFactory = repositoryFactory; + _contentService = contentService; + _contentTypeService = contentTypeService; + _mediaService = mediaService; + _dataTypeService = dataTypeService; + + _supportedObjectTypes = new Dictionary>> + { + {typeof(IDataTypeDefinition).FullName, new Tuple>(UmbracoObjectTypes.DataType, _dataTypeService.GetDataTypeDefinitionById)}, + {typeof(IContent).FullName, new Tuple>(UmbracoObjectTypes.Document, _contentService.GetById)}, + {typeof(IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, _contentTypeService.GetContentType)}, + {typeof(IMedia).FullName, new Tuple>(UmbracoObjectTypes.Media, _mediaService.GetById)}, + {typeof(IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, _contentTypeService.GetMediaType)} + }; } /// @@ -50,9 +59,11 @@ namespace Umbraco.Core.Services } var objectType = GetObjectType(id); + var entityType = GetEntityType(objectType); + var typeFullName = entityType.FullName; + var entity = _supportedObjectTypes[typeFullName].Item2(id); - //TODO Implementing loading from the various services - throw new NotImplementedException(); + return entity; } /// @@ -76,9 +87,11 @@ namespace Umbraco.Core.Services } } - - //TODO Implementing loading from the various services - throw new NotImplementedException(); + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + var entity = _supportedObjectTypes[typeFullName].Item2(id); + + return entity; } /// @@ -101,16 +114,15 @@ namespace Umbraco.Core.Services } } - Mandate.That(_supportedObjectTypes.ContainsKey(typeof (T).FullName), () => - { - throw - new NotSupportedException - ("The passed in type is not supported"); - }); - var objectType = _supportedObjectTypes[typeof(T).FullName]; + var typeFullName = typeof(T).FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + var entity = _supportedObjectTypes[typeFullName].Item2(id); - //TODO Implementing loading from the various services - throw new NotImplementedException(); + return entity; } /// @@ -243,12 +255,13 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public virtual IEnumerable GetAll() where T : IUmbracoEntity { - Mandate.That(_supportedObjectTypes.ContainsKey(typeof(T).FullName), () => + var typeFullName = typeof (T).FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => { throw new NotSupportedException ("The passed in type is not supported"); }); - var objectType = _supportedObjectTypes[typeof (T).FullName]; + var objectType = _supportedObjectTypes[typeFullName].Item1; return GetAll(objectType); } @@ -260,6 +273,14 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public virtual IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType) { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + var objectTypeId = umbracoObjectType.GetGuid(); using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) { @@ -274,6 +295,15 @@ namespace Umbraco.Core.Services /// An enumerable list of objects public virtual IEnumerable GetAll(Guid objectTypeId) { + var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) { return repository.GetAll(objectTypeId); @@ -328,7 +358,12 @@ namespace Umbraco.Core.Services /// Type of the entity public virtual Type GetEntityType(UmbracoObjectTypes umbracoObjectType) { - var attribute = umbracoObjectType.GetType().FirstAttribute(); + var type = typeof(UmbracoObjectTypes); + var memInfo = type.GetMember(umbracoObjectType.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), + false); + + var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); if (attribute == null) throw new NullReferenceException("The passed in UmbracoObjectType does not contain an UmbracoObjectTypeAttribute, which is used to retrieve the Type."); diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 954ab45490..35b124f1db 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -79,8 +79,9 @@ namespace Umbraco.Core.Services if(_packagingService == null) _packagingService = new Lazy(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _fileService.Value, repositoryFactory.Value, provider)); + if (_entityService == null) - _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value)); + _entityService = new Lazy(() => new EntityService(provider, repositoryFactory.Value, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value)); } /// diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs new file mode 100644 index 0000000000..abf541883f --- /dev/null +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -0,0 +1,109 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering the EntityService + /// + [TestFixture, RequiresSTA] + public class EntityServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.Document); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_UmbracoObjectType_Id() + { + var service = ServiceContext.EntityService; + + var objectTypeId = new Guid(Constants.ObjectTypes.Document); + var entities = service.GetAll(objectTypeId); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_Type() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Throws_When_Getting_All_With_Invalid_Type() + { + var service = ServiceContext.EntityService; + var objectTypeId = new Guid(Constants.ObjectTypes.ContentItem); + + Assert.Throws(() => service.GetAll()); + Assert.Throws(() => service.GetAll(UmbracoObjectTypes.ContentItem)); + Assert.Throws(() => service.GetAll(objectTypeId)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.DocumentType); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectType_Id() + { + var service = ServiceContext.EntityService; + + var objectTypeId = new Guid(Constants.ObjectTypes.DocumentType); + var entities = service.GetAll(objectTypeId); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_Type() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 83abf06eab..2ab62ab199 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -295,6 +295,7 @@ + True