using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Web; using System.Xml.Linq; using Newtonsoft.Json; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; namespace Umbraco.Core.Services { //TODO: Move the rest of the logic for the PackageService.Export methods to here! /// /// A helper class to serialize entities to XML /// internal class EntityXmlSerializer { /// /// Exports an IContent item as an XElement. /// public static XElement Serialize( IContentService contentService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IContent content, bool published, bool withDescendants = false) //fixme take care of usage! only used for the packager { if (contentService == null) throw new ArgumentNullException(nameof(contentService)); 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 (content == null) throw new ArgumentNullException(nameof(content)); if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders)); // nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); var nodeName = content.ContentType.Alias.ToSafeAliasWithForcingCheck(); var xml = SerializeContentBase(dataTypeService, localizationService, content, content.GetUrlSegment(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.Template?.Id.ToString(CultureInfo.InvariantCulture) ?? "0")); 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(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, children, xml, published); } } return xml; } /// /// Exports an IMedia item as an XElement. /// public static XElement Serialize( IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, 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)); // nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); var nodeName = media.ContentType.Alias.ToSafeAliasWithForcingCheck(); const bool published = false; // always false for media var xml = SerializeContentBase(dataTypeService, localizationService, media, media.GetUrlSegment(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("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(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, children, xml); } } return xml; } /// /// Exports an IMember item as an XElement. /// public static XElement Serialize( IDataTypeService dataTypeService, ILocalizationService localizationService, IMember member) { // nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias); var nodeName = member.ContentType.Alias.ToSafeAliasWithForcingCheck(); const bool published = false; // always false for member var xml = SerializeContentBase(dataTypeService, localizationService, 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; } public XElement Serialize(IDataTypeService dataTypeService, 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", JsonConvert.SerializeObject(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 => HttpUtility.UrlEncode(x.Name)); folderNames = string.Join("/", folders.ToArray()); } if (string.IsNullOrWhiteSpace(folderNames) == false) xml.Add(new XAttribute("Folders", folderNames)); return xml; } public 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; } 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; } public XElement Serialize(IDataTypeService dataTypeService, 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("Validation", propertyType.ValidationRegExp), new XElement("Description", new XCData(propertyType.Description))); 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; } 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("macroType", macro.MacroType)); 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(IDataTypeService dataTypeService, IContentTypeService contentTypeService, IContentType contentType) { var info = new XElement("Info", new XElement("Name", contentType.Name), new XElement("Alias", contentType.Alias), 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())); 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("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()), propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null); 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 => HttpUtility.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 static XElement SerializeContentBase(IDataTypeService dataTypeService, ILocalizationService localizationService, 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(dataTypeService, localizationService, property, published)); return xml; } // exports a property as XElements. private static IEnumerable SerializeProperty(IDataTypeService dataTypeService, ILocalizationService localizationService, Property 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 = Current.PropertyEditors[propertyType.PropertyEditorAlias]; return propertyEditor == null ? Array.Empty() : propertyEditor.GetValueEditor().ConvertDbToXml(property, dataTypeService, localizationService, published); } // exports an IContent item descendants. private static void SerializeChildren(IContentService contentService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IEnumerable children, XElement xml, bool published) { foreach (var child in children) { // add the child xml var childXml = Serialize(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, 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(contentService, dataTypeService, userService, localizationService, urlSegmentProviders, grandChildren, childXml, published); } } } // exports an IMedia item descendants. private static void SerializeChildren(IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IEnumerable children, XElement xml) { foreach (var child in children) { // add the child xml var childXml = Serialize(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, 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(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, grandChildren, childXml); } } } } }