Files
Umbraco-CMS/src/Umbraco.Infrastructure/Services/Implement/EntityXmlSerializer.cs

629 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Xml.Linq;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Services.Implement
{
/// <summary>
/// Serializes entities to XML
/// </summary>
internal class EntityXmlSerializer : IEntityXmlSerializer
{
private readonly IContentTypeService _contentTypeService;
private readonly IMediaService _mediaService;
private readonly IContentService _contentService;
private readonly IDataTypeService _dataTypeService;
private readonly IUserService _userService;
private readonly ILocalizationService _localizationService;
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
private readonly IShortStringHelper _shortStringHelper;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
public EntityXmlSerializer(
IContentService contentService,
IMediaService mediaService,
IDataTypeService dataTypeService,
IUserService userService,
ILocalizationService localizationService,
IContentTypeService contentTypeService,
UrlSegmentProviderCollection urlSegmentProviders,
IShortStringHelper shortStringHelper,
PropertyEditorCollection propertyEditors,
IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
{
_contentTypeService = contentTypeService;
_mediaService = mediaService;
_contentService = contentService;
_dataTypeService = dataTypeService;
_userService = userService;
_localizationService = localizationService;
_urlSegmentProviders = urlSegmentProviders;
_shortStringHelper = shortStringHelper;
_propertyEditors = propertyEditors;
_configurationEditorJsonSerializer = configurationEditorJsonSerializer;
}
/// <summary>
/// Exports an IContent item as an XElement.
/// </summary>
public XElement Serialize(IContent content,
bool published,
bool withDescendants = false) // TODO: take care of usage! only used for the packager
{
if (content == null) throw new ArgumentNullException(nameof(content));
var nodeName = content.ContentType.Alias.ToSafeAlias(_shortStringHelper);
var xml = SerializeContentBase(content, content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders), nodeName, published);
xml.Add(new XAttribute("nodeType", content.ContentType.Id));
xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
xml.Add(new XAttribute("creatorName", content.GetCreatorProfile(_userService)?.Name ?? "??"));
//xml.Add(new XAttribute("creatorID", content.CreatorId));
xml.Add(new XAttribute("writerName", content.GetWriterProfile(_userService)?.Name ?? "??"));
xml.Add(new XAttribute("writerID", content.WriterId));
xml.Add(new XAttribute("template", content.TemplateId?.ToString(CultureInfo.InvariantCulture) ?? ""));
xml.Add(new XAttribute("isPublished", content.Published));
if (withDescendants)
{
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while(page * pageSize < total)
{
var children = _contentService.GetPagedChildren(content.Id, page++, pageSize, out total);
SerializeChildren(children, xml, published);
}
}
return xml;
}
/// <summary>
/// Exports an IMedia item as an XElement.
/// </summary>
public XElement Serialize(
IMedia media,
bool withDescendants = false)
{
if (_mediaService == null) throw new ArgumentNullException(nameof(_mediaService));
if (_dataTypeService == null) throw new ArgumentNullException(nameof(_dataTypeService));
if (_userService == null) throw new ArgumentNullException(nameof(_userService));
if (_localizationService == null) throw new ArgumentNullException(nameof(_localizationService));
if (media == null) throw new ArgumentNullException(nameof(media));
if (_urlSegmentProviders == null) throw new ArgumentNullException(nameof(_urlSegmentProviders));
var nodeName = media.ContentType.Alias.ToSafeAlias(_shortStringHelper);
const bool published = false; // always false for media
var xml = SerializeContentBase(media, media.GetUrlSegment(_shortStringHelper, _urlSegmentProviders), nodeName, published);
xml.Add(new XAttribute("nodeType", media.ContentType.Id));
xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias));
//xml.Add(new XAttribute("creatorName", media.GetCreatorProfile(userService).Name));
//xml.Add(new XAttribute("creatorID", media.CreatorId));
xml.Add(new XAttribute("writerName", media.GetWriterProfile(_userService)?.Name ?? string.Empty));
xml.Add(new XAttribute("writerID", media.WriterId));
xml.Add(new XAttribute("udi", media.GetUdi()));
//xml.Add(new XAttribute("template", 0)); // no template for media
if (withDescendants)
{
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while (page * pageSize < total)
{
var children = _mediaService.GetPagedChildren(media.Id, page++, pageSize, out total);
SerializeChildren(children, xml);
}
}
return xml;
}
/// <summary>
/// Exports an IMember item as an XElement.
/// </summary>
public XElement Serialize(IMember member)
{
var nodeName = member.ContentType.Alias.ToSafeAlias(_shortStringHelper);
const bool published = false; // always false for member
var xml = SerializeContentBase(member, "", nodeName, published);
xml.Add(new XAttribute("nodeType", member.ContentType.Id));
xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias));
// what about writer/creator/version?
xml.Add(new XAttribute("loginName", member.Username));
xml.Add(new XAttribute("email", member.Email));
xml.Add(new XAttribute("icon", member.ContentType.Icon));
return xml;
}
/// <summary>
/// Exports a list of Data Types
/// </summary>
/// <param name="dataTypeDefinitions">List of data types to export</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IDataTypeDefinition objects</returns>
public XElement Serialize(IEnumerable<IDataType> dataTypeDefinitions)
{
var container = new XElement("DataTypes");
foreach (var dataTypeDefinition in dataTypeDefinitions)
{
container.Add(Serialize(dataTypeDefinition));
}
return container;
}
public XElement Serialize(IDataType dataType)
{
var xml = new XElement("DataType");
xml.Add(new XAttribute("Name", dataType.Name));
//The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id)
xml.Add(new XAttribute("Id", dataType.EditorAlias));
xml.Add(new XAttribute("Definition", dataType.Key));
xml.Add(new XAttribute("DatabaseType", dataType.DatabaseType.ToString()));
xml.Add(new XAttribute("Configuration", _configurationEditorJsonSerializer.Serialize(dataType.Configuration)));
var folderNames = string.Empty;
if (dataType.Level != 1)
{
//get URL encoded folder names
var folders = _dataTypeService.GetContainers(dataType)
.OrderBy(x => x.Level)
.Select(x => WebUtility.UrlEncode(x.Name));
folderNames = string.Join("/", folders.ToArray());
}
if (string.IsNullOrWhiteSpace(folderNames) == false)
xml.Add(new XAttribute("Folders", folderNames));
return xml;
}
/// <summary>
/// Exports a list of <see cref="IDictionaryItem"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="dictionaryItem">List of dictionary items to export</param>
/// <param name="includeChildren">Optional boolean indicating whether or not to include children</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IDictionaryItem objects</returns>
public XElement Serialize(IEnumerable<IDictionaryItem> dictionaryItem, bool includeChildren = true)
{
var xml = new XElement("DictionaryItems");
foreach (var item in dictionaryItem)
{
xml.Add(Serialize(item, includeChildren));
}
return xml;
}
/// <summary>
/// Exports a single <see cref="IDictionaryItem"/> item to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="dictionaryItem">Dictionary Item to export</param>
/// <param name="includeChildren">Optional boolean indicating whether or not to include children</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IDictionaryItem object</returns>
public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren)
{
var xml = Serialize(dictionaryItem);
if (includeChildren)
{
var children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key);
foreach (var child in children)
{
xml.Add(Serialize(child, true));
}
}
return xml;
}
private XElement Serialize(IDictionaryItem dictionaryItem)
{
var xml = new XElement("DictionaryItem", new XAttribute("Key", dictionaryItem.ItemKey));
foreach (var translation in dictionaryItem.Translations)
{
xml.Add(new XElement("Value",
new XAttribute("LanguageId", translation.Language.Id),
new XAttribute("LanguageCultureAlias", translation.Language.IsoCode),
new XCData(translation.Value)));
}
return xml;
}
public XElement Serialize(Stylesheet stylesheet)
{
var xml = new XElement("Stylesheet",
new XElement("Name", stylesheet.Alias),
new XElement("FileName", stylesheet.Path),
new XElement("Content", new XCData(stylesheet.Content)));
var props = new XElement("Properties");
xml.Add(props);
foreach (var prop in stylesheet.Properties)
{
props.Add(new XElement("Property",
new XElement("Name", prop.Name),
new XElement("Alias", prop.Alias),
new XElement("Value", prop.Value)));
}
return xml;
}
/// <summary>
/// Exports a list of <see cref="ILanguage"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="languages">List of Languages to export</param>
/// <returns><see cref="XElement"/> containing the xml representation of the ILanguage objects</returns>
public XElement Serialize(IEnumerable<ILanguage> languages)
{
var xml = new XElement("Languages");
foreach (var language in languages)
{
xml.Add(Serialize(language));
}
return xml;
}
public XElement Serialize(ILanguage language)
{
var xml = new XElement("Language",
new XAttribute("Id", language.Id),
new XAttribute("CultureAlias", language.IsoCode),
new XAttribute("FriendlyName", language.CultureName));
return xml;
}
public XElement Serialize(ITemplate template)
{
var xml = new XElement("Template");
xml.Add(new XElement("Name", template.Name));
xml.Add(new XElement("Alias", template.Alias));
xml.Add(new XElement("Design", new XCData(template.Content)));
if (template is Template concreteTemplate && concreteTemplate.MasterTemplateId != null)
{
if (concreteTemplate.MasterTemplateId.IsValueCreated &&
concreteTemplate.MasterTemplateId.Value != default)
{
xml.Add(new XElement("Master", concreteTemplate.MasterTemplateId.ToString()));
xml.Add(new XElement("MasterAlias", concreteTemplate.MasterTemplateAlias));
}
}
return xml;
}
/// <summary>
/// Exports a list of <see cref="ITemplate"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="templates"></param>
/// <returns></returns>
public XElement Serialize(IEnumerable<ITemplate> templates)
{
var xml = new XElement("Templates");
foreach (var item in templates)
{
xml.Add(Serialize(item));
}
return xml;
}
public XElement Serialize(IMediaType mediaType)
{
var info = new XElement("Info",
new XElement("Name", mediaType.Name),
new XElement("Alias", mediaType.Alias),
new XElement("Icon", mediaType.Icon),
new XElement("Thumbnail", mediaType.Thumbnail),
new XElement("Description", mediaType.Description),
new XElement("AllowAtRoot", mediaType.AllowedAsRoot.ToString()));
var masterContentType = mediaType.CompositionAliases().FirstOrDefault();
if (masterContentType != null)
info.Add(new XElement("Master", masterContentType));
var structure = new XElement("Structure");
foreach (var allowedType in mediaType.AllowedContentTypes)
{
structure.Add(new XElement("MediaType", allowedType.Alias));
}
var genericProperties = new XElement("GenericProperties"); // actually, all of them
foreach (var propertyType in mediaType.PropertyTypes)
{
var definition = _dataTypeService.GetDataType(propertyType.DataTypeId);
var propertyGroup = propertyType.PropertyGroupId == null // true generic property
? null
: mediaType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value);
var genericProperty = new XElement("GenericProperty",
new XElement("Name", propertyType.Name),
new XElement("Alias", propertyType.Alias),
new XElement("Type", propertyType.PropertyEditorAlias),
new XElement("Definition", definition.Key),
new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name),
new XElement("Mandatory", propertyType.Mandatory.ToString()),
new XElement("MandatoryMessage", propertyType.MandatoryMessage),
new XElement("Validation", propertyType.ValidationRegExp),
new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage),
new XElement("LabelOnTop", propertyType.LabelOnTop),
new XElement("Description", new XCData(propertyType.Description ?? string.Empty)));
genericProperties.Add(genericProperty);
}
var tabs = new XElement("Tabs");
foreach (var propertyGroup in mediaType.PropertyGroups)
{
var tab = new XElement("Tab",
new XElement("Id", propertyGroup.Id.ToString(CultureInfo.InvariantCulture)),
new XElement("Caption", propertyGroup.Name),
new XElement("SortOrder", propertyGroup.SortOrder));
tabs.Add(tab);
}
var xml = new XElement("MediaType",
info,
structure,
genericProperties,
tabs);
return xml;
}
/// <summary>
/// Exports a list of <see cref="IMacro"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="macros">Macros to export</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IMacro objects</returns>
public XElement Serialize(IEnumerable<IMacro> macros)
{
var xml = new XElement("Macros");
foreach (var item in macros)
{
xml.Add(Serialize(item));
}
return xml;
}
public XElement Serialize(IMacro macro)
{
var xml = new XElement("macro");
xml.Add(new XElement("name", macro.Name));
xml.Add(new XElement("alias", macro.Alias));
xml.Add(new XElement("macroSource", macro.MacroSource));
xml.Add(new XElement("useInEditor", macro.UseInEditor.ToString()));
xml.Add(new XElement("dontRender", macro.DontRender.ToString()));
xml.Add(new XElement("refreshRate", macro.CacheDuration.ToString(CultureInfo.InvariantCulture)));
xml.Add(new XElement("cacheByMember", macro.CacheByMember.ToString()));
xml.Add(new XElement("cacheByPage", macro.CacheByPage.ToString()));
var properties = new XElement("properties");
foreach (var property in macro.Properties)
{
properties.Add(new XElement("property",
new XAttribute("name", property.Name),
new XAttribute("alias", property.Alias),
new XAttribute("sortOrder", property.SortOrder),
new XAttribute("propertyType", property.EditorAlias)));
}
xml.Add(properties);
return xml;
}
public XElement Serialize(IContentType contentType)
{
var info = new XElement("Info",
new XElement("Name", contentType.Name),
new XElement("Alias", contentType.Alias),
new XElement("Key", contentType.Key),
new XElement("Icon", contentType.Icon),
new XElement("Thumbnail", contentType.Thumbnail),
new XElement("Description", contentType.Description),
new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
new XElement("IsListView", contentType.IsContainer.ToString()),
new XElement("IsElement", contentType.IsElement.ToString()),
new XElement("Variations", contentType.Variations.ToString()));
var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
if(masterContentType != null)
info.Add(new XElement("Master", masterContentType.Alias));
var compositionsElement = new XElement("Compositions");
var compositions = contentType.ContentTypeComposition;
foreach (var composition in compositions)
{
compositionsElement.Add(new XElement("Composition", composition.Alias));
}
info.Add(compositionsElement);
var allowedTemplates = new XElement("AllowedTemplates");
foreach (var template in contentType.AllowedTemplates)
{
allowedTemplates.Add(new XElement("Template", template.Alias));
}
info.Add(allowedTemplates);
if (contentType.DefaultTemplate != null && contentType.DefaultTemplate.Id != 0)
info.Add(new XElement("DefaultTemplate", contentType.DefaultTemplate.Alias));
else
info.Add(new XElement("DefaultTemplate", ""));
var structure = new XElement("Structure");
foreach (var allowedType in contentType.AllowedContentTypes)
{
structure.Add(new XElement("DocumentType", allowedType.Alias));
}
var genericProperties = new XElement("GenericProperties"); // actually, all of them
foreach (var propertyType in contentType.PropertyTypes)
{
var definition = _dataTypeService.GetDataType(propertyType.DataTypeId);
var propertyGroup = propertyType.PropertyGroupId == null // true generic property
? null
: contentType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value);
var genericProperty = new XElement("GenericProperty",
new XElement("Name", propertyType.Name),
new XElement("Alias", propertyType.Alias),
new XElement("Key", propertyType.Key),
new XElement("Type", propertyType.PropertyEditorAlias),
new XElement("Definition", definition.Key),
new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name),
new XElement("SortOrder", propertyType.SortOrder),
new XElement("Mandatory", propertyType.Mandatory.ToString()),
new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()),
propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null,
propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null,
propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null,
propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null,
new XElement("Variations", propertyType.Variations.ToString()));
genericProperties.Add(genericProperty);
}
var tabs = new XElement("Tabs");
foreach (var propertyGroup in contentType.PropertyGroups)
{
var tab = new XElement("Tab",
new XElement("Id", propertyGroup.Id.ToString(CultureInfo.InvariantCulture)),
new XElement("Caption", propertyGroup.Name),
new XElement("SortOrder", propertyGroup.SortOrder));
tabs.Add(tab);
}
var xml = new XElement("DocumentType",
info,
structure,
genericProperties,
tabs);
var folderNames = string.Empty;
//don't add folders if this is a child doc type
if (contentType.Level != 1 && masterContentType == null)
{
//get URL encoded folder names
var folders = _contentTypeService.GetContainers(contentType)
.OrderBy(x => x.Level)
.Select(x => WebUtility.UrlEncode(x.Name));
folderNames = string.Join("/", folders.ToArray());
}
if (string.IsNullOrWhiteSpace(folderNames) == false)
xml.Add(new XAttribute("Folders", folderNames));
return xml;
}
// exports an IContentBase (IContent, IMedia or IMember) as an XElement.
private XElement SerializeContentBase(IContentBase contentBase, string urlValue, string nodeName, bool published)
{
var xml = new XElement(nodeName,
new XAttribute("id", contentBase.Id),
new XAttribute("key", contentBase.Key),
new XAttribute("parentID", contentBase.Level > 1 ? contentBase.ParentId : -1),
new XAttribute("level", contentBase.Level),
new XAttribute("creatorID", contentBase.CreatorId),
new XAttribute("sortOrder", contentBase.SortOrder),
new XAttribute("createDate", contentBase.CreateDate.ToString("s")),
new XAttribute("updateDate", contentBase.UpdateDate.ToString("s")),
new XAttribute("nodeName", contentBase.Name),
new XAttribute("urlName", urlValue),
new XAttribute("path", contentBase.Path),
new XAttribute("isDoc", ""));
foreach (var property in contentBase.Properties)
xml.Add(SerializeProperty(property, published));
return xml;
}
// exports a property as XElements.
private IEnumerable<XElement> SerializeProperty(IProperty property, bool published)
{
var propertyType = property.PropertyType;
// get the property editor for this property and let it convert it to the xml structure
var propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias];
return propertyEditor == null
? Array.Empty<XElement>()
: propertyEditor.GetValueEditor().ConvertDbToXml(property, _dataTypeService, _localizationService, published);
}
// exports an IContent item descendants.
private void SerializeChildren(IEnumerable<IContent> children, XElement xml, bool published)
{
foreach (var child in children)
{
// add the child xml
var childXml = Serialize(child, published);
xml.Add(childXml);
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while(page * pageSize < total)
{
var grandChildren = _contentService.GetPagedChildren(child.Id, page++, pageSize, out total);
// recurse
SerializeChildren(grandChildren, childXml, published);
}
}
}
// exports an IMedia item descendants.
private void SerializeChildren(IEnumerable<IMedia> children, XElement xml)
{
foreach (var child in children)
{
// add the child xml
var childXml = Serialize(child);
xml.Add(childXml);
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while (page * pageSize < total)
{
var grandChildren = _mediaService.GetPagedChildren(child.Id, page++, pageSize, out total);
// recurse
SerializeChildren(grandChildren, childXml);
}
}
}
}
}