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