diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index e84b68b063..959d2f1aa9 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,10 +1,38 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { + public class TaggedEntity + { + public TaggedEntity(int entityId, IEnumerable taggedProperties) + { + EntityId = entityId; + TaggedProperties = taggedProperties; + } + + public int EntityId { get; private set; } + public IEnumerable TaggedProperties { get; private set; } + } + + public class TaggedProperty + { + public TaggedProperty(int propertyTypeId, string propertyTypeAlias, IEnumerable tags) + { + PropertyTypeId = propertyTypeId; + PropertyTypeAlias = propertyTypeAlias; + Tags = tags; + } + + public int PropertyTypeId { get; private set; } + public string PropertyTypeAlias { get; private set; } + public IEnumerable Tags { get; private set; } + } + [Serializable] [DataContract(IsReference = true)] public class Tag : Entity, ITag diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagsRepository.cs index 459ef81882..80320b70b9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagsRepository.cs @@ -5,9 +5,9 @@ namespace Umbraco.Core.Persistence.Repositories { public interface ITagsRepository : IRepositoryQueryable { - IEnumerable GetIdsForEntityTypeByTagGroup(TaggableObjectTypes objectType, string tagGroup); + IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup); - IEnumerable GetIdsForEntityTypeByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null); + IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null); /// /// Returns all tags for an entity type (content/media/member) diff --git a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs index f278a19952..1d11f8d3d4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs @@ -161,36 +161,41 @@ namespace Umbraco.Core.Persistence.Repositories //TODO: Consider caching implications. - public IEnumerable GetIdsForEntityTypeByTagGroup(TaggableObjectTypes objectType, string tagGroup) + public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup) { var nodeObjectType = GetNodeObjectType(objectType); var sql = new Sql() - .Select("DISTINCT cmsTagRelationship.nodeId") + .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group")) .From() .InnerJoin() .On(left => left.TagId, right => right.Id) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .Where(dto => dto.NodeObjectType == nodeObjectType) - .Where(dto => dto.Group == tagGroup); + .Where(dto => dto.Group == tagGroup); - return ApplicationContext.Current.DatabaseContext.Database.Fetch(sql); + return CreateTaggedEntityCollection( + ApplicationContext.Current.DatabaseContext.Database.Fetch(sql)); } - public IEnumerable GetIdsForEntityTypeByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null) + public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null) { var nodeObjectType = GetNodeObjectType(objectType); var sql = new Sql() - .Select("DISTINCT cmsTagRelationship.nodeId") + .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group")) .From() .InnerJoin() .On(left => left.TagId, right => right.Id) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .Where(dto => dto.NodeObjectType == nodeObjectType) @@ -201,7 +206,24 @@ namespace Umbraco.Core.Persistence.Repositories sql = sql.Where(dto => dto.Group == tagGroup); } - return ApplicationContext.Current.DatabaseContext.Database.Fetch(sql); + return CreateTaggedEntityCollection( + ApplicationContext.Current.DatabaseContext.Database.Fetch(sql)); + } + + private IEnumerable CreateTaggedEntityCollection(IEnumerable dbResult) + { + var list = new List(); + foreach (var node in dbResult.GroupBy(x => (int)x.nodeId)) + { + var properties = new List(); + foreach (var propertyType in node.GroupBy(x => new { id = (int)x.propertyTypeId, alias = (string)x.Alias })) + { + var tags = propertyType.Select(x => new Tag((int)x.tagId, (string)x.tag, (string)x.group)); + properties.Add(new TaggedProperty(propertyType.Key.id, propertyType.Key.alias, tags)); + } + list.Add(new TaggedEntity(node.Key, properties)); + } + return list; } public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null) diff --git a/src/Umbraco.Core/Services/ITagService.cs b/src/Umbraco.Core/Services/ITagService.cs index 839df8a05d..87b47c1cbc 100644 --- a/src/Umbraco.Core/Services/ITagService.cs +++ b/src/Umbraco.Core/Services/ITagService.cs @@ -17,12 +17,12 @@ namespace Umbraco.Core.Services public interface ITagService : IService { - IEnumerable GetContentIdsByTagGroup(string tagGroup); - IEnumerable GetContentIdsByTag(string tag, string tagGroup = null); - IEnumerable GetMediaIdsByTagGroup(string tagGroup); - IEnumerable GetMediaIdsByTag(string tag, string tagGroup = null); - IEnumerable GetMemberIdsByTagGroup(string tagGroup); - IEnumerable GetMemberIdsByTag(string tag, string tagGroup = null); + IEnumerable GetTaggedContentByTagGroup(string tagGroup); + IEnumerable GetTaggedContentByTag(string tag, string tagGroup = null); + IEnumerable GetTaggedMediaByTagGroup(string tagGroup); + IEnumerable GetTaggedMediaByTag(string tag, string tagGroup = null); + IEnumerable GetTaggedMembersByTagGroup(string tagGroup); + IEnumerable GetTaggedMembersByTag(string tag, string tagGroup = null); /// /// Get every tag stored in the database (with optional group) diff --git a/src/Umbraco.Core/Services/TagService.cs b/src/Umbraco.Core/Services/TagService.cs index 8e5d6c0ab0..01c2826cea 100644 --- a/src/Umbraco.Core/Services/TagService.cs +++ b/src/Umbraco.Core/Services/TagService.cs @@ -41,51 +41,51 @@ namespace Umbraco.Core.Services _uowProvider = provider; } - public IEnumerable GetContentIdsByTagGroup(string tagGroup) + public IEnumerable GetTaggedContentByTagGroup(string tagGroup) { using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork())) { - return repository.GetIdsForEntityTypeByTagGroup(TaggableObjectTypes.Content, tagGroup); + return repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, tagGroup); } } - public IEnumerable GetContentIdsByTag(string tag, string tagGroup = null) + public IEnumerable GetTaggedContentByTag(string tag, string tagGroup = null) { using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork())) { - return repository.GetIdsForEntityTypeByTag(TaggableObjectTypes.Content, tag, tagGroup); + return repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, tag, tagGroup); } } - public IEnumerable GetMediaIdsByTagGroup(string tagGroup) + public IEnumerable GetTaggedMediaByTagGroup(string tagGroup) { using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork())) { - return repository.GetIdsForEntityTypeByTagGroup(TaggableObjectTypes.Media, tagGroup); + return repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, tagGroup); } } - public IEnumerable GetMediaIdsByTag(string tag, string tagGroup = null) + public IEnumerable GetTaggedMediaByTag(string tag, string tagGroup = null) { using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork())) { - return repository.GetIdsForEntityTypeByTag(TaggableObjectTypes.Media, tag, tagGroup); + return repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Media, tag, tagGroup); } } - public IEnumerable GetMemberIdsByTagGroup(string tagGroup) + public IEnumerable GetTaggedMembersByTagGroup(string tagGroup) { using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork())) { - return repository.GetIdsForEntityTypeByTagGroup(TaggableObjectTypes.Member, tagGroup); + return repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Member, tagGroup); } } - public IEnumerable GetMemberIdsByTag(string tag, string tagGroup = null) + public IEnumerable GetTaggedMembersByTag(string tag, string tagGroup = null) { using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork())) { - return repository.GetIdsForEntityTypeByTag(TaggableObjectTypes.Member, tag, tagGroup); + return repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Member, tag, tagGroup); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index 0a8c478736..b7310308bc 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -691,6 +691,177 @@ namespace Umbraco.Tests.Persistence.Repositories } + [Test] + public void Can_Get_Tagged_Entities_For_Tag_Group() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + ContentTypeRepository contentTypeRepository; + using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) + using (var mediaRepository = CreateMediaRepository(unitOfWork, out mediaTypeRepository)) + { + //create data to relate to + var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + var content1 = MockedContent.CreateSimpleContent(contentType); + contentRepository.AddOrUpdate(content1); + unitOfWork.Commit(); + + var content2 = MockedContent.CreateSimpleContent(contentType); + contentRepository.AddOrUpdate(content2); + unitOfWork.Commit(); + + var mediaType = MockedContentTypes.CreateImageMediaType("image2"); + mediaTypeRepository.AddOrUpdate(mediaType); + unitOfWork.Commit(); + var media1 = MockedMedia.CreateMediaImage(mediaType, -1); + mediaRepository.AddOrUpdate(media1); + unitOfWork.Commit(); + + using (var repository = CreateRepository(unitOfWork)) + { + repository.AssignTagsToProperty( + content1.Id, + contentType.PropertyTypes.First().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test1"}, + new Tag {Text = "tag3", Group = "test"} + }, false); + + repository.AssignTagsToProperty( + content2.Id, + contentType.PropertyTypes.Last().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test1"}, + new Tag {Text = "tag3", Group = "test"} + }, false); + + repository.AssignTagsToProperty( + media1.Id, + mediaType.PropertyTypes.Last().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test1"} + }, false); + + var contentTestIds = repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, "test").ToArray(); + //there are two content items tagged against the 'test' group + Assert.AreEqual(2, contentTestIds.Count()); + //there are a total of two property types tagged against the 'test' group + Assert.AreEqual(2, contentTestIds.SelectMany(x => x.TaggedProperties).Count()); + //there are a total of 2 tags tagged against the 'test' group + Assert.AreEqual(2, contentTestIds.SelectMany(x => x.TaggedProperties).SelectMany(x => x.Tags).Select(x => x.Id).Distinct().Count()); + + var contentTest1Ids = repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, "test1").ToArray(); + //there are two content items tagged against the 'test1' group + Assert.AreEqual(2, contentTest1Ids.Count()); + //there are a total of two property types tagged against the 'test1' group + Assert.AreEqual(2, contentTest1Ids.SelectMany(x => x.TaggedProperties).Count()); + //there are a total of 1 tags tagged against the 'test1' group + Assert.AreEqual(1, contentTest1Ids.SelectMany(x => x.TaggedProperties).SelectMany(x => x.Tags).Select(x => x.Id).Distinct().Count()); + + var mediaTestIds = repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, "test"); + Assert.AreEqual(1, mediaTestIds.Count()); + + var mediaTest1Ids = repository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, "test1"); + Assert.AreEqual(1, mediaTest1Ids.Count()); + } + } + + } + + [Test] + public void Can_Get_Tagged_Entities_For_Tag() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + ContentTypeRepository contentTypeRepository; + using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) + using (var mediaRepository = CreateMediaRepository(unitOfWork, out mediaTypeRepository)) + { + //create data to relate to + var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test"); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + var content1 = MockedContent.CreateSimpleContent(contentType); + contentRepository.AddOrUpdate(content1); + unitOfWork.Commit(); + + var content2 = MockedContent.CreateSimpleContent(contentType); + contentRepository.AddOrUpdate(content2); + unitOfWork.Commit(); + + var mediaType = MockedContentTypes.CreateImageMediaType("image2"); + mediaTypeRepository.AddOrUpdate(mediaType); + unitOfWork.Commit(); + var media1 = MockedMedia.CreateMediaImage(mediaType, -1); + mediaRepository.AddOrUpdate(media1); + unitOfWork.Commit(); + + using (var repository = CreateRepository(unitOfWork)) + { + repository.AssignTagsToProperty( + content1.Id, + contentType.PropertyTypes.First().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test1"}, + new Tag {Text = "tag3", Group = "test"} + }, false); + + repository.AssignTagsToProperty( + content2.Id, + contentType.PropertyTypes.Last().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test1"}, + }, false); + + repository.AssignTagsToProperty( + media1.Id, + mediaType.PropertyTypes.Last().Id, + new[] + { + new Tag {Text = "tag1", Group = "test"}, + new Tag {Text = "tag2", Group = "test1"} + }, false); + + var contentTestIds = repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, "tag1").ToArray(); + //there are two content items tagged against the 'tag1' tag + Assert.AreEqual(2, contentTestIds.Count()); + //there are a total of two property types tagged against the 'tag1' tag + Assert.AreEqual(2, contentTestIds.SelectMany(x => x.TaggedProperties).Count()); + //there are a total of 1 tags since we're only looking against one tag + Assert.AreEqual(1, contentTestIds.SelectMany(x => x.TaggedProperties).SelectMany(x => x.Tags).Select(x => x.Id).Distinct().Count()); + + var contentTest1Ids = repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, "tag3").ToArray(); + //there are 1 content items tagged against the 'tag3' tag + Assert.AreEqual(1, contentTest1Ids.Count()); + //there are a total of two property types tagged against the 'tag3' tag + Assert.AreEqual(1, contentTest1Ids.SelectMany(x => x.TaggedProperties).Count()); + //there are a total of 1 tags since we're only looking against one tag + Assert.AreEqual(1, contentTest1Ids.SelectMany(x => x.TaggedProperties).SelectMany(x => x.Tags).Select(x => x.Id).Distinct().Count()); + + var mediaTestIds = repository.GetTaggedEntitiesByTag(TaggableObjectTypes.Media, "tag1"); + Assert.AreEqual(1, mediaTestIds.Count()); + + } + } + + } + private ContentRepository CreateContentRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) { var templateRepository = new TemplateRepository(unitOfWork, NullCacheProvider.Current); diff --git a/src/Umbraco.Web/TagQuery.cs b/src/Umbraco.Web/TagQuery.cs index ebdf67b77a..58dcdc3947 100644 --- a/src/Umbraco.Web/TagQuery.cs +++ b/src/Umbraco.Web/TagQuery.cs @@ -38,7 +38,8 @@ namespace Umbraco.Web /// public IEnumerable GetContentByTag(string tag, string tagGroup = null) { - var ids = _tagService.GetContentIdsByTag(tag, tagGroup); + var ids = _tagService.GetTaggedContentByTag(tag, tagGroup) + .Select(x => x.EntityId); return _contentQuery.TypedContent(ids) .Where(x => x != null); } @@ -50,7 +51,8 @@ namespace Umbraco.Web /// public IEnumerable GetContentByTagGroup(string tagGroup) { - var ids = _tagService.GetContentIdsByTagGroup(tagGroup); + var ids = _tagService.GetTaggedContentByTagGroup(tagGroup) + .Select(x => x.EntityId); return _contentQuery.TypedContent(ids) .Where(x => x != null); } @@ -63,7 +65,8 @@ namespace Umbraco.Web /// public IEnumerable GetMediaByTag(string tag, string tagGroup = null) { - var ids = _tagService.GetMediaIdsByTag(tag, tagGroup); + var ids = _tagService.GetTaggedMediaByTag(tag, tagGroup) + .Select(x => x.EntityId); return _contentQuery.TypedMedia(ids) .Where(x => x != null); } @@ -75,7 +78,8 @@ namespace Umbraco.Web /// public IEnumerable GetMediaByTagGroup(string tagGroup) { - var ids = _tagService.GetMediaIdsByTagGroup(tagGroup); + var ids = _tagService.GetTaggedMediaByTagGroup(tagGroup) + .Select(x => x.EntityId); return _contentQuery.TypedMedia(ids) .Where(x => x != null); }