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.

This commit is contained in:
Shannon
2014-06-30 16:43:51 +10:00
parent 1be2706c30
commit 1e1d49f305
8 changed files with 168 additions and 50 deletions

View File

@@ -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<TaggedProperty> taggedProperties)
{
EntityId = entityId;
TaggedProperties = taggedProperties;
}
public int EntityId { get; private set; }
public IEnumerable<TaggedProperty> TaggedProperties { get; private set; }
}
public class TaggedProperty
{
public TaggedProperty(int propertyTypeId, string propertyTypeAlias, IEnumerable<Tag> tags)
{
PropertyTypeId = propertyTypeId;
PropertyTypeAlias = propertyTypeAlias;
Tags = tags;
}
public int PropertyTypeId { get; private set; }
public string PropertyTypeAlias { get; private set; }
public IEnumerable<Tag> Tags { get; private set; }
}
[Serializable]
[DataContract(IsReference = true)]
public class Tag : Entity, ITag

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
public class TaggedEntity
{
public TaggedEntity(int entityId, IEnumerable<TaggedProperty> taggedProperties)
{
EntityId = entityId;
TaggedProperties = taggedProperties;
}
public int EntityId { get; private set; }
public IEnumerable<TaggedProperty> TaggedProperties { get; private set; }
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
public class TaggedProperty
{
public TaggedProperty(int propertyTypeId, string propertyTypeAlias, IEnumerable<ITag> tags)
{
PropertyTypeId = propertyTypeId;
PropertyTypeAlias = propertyTypeAlias;
Tags = tags;
}
public int PropertyTypeId { get; private set; }
public string PropertyTypeAlias { get; private set; }
public IEnumerable<ITag> Tags { get; private set; }
}
}

View File

@@ -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<IContent>();
}
//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<XElement> { 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<IContent> ParseDocumentRootXml(IEnumerable<XElement> roots, int parentId)
private IEnumerable<IContent> ParseDocumentRootXml(IEnumerable<XElement> roots, int parentId, XElement tagProperties)
{
var contents = new List<IContent>();
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<IContent> CreateContentFromXml(IEnumerable<XElement> children, IContent parent, bool isLegacySchema)
private IEnumerable<IContent> CreateContentFromXml(IEnumerable<XElement> children, IContent parent, bool isLegacySchema, XElement tagProperties)
{
var list = new List<IContent>();
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<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new { Id = propertyType.DataTypeDefinitionId });
var propertyValueList = new List<string>();
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<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new {Id = propertyType.DataTypeDefinitionId});
var propertyValueList = new List<string>();
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<string[]>(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);
}
}
}

View File

@@ -364,6 +364,8 @@
<Compile Include="Models\PublishedContent\PublishedContentModelFactoryResolver.cs" />
<Compile Include="Models\TagCacheStorageType.cs" />
<Compile Include="Models\TaggableObjectTypes.cs" />
<Compile Include="Models\TaggedEntity.cs" />
<Compile Include="Models\TaggedProperty.cs" />
<Compile Include="Models\TemplateNode.cs" />
<Compile Include="Models\UmbracoEntityExtensions.cs" />
<Compile Include="Packaging\PackageBinaryByteInspector.cs" />

View File

@@ -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<XmlNode>()
.Select(x => x.Value.TryConvertTo<int>())
.Where(x => x.Success)
.Select(x => x.Result)
.ToArray();
var allContentTags = new List<ITag>();
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<TaggedEntity>();
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);
}
}

View File

@@ -2,6 +2,7 @@
<packages>
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.2" targetFramework="net45" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
<package id="Tidy.Net" version="1.0.0" targetFramework="net40" />
</packages>

View File

@@ -118,6 +118,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System">
<Name>System</Name>
</Reference>