diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index de586b1716..c63c8eba1e 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -50,6 +50,7 @@ namespace Umbraco.Core.Models _contentTypeId = int.Parse(contentType.Id.ToString(CultureInfo.InvariantCulture)); _properties = properties; _properties.EnsurePropertyTypes(PropertyTypes); + AdditionalData = new Dictionary(); } /// @@ -73,6 +74,7 @@ namespace Umbraco.Core.Models _contentTypeId = int.Parse(contentType.Id.ToString(CultureInfo.InvariantCulture)); _properties = properties; _properties.EnsurePropertyTypes(PropertyTypes); + AdditionalData = new Dictionary(); } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -253,6 +255,11 @@ namespace Umbraco.Core.Models } } + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + public IDictionary AdditionalData { get; private set; } + /// /// List of PropertyGroups available on this Content object /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index e8d2effbc1..0457c113b7 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -42,6 +42,7 @@ namespace Umbraco.Core.Models _allowedContentTypes = new List(); _propertyGroups = new PropertyGroupCollection(); _propertyTypes = new PropertyTypeCollection(); + AdditionalData = new Dictionary(); } protected ContentTypeBase(IContentTypeBase parent) @@ -52,6 +53,7 @@ namespace Umbraco.Core.Models _allowedContentTypes = new List(); _propertyGroups = new PropertyGroupCollection(); _propertyTypes = new PropertyTypeCollection(); + AdditionalData = new Dictionary(); } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -314,6 +316,11 @@ namespace Umbraco.Core.Models } } + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + public IDictionary AdditionalData { get; private set; } + /// /// Gets or sets a list of integer Ids for allowed ContentTypes /// diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 6f7fac6db7..a930bf94c4 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; @@ -33,12 +34,14 @@ namespace Umbraco.Core.Models { _parentId = parentId; _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, true); + AdditionalData = new Dictionary(); } public DataTypeDefinition(int parentId, string propertyEditorAlias) { _parentId = parentId; _propertyEditorAlias = propertyEditorAlias; + AdditionalData = new Dictionary(); } private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -184,6 +187,8 @@ namespace Umbraco.Core.Models _propertyEditorAlias = value; return _propertyEditorAlias; }, _propertyEditorAlias, PropertyEditorAliasSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["DatabaseType"] = value; } } @@ -203,6 +208,8 @@ namespace Umbraco.Core.Models { var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); PropertyEditorAlias = alias; + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["DatabaseType"] = value; } } @@ -220,9 +227,17 @@ namespace Umbraco.Core.Models _databaseType = value; return _databaseType; }, _databaseType, DatabaseTypeSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["DatabaseType"] = value; } } + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + public IDictionary AdditionalData { get; private set; } + internal override void AddingEntity() { base.AddingEntity(); diff --git a/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs b/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs index 62130fdab9..397f4518a9 100644 --- a/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Models.EntityBase +using System.Collections.Generic; + +namespace Umbraco.Core.Models.EntityBase { public interface IUmbracoEntity : IAggregateRoot { @@ -41,5 +43,10 @@ /// Not all entities support being trashed, they'll always return false. /// bool Trashed { get; } + + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + IDictionary AdditionalData { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 030d97272f..b735f385d8 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -44,10 +44,12 @@ namespace Umbraco.Core.Models public UmbracoEntity() { + AdditionalData = new Dictionary(); } public UmbracoEntity(bool trashed) { + AdditionalData = new Dictionary(); Trashed = trashed; } @@ -142,6 +144,9 @@ namespace Umbraco.Core.Models } } + public IDictionary AdditionalData { get; private set; } + + public bool HasChildren { get { return _hasChildren; } @@ -152,6 +157,9 @@ namespace Umbraco.Core.Models _hasChildren = value; return _hasChildren; }, _hasChildren, HasChildrenSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["HasChildren"] = value; } } @@ -164,7 +172,10 @@ namespace Umbraco.Core.Models { _isPublished = value; return _isPublished; - }, _isPublished, IsPublishedSelector); + }, _isPublished, IsPublishedSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["IsPublished"] = value; } } @@ -178,6 +189,9 @@ namespace Umbraco.Core.Models _isDraft = value; return _isDraft; }, _isDraft, IsDraftSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["IsDraft"] = value; } } @@ -191,6 +205,9 @@ namespace Umbraco.Core.Models _hasPendingChanges = value; return _hasPendingChanges; }, _hasPendingChanges, HasPendingChangesSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["HasPendingChanges"] = value; } } @@ -204,6 +221,9 @@ namespace Umbraco.Core.Models _contentTypeAlias = value; return _contentTypeAlias; }, _contentTypeAlias, ContentTypeAliasSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["ContentTypeAlias"] = value; } } @@ -217,6 +237,9 @@ namespace Umbraco.Core.Models _contentTypeIcon = value; return _contentTypeIcon; }, _contentTypeIcon, ContentTypeIconSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["ContentTypeIcon"] = value; } } @@ -230,6 +253,9 @@ namespace Umbraco.Core.Models _contentTypeThumbnail = value; return _contentTypeThumbnail; }, _contentTypeThumbnail, ContentTypeThumbnailSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["ContentTypeThumbnail"] = value; } } @@ -242,10 +268,16 @@ namespace Umbraco.Core.Models { _nodeObjectTypeId = value; return _nodeObjectTypeId; - }, _nodeObjectTypeId, NodeObjectTypeIdSelector); + }, _nodeObjectTypeId, NodeObjectTypeIdSelector); + + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + AdditionalData["NodeObjectTypeId"] = value; } } + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// public IList UmbracoProperties { get; set; } internal class UmbracoProperty diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 182b61f80a..ec3f48a0ca 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -8,6 +8,45 @@ namespace Umbraco.Core.Persistence.Factories { internal class UmbracoEntityFactory : IEntityFactory { + internal UmbracoEntity BuildEntityFromDynamic(dynamic d) + { + var entity = new UmbracoEntity(d.trashed) + { + CreateDate = d.createDate, + CreatorId = d.nodeUser, + Id = d.id, + Key = d.uniqueID, + Level = d.level, + Name = d.text, + NodeObjectTypeId = d.nodeObjectType, + ParentId = d.parentID, + Path = d.path, + SortOrder = d.sortOrder, + HasChildren = d.children > 0, + ContentTypeAlias = d.alias ?? string.Empty, + ContentTypeIcon = d.icon ?? string.Empty, + ContentTypeThumbnail = d.thumbnail ?? string.Empty, + UmbracoProperties = new List() + }; + + var asDictionary = (IDictionary)d; + + var publishedVersion = default(Guid); + //some content items don't have a published version + if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) + { + Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); + } + var newestVersion = default(Guid); + Guid.TryParse(d.newestVersion.ToString(), out newestVersion); + + entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); + entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); + entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; + + return entity; + } + public UmbracoEntity BuildEntity(EntityRepository.UmbracoEntityDto dto) { var entity = new UmbracoEntity(dto.Trashed) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 50e0063937..de2aea0b10 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -1,12 +1,18 @@ using System; using System.Collections.Generic; +using System.Dynamic; +using System.Globalization; using System.Linq; +using System.Reflection; +using System.Text; using Umbraco.Core.Models; +using Umbraco.Core; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Strings; namespace Umbraco.Core.Persistence.Repositories { @@ -124,15 +130,43 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate().Append(GetGroupBy(isContent, isMedia)); - var dtos = isMedia - ? _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql) - : _work.Database.Fetch(sql); - var factory = new UmbracoEntityFactory(); - var list = dtos.Select(factory.BuildEntity).Cast().ToList(); - return list; + if (isMedia) + { + //treat media differently for now + var dtos = _work.Database.Fetch( + new UmbracoEntityRelator().Map, sql); + return dtos.Select(factory.BuildEntity).Cast().ToArray(); + } + else + { + //use dynamic so that we can get ALL properties from the SQL + //we'll have to stitch stuff together manually but we can get our + //additional data to put in the dictionary. + var dtos = _work.Database.Fetch(sql); + var entityProps = TypeHelper.GetPublicProperties(typeof (IUmbracoEntity)).Select(x => x.Name).ToArray(); + var result = new List(); + foreach (var d in dtos) + { + //build the initial entity + IUmbracoEntity entity = factory.BuildEntityFromDynamic(d); + + //convert the dynamic row to dictionary + var asDictionary = (IDictionary) d; + + //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data + foreach (var k in asDictionary.Keys + .Select(x => new {orig = x, title = x.ConvertCase(StringAliasCaseType.PascalCase)}) + .Where(x => entityProps.InvariantContains(x.title) == false)) + { + entity.AdditionalData[k.title] = asDictionary[k.orig]; + } + + result.Add(entity); + } + return result; + } } #endregion @@ -159,11 +193,12 @@ namespace Umbraco.Core.Persistence.Repositories if (isContent || isMedia) { - columns.Add("published.versionId as publishedVerison"); + columns.Add("published.versionId as publishedVersion"); columns.Add("latest.versionId as newestVersion"); columns.Add("contenttype.alias"); columns.Add("contenttype.icon"); columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); } if (isMedia) @@ -251,6 +286,7 @@ namespace Umbraco.Core.Persistence.Repositories columns.Add("contenttype.alias"); columns.Add("contenttype.icon"); columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); } if (isMedia) @@ -287,7 +323,7 @@ namespace Umbraco.Core.Persistence.Repositories [Column("children")] public int Children { get; set; } - [Column("publishedVerison")] + [Column("publishedVersion")] public Guid PublishedVersion { get; set; } [Column("newestVersion")] diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index 7d0810cf13..7fba74b2a4 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -203,6 +203,53 @@ namespace Umbraco.Core }); } + /// + /// Returns all public properties including inherited properties even for interfaces + /// + /// + /// + /// + /// taken from http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy + /// + public static PropertyInfo[] GetPublicProperties(Type type) + { + if (type.IsInterface) + { + var propertyInfos = new List(); + + var considered = new List(); + var queue = new Queue(); + considered.Add(type); + queue.Enqueue(type); + while (queue.Count > 0) + { + var subType = queue.Dequeue(); + foreach (var subInterface in subType.GetInterfaces()) + { + if (considered.Contains(subInterface)) continue; + + considered.Add(subInterface); + queue.Enqueue(subInterface); + } + + var typeProperties = subType.GetProperties( + BindingFlags.FlattenHierarchy + | BindingFlags.Public + | BindingFlags.Instance); + + var newPropertyInfos = typeProperties + .Where(x => !propertyInfos.Contains(x)); + + propertyInfos.InsertRange(0, newPropertyInfos); + } + + return propertyInfos.ToArray(); + } + + return type.GetProperties(BindingFlags.FlattenHierarchy + | BindingFlags.Public | BindingFlags.Instance); + } + /// /// Gets (and caches) discoverable in the current for a given . ///