From 1e1d49f3050ad28fa5785be591424da385048f9e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 30 Jun 2014 16:43:51 +1000 Subject: [PATCH] fixes U4-5140 Package installation for content does not respect tags, however need to check with Morten if it's required because if we never actually publish content on package install then this wouldn't really matter. But surely we must publish on package install for some thing because starter kits are published... hrm. --- src/Umbraco.Core/Models/Tag.cs | 27 ------- src/Umbraco.Core/Models/TaggedEntity.cs | 16 ++++ src/Umbraco.Core/Models/TaggedProperty.cs | 18 +++++ src/Umbraco.Core/Services/PackagingService.cs | 77 ++++++++++++++----- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../PackageInstance/CreatedPackage.cs | 73 +++++++++++++++++- src/umbraco.cms/packages.config | 1 + src/umbraco.cms/umbraco.cms.csproj | 4 + 8 files changed, 168 insertions(+), 50 deletions(-) create mode 100644 src/Umbraco.Core/Models/TaggedEntity.cs create mode 100644 src/Umbraco.Core/Models/TaggedProperty.cs diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index 959d2f1aa9..1bd5bff76d 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,38 +1,11 @@ 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/Models/TaggedEntity.cs b/src/Umbraco.Core/Models/TaggedEntity.cs new file mode 100644 index 0000000000..decd4220fe --- /dev/null +++ b/src/Umbraco.Core/Models/TaggedEntity.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +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; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/TaggedProperty.cs b/src/Umbraco.Core/Models/TaggedProperty.cs new file mode 100644 index 0000000000..3b92413cdb --- /dev/null +++ b/src/Umbraco.Core/Models/TaggedProperty.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + 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; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index c8fe4e781b..9c4b989dbf 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; +using System.Xml.XPath; +using Newtonsoft.Json; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; @@ -106,6 +108,17 @@ namespace Umbraco.Core.Services return Enumerable.Empty(); } + //check for tag properties element at root + XElement tagProperties = null; + if (element.Document != null && element.Document.Root != null) + { + var found = element.Document.Root.XPathSelectElement("/umbPackage/TagProperties"); + if (found != null) + { + tagProperties = found; + } + } + var name = element.Name.LocalName; if (name.Equals("DocumentSet")) { @@ -114,7 +127,7 @@ namespace Umbraco.Core.Services where (string)doc.Attribute("isDoc") == "" select doc; - var contents = ParseDocumentRootXml(roots, parentId); + var contents = ParseDocumentRootXml(roots, parentId, tagProperties); if (contents.Any()) _contentService.Save(contents, userId); @@ -128,7 +141,7 @@ namespace Umbraco.Core.Services { //This is a single doc import var elements = new List { element }; - var contents = ParseDocumentRootXml(elements, parentId); + var contents = ParseDocumentRootXml(elements, parentId, tagProperties); if (contents.Any()) _contentService.Save(contents, userId); @@ -142,7 +155,7 @@ namespace Umbraco.Core.Services "'DocumentSet' (for structured imports) nor is the first element a Document (for single document import)."); } - private IEnumerable ParseDocumentRootXml(IEnumerable roots, int parentId) + private IEnumerable ParseDocumentRootXml(IEnumerable roots, int parentId, XElement tagProperties) { var contents = new List(); foreach (var root in roots) @@ -158,19 +171,19 @@ namespace Umbraco.Core.Services _importedContentTypes.Add(contentTypeAlias, contentType); } - var content = CreateContentFromXml(root, _importedContentTypes[contentTypeAlias], null, parentId, isLegacySchema); + var content = CreateContentFromXml(root, _importedContentTypes[contentTypeAlias], null, parentId, isLegacySchema, tagProperties); contents.Add(content); var children = from child in root.Elements() where (string)child.Attribute("isDoc") == "" select child; if (children.Any()) - contents.AddRange(CreateContentFromXml(children, content, isLegacySchema)); + contents.AddRange(CreateContentFromXml(children, content, isLegacySchema, tagProperties)); } return contents; } - private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, bool isLegacySchema) + private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, bool isLegacySchema, XElement tagProperties) { var list = new List(); foreach (var child in children) @@ -186,7 +199,7 @@ namespace Umbraco.Core.Services } //Create and add the child to the list - var content = CreateContentFromXml(child, _importedContentTypes[contentTypeAlias], parent, default(int), isLegacySchema); + var content = CreateContentFromXml(child, _importedContentTypes[contentTypeAlias], parent, default(int), isLegacySchema, tagProperties); list.Add(content); //Recursive call @@ -196,13 +209,13 @@ namespace Umbraco.Core.Services select grand; if (grandChildren.Any()) - list.AddRange(CreateContentFromXml(grandChildren, content, isLegacySchema)); + list.AddRange(CreateContentFromXml(grandChildren, content, isLegacySchema, tagProperties)); } return list; } - private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId, bool isLegacySchema) + private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId, bool isLegacySchema, XElement tagProperties) { var id = element.Attribute("id").Value; var level = element.Attribute("level").Value; @@ -235,21 +248,47 @@ namespace Umbraco.Core.Services var propertyValue = property.Value; var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias); - if (propertyType != null && propertyType.PropertyEditorAlias == Constants.PropertyEditors.CheckBoxListAlias) - { - var database = ApplicationContext.Current.DatabaseContext.Database; - var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new { Id = propertyType.DataTypeDefinitionId }); - var propertyValueList = new List(); - foreach (var preValue in propertyValue.Split(',')) + //TODO: It would be heaps nicer if we didn't have to hard code references to specific property editors + // we'd have to modify the packaging format to denote how to parse/store the value instead of relying on this + + if (propertyType != null) + { + if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.CheckBoxListAlias) { - propertyValueList.Add(dtos.Single(x => x.Value == preValue).Id.ToString(CultureInfo.InvariantCulture)); + var database = ApplicationContext.Current.DatabaseContext.Database; + var dtos = database.Fetch("WHERE datatypeNodeId = @Id", new {Id = propertyType.DataTypeDefinitionId}); + + var propertyValueList = new List(); + foreach (var preValue in propertyValue.Split(',')) + { + propertyValueList.Add(dtos.Single(x => x.Value == preValue).Id.ToString(CultureInfo.InvariantCulture)); + } + + propertyValue = string.Join(",", propertyValueList.ToArray()); + + //set value as per normal + content.SetValue(propertyTypeAlias, propertyValue); + } + else + { + //check if this exists in tagProperties + var hasTags = tagProperties.XPathSelectElement(string.Format("//TagProperty[@docId=\"{0}\" and @propertyAlias=\"{1}\"]", id, propertyType.Alias)); + if (hasTags != null) + { + var tags = JsonConvert.DeserializeObject(hasTags.Value); + content.SetTags(propertyTypeAlias, tags, true, hasTags.Attribute("group").Value); + } + } - propertyValue = string.Join(",", propertyValueList.ToArray()); } - - content.SetValue(propertyTypeAlias, propertyValue); + else + { + //set value as per normal + content.SetValue(propertyTypeAlias, propertyValue); + } + } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2ff13b5ada..9149248e18 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -364,6 +364,8 @@ + + diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs index 2a4c155efb..5bc792799a 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/CreatedPackage.cs @@ -1,14 +1,20 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; using System.Text; using System.Xml; using System.Web; +using Newtonsoft.Json; +using Umbraco.Core; using Umbraco.Core.Logging; -using umbraco.cms.businesslogic.template; using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic.macro; using Umbraco.Core.IO; +using Umbraco.Core.Models; +using File = System.IO.File; +using Template = umbraco.cms.businesslogic.template.Template; namespace umbraco.cms.businesslogic.packager @@ -120,14 +126,14 @@ namespace umbraco.cms.businesslogic.packager //Info section.. AppendElement(utill.PackageInfo(pack, _packageManifest)); - //Documents... + //Documents and tags... var contentNodeId = 0; if (string.IsNullOrEmpty(pack.ContentNodeId) == false && int.TryParse(pack.ContentNodeId, out contentNodeId)) { if (contentNodeId > 0) { + //Create the Documents/DocumentSet node XmlNode documents = _packageManifest.CreateElement("Documents"); - XmlNode documentSet = _packageManifest.CreateElement("DocumentSet"); XmlAttribute importMode = _packageManifest.CreateAttribute("importMode", ""); importMode.Value = "root"; @@ -139,7 +145,66 @@ namespace umbraco.cms.businesslogic.packager documentSet.AppendChild(umbDocument.ToXml(_packageManifest, pack.ContentLoadChildNodes)); - AppendElement(documents); + AppendElement(documents); + + //Create the TagProperties node - this is used to store a definition for all + // document properties that are tags, this ensures that we can re-import tags properly + XmlNode tagProps = _packageManifest.CreateElement("TagProperties"); + + //before we try to populate this, we'll do a quick lookup to see if any of the documents + // being exported contain published tags. + var allExportedIds = documents.SelectNodes("//@id").Cast() + .Select(x => x.Value.TryConvertTo()) + .Where(x => x.Success) + .Select(x => x.Result) + .ToArray(); + var allContentTags = new List(); + foreach (var exportedId in allExportedIds) + { + allContentTags.AddRange( + ApplicationContext.Current.Services.TagService.GetTagsForEntity(exportedId)); + } + + //This is pretty round-about but it works. Essentially we need to get the properties that are tagged + // but to do that we need to lookup by a tag (string) + var allTaggedEntities = new List(); + foreach (var group in allContentTags.Select(x => x.Group).Distinct()) + { + allTaggedEntities.AddRange( + ApplicationContext.Current.Services.TagService.GetTaggedContentByTagGroup(group)); + } + + //Now, we have all property Ids/Aliases and their referenced document Ids and tags + var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) + .DistinctBy(x => x.EntityId) + .OrderBy(x => x.EntityId); + + foreach (var taggedEntity in allExportedTaggedEntities) + { + foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) + { + XmlNode tagProp = _packageManifest.CreateElement("TagProperty"); + var docId = _packageManifest.CreateAttribute("docId", ""); + docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); + tagProp.Attributes.Append(docId); + + var propertyAlias = _packageManifest.CreateAttribute("propertyAlias", ""); + propertyAlias.Value = taggedProperty.PropertyTypeAlias; + tagProp.Attributes.Append(propertyAlias); + + var group = _packageManifest.CreateAttribute("group", ""); + group.Value = taggedProperty.Tags.First().Group; + tagProp.Attributes.Append(group); + + tagProp.AppendChild(_packageManifest.CreateCDataSection( + JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); + + tagProps.AppendChild(tagProp); + } + } + + AppendElement(tagProps); + } } diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config index fb9ebcc90d..4d7778d690 100644 --- a/src/umbraco.cms/packages.config +++ b/src/umbraco.cms/packages.config @@ -2,6 +2,7 @@ + \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 2cff54744b..75afd7f433 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -118,6 +118,10 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + False + ..\packages\Newtonsoft.Json.6.0.2\lib\net45\Newtonsoft.Json.dll + System