From d0787e92fec9e6525e91dc01e1c4c1b3bf0ba633 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Wed, 20 Mar 2013 13:51:46 -0100 Subject: [PATCH] Implementing PackagingService and moving Import Export methods to this service to have it specialized and centralized around packaging operations. --- src/Umbraco.Core/Models/ContentTypeSort.cs | 11 + .../Repositories/TemplateRepository.cs | 4 +- src/Umbraco.Core/Services/ContentService.cs | 182 +------ .../Services/ContentTypeService.cs | 57 +- src/Umbraco.Core/Services/DataTypeService.cs | 88 ++- src/Umbraco.Core/Services/IContentService.cs | 7 - .../Services/IContentTypeService.cs | 7 - src/Umbraco.Core/Services/IDataTypeService.cs | 14 + src/Umbraco.Core/Services/PackagingService.cs | 499 ++++++++++++++++++ src/Umbraco.Core/Services/ServiceContext.cs | 12 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Services/Importing/ContentImportTests.cs | 19 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +- .../businesslogic/Packager/Installer.cs | 2 +- 14 files changed, 658 insertions(+), 249 deletions(-) create mode 100644 src/Umbraco.Core/Services/PackagingService.cs diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index 009abc110c..6790f15fb0 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -8,6 +8,17 @@ namespace Umbraco.Core.Models /// public class ContentTypeSort : IValueObject { + public ContentTypeSort() + { + } + + public ContentTypeSort(Lazy id, int sortOrder, string @alias) + { + Id = id; + SortOrder = sortOrder; + Alias = alias; + } + /// /// Gets or sets the Id of the ContentType /// diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 90bb835e8c..a83bdca872 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -328,8 +328,8 @@ namespace Umbraco.Core.Persistence.Repositories public ITemplate Get(string alias) { - var sql = GetBaseQuery(false); - sql.Where("cmsTemplate.alias = @Alias", new { Alias = alias }); + var sql = GetBaseQuery(false) + .Where(x => x.Alias == alias); var dto = Database.Fetch(sql).FirstOrDefault(); diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 80ee4813e2..c645d60175 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1001,166 +1001,6 @@ namespace Umbraco.Core.Services return content; } - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// An enumrable list of generated content - public IEnumerable Import(XElement element) - { - var name = element.Name.LocalName; - if (name.Equals("DocumentSet")) - { - //This is a regular deep-structured import - var roots = from doc in element.Elements() - where (string) doc.Attribute("isDoc") == "" - select doc; - - var contents = ParseRootXml(roots); - Save(contents); - - return contents; - } - - var attribute = element.Attribute("isDoc"); - if (attribute != null) - { - //This is a single doc import - var elements = new List { element }; - var contents = ParseRootXml(elements); - Save(contents); - - return contents; - } - - throw new ArgumentException( - "The passed in XElement is not valid! It does not contain a root element called "+ - "'DocumentSet' (for structured imports) nor is the first element a Document (for single document import)."); - } - - private IEnumerable ParseRootXml(IEnumerable roots) - { - var contentTypes = new Dictionary(); - var contents = new List(); - foreach (var root in roots) - { - bool isLegacySchema = root.Name.LocalName.ToLowerInvariant().Equals("node"); - string contentTypeAlias = isLegacySchema - ? root.Attribute("nodeTypeAlias").Value - : root.Name.LocalName; - - if (contentTypes.ContainsKey(contentTypeAlias) == false) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - contentTypes.Add(contentTypeAlias, contentType); - } - - var content = CreateContentFromXml(root, contentTypes[contentTypeAlias], null, -1, isLegacySchema); - 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, contentTypes, isLegacySchema)); - } - return contents; - } - - private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, Dictionary contentTypes, bool isLegacySchema) - { - var list = new List(); - foreach (var child in children) - { - string contentTypeAlias = isLegacySchema - ? child.Attribute("nodeTypeAlias").Value - : child.Name.LocalName; - - if (contentTypes.ContainsKey(contentTypeAlias) == false) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - contentTypes.Add(contentTypeAlias, contentType); - } - - //Create and add the child to the list - var content = CreateContentFromXml(child, contentTypes[contentTypeAlias], parent, default(int), isLegacySchema); - list.Add(content); - - //Recursive call - XElement child1 = child; - var grandChildren = from grand in child1.Elements() - where (string) grand.Attribute("isDoc") == "" - select grand; - - if (grandChildren.Any()) - list.AddRange(CreateContentFromXml(grandChildren, content, contentTypes, isLegacySchema)); - } - - return list; - } - - private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId, bool isLegacySchema) - { - var id = element.Attribute("id").Value; - var level = element.Attribute("level").Value; - var sortOrder = element.Attribute("sortOrder").Value; - var nodeName = element.Attribute("nodeName").Value; - var path = element.Attribute("path").Value; - var template = element.Attribute("template").Value; - - var properties = from property in element.Elements() - where property.Attribute("isDoc") == null - select property; - - IContent content = parent == null - ? new Content(nodeName, parentId, contentType) - { - Level = int.Parse(level), - SortOrder = int.Parse(sortOrder) - } - : new Content(nodeName, parent, contentType) - { - Level = int.Parse(level), - SortOrder = int.Parse(sortOrder) - }; - - foreach (var property in properties) - { - string propertyTypeAlias = isLegacySchema ? property.Attribute("alias").Value : property.Name.LocalName; - if(content.HasProperty(propertyTypeAlias)) - content.SetValue(propertyTypeAlias, property.Value); - } - - return content; - } - - private IContentType FindContentTypeByAlias(string contentTypeAlias) - { - using (var repository = _repositoryFactory.CreateContentTypeRepository(_uowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); - var types = repository.GetByQuery(query); - - if (!types.Any()) - throw new Exception( - string.Format("No ContentType matching the passed in Alias: '{0}' was found", - contentTypeAlias)); - - var contentType = types.First(); - - if (contentType == null) - throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", - contentTypeAlias)); - - return contentType; - } - } - - public XElement Export(IContent content, bool deep = false) - { - throw new NotImplementedException(); - } - #region Internal Methods /// @@ -1607,6 +1447,28 @@ namespace Umbraco.Core.Services return true; } + private IContentType FindContentTypeByAlias(string contentTypeAlias) + { + using (var repository = _repositoryFactory.CreateContentTypeRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); + var types = repository.GetByQuery(query); + + if (!types.Any()) + throw new Exception( + string.Format("No ContentType matching the passed in Alias: '{0}' was found", + contentTypeAlias)); + + var contentType = types.First(); + + if (contentType == null) + throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", + contentTypeAlias)); + + return contentType; + } + } + #endregion #region Event Handlers diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 6a36cc2a76..3dfef9a3a8 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; using System.Xml.Linq; @@ -474,61 +475,7 @@ namespace Umbraco.Core.Services } return dtd.ToString(); } - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// An enumrable list of generated ContentTypes - public List Import(XElement element) - { - var name = element.Name.LocalName; - if (name.Equals("DocumentTypes") == false) - { - throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DocumentTypes'."); - } - - var list = new List(); - var documentTypes = from doc in element.Elements("DocumentType") select doc; - foreach (var documentType in documentTypes) - { - //TODO Check if the ContentType already exists by looking up the alias - list.Add(CreateContentTypeFromXml(documentType)); - } - - Save(list); - return list; - } - - private IContentType CreateContentTypeFromXml(XElement documentType) - { - var infoElement = documentType.Element("Info"); - var name = infoElement.Element("Name").Value; - var alias = infoElement.Element("Alias").Value; - var masterElement = infoElement.Element("Master");//Name of the master corresponds to the parent - var icon = infoElement.Element("Icon").Value; - var thumbnail = infoElement.Element("Thumbnail").Value; - var description = infoElement.Element("Description").Value; - var allowAtRoot = infoElement.Element("AllowAtRoot").Value; - var defaultTemplate = infoElement.Element("DefaultTemplate").Value; - var allowedTemplatesElement = infoElement.Elements("AllowedTemplates"); - - var structureElement = documentType.Element("Structure"); - var genericPropertiesElement = documentType.Element("GenericProperties"); - var tabElement = documentType.Element("Tab"); - - var contentType = new ContentType(-1) - { - Alias = alias, - Name = name, - Icon = icon, - Thumbnail = thumbnail, - AllowedAsRoot = allowAtRoot.ToLowerInvariant().Equals("true"), - Description = description - }; - return contentType; - } - + #region Event Handlers /// diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 50ef680d1c..2142c5ca70 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using Umbraco.Core.Auditing; using Umbraco.Core.Events; using Umbraco.Core.Models; @@ -19,6 +20,7 @@ namespace Umbraco.Core.Services { private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); public DataTypeService() : this(new RepositoryFactory()) @@ -121,18 +123,84 @@ namespace Umbraco.Core.Services { if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; - - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - uow.Commit(); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); - } + using (new WriteLock(Locker)) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + uow.Commit(); - Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); + } + } + + Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + public void Save(IEnumerable dataTypeDefinitions, int userId = 0) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinitions), this)) + return; + + using (new WriteLock(Locker)) + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + foreach (var dataTypeDefinition in dataTypeDefinitions) + { + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + } + uow.Commit(); + + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); + } + } + Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); + } + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + public void SavePreValues(int id, IEnumerable values) + { + using (new WriteLock(Locker)) + { + using (var uow = _uowProvider.GetUnitOfWork()) + { + var sortOrderObj = + uow.Database.ExecuteScalar( + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = id }); + int sortOrder; + if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) + { + sortOrder = 1; + } + + using (var transaction = uow.Database.GetTransaction()) + { + foreach (var value in values) + { + var dto = new DataTypePreValueDto { DataTypeNodeId = id, Value = value, SortOrder = sortOrder }; + uow.Database.Insert(dto); + sortOrder++; + } + + transaction.Complete(); + } + } + } } /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 9cc8edc5df..08413d6f61 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -288,12 +288,5 @@ namespace Umbraco.Core.Services /// to check if anscestors are published /// True if the Content can be published, otherwise False bool IsPublishable(IContent content); - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// An enumrable list of generated content - IEnumerable Import(XElement element); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 4be9a9b492..9e7849e33c 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -150,12 +150,5 @@ namespace Umbraco.Core.Services /// Id of the /// True if the media type has any children otherwise False bool MediaTypeHasChildren(int id); - - /// - /// Imports and saves package xml as - /// - /// Xml to import - /// An enumrable list of generated ContentTypes - List Import(XElement element); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 15ee44298b..4bef5430ae 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -38,6 +38,13 @@ namespace Umbraco.Core.Services /// Id of the user issueing the save void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0); + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + void Save(IEnumerable dataTypeDefinitions, int userId = 0); + /// /// Deletes an /// @@ -75,5 +82,12 @@ namespace Umbraco.Core.Services /// Id of the to retrieve prevalues from /// An enumerable list of string values IEnumerable GetPreValuesByDataTypeId(int id); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + void SavePreValues(int id, IEnumerable values); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs new file mode 100644 index 0000000000..b25ff04d67 --- /dev/null +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Xml.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + public class PackagingService : IService + { + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly IMediaService _mediaService; + private readonly IDataTypeService _dataTypeService; + private readonly IFileService _fileService; + private readonly RepositoryFactory _repositoryFactory; + private readonly IDatabaseUnitOfWorkProvider _uowProvider; + private Dictionary _importedContentTypes; + //Support recursive locks because some of the methods that require locking call other methods that require locking. + //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + + public PackagingService(IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, IFileService fileService, RepositoryFactory repositoryFactory, IDatabaseUnitOfWorkProvider uowProvider) + { + _contentService = contentService; + _contentTypeService = contentTypeService; + _mediaService = mediaService; + _dataTypeService = dataTypeService; + _fileService = fileService; + _repositoryFactory = repositoryFactory; + _uowProvider = uowProvider; + + _importedContentTypes = new Dictionary(); + } + + #region Content + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// + /// An enumrable list of generated content + public IEnumerable ImportContent(XElement element, int userId = 0) + { + var name = element.Name.LocalName; + if (name.Equals("DocumentSet")) + { + //This is a regular deep-structured import + var roots = from doc in element.Elements() + where (string)doc.Attribute("isDoc") == "" + select doc; + + var contents = ParseDocumentRootXml(roots); + _contentService.Save(contents, userId); + + return contents; + } + + var attribute = element.Attribute("isDoc"); + if (attribute != null) + { + //This is a single doc import + var elements = new List { element }; + var contents = ParseDocumentRootXml(elements); + _contentService.Save(contents, userId); + + return contents; + } + + throw new ArgumentException( + "The passed in XElement is not valid! It does not contain a root element called " + + "'DocumentSet' (for structured imports) nor is the first element a Document (for single document import)."); + } + + private IEnumerable ParseDocumentRootXml(IEnumerable roots) + { + var contents = new List(); + foreach (var root in roots) + { + bool isLegacySchema = root.Name.LocalName.ToLowerInvariant().Equals("node"); + string contentTypeAlias = isLegacySchema + ? root.Attribute("nodeTypeAlias").Value + : root.Name.LocalName; + + if (_importedContentTypes.ContainsKey(contentTypeAlias) == false) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + _importedContentTypes.Add(contentTypeAlias, contentType); + } + + var content = CreateContentFromXml(root, _importedContentTypes[contentTypeAlias], null, -1, isLegacySchema); + 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)); + } + return contents; + } + + private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, bool isLegacySchema) + { + var list = new List(); + foreach (var child in children) + { + string contentTypeAlias = isLegacySchema + ? child.Attribute("nodeTypeAlias").Value + : child.Name.LocalName; + + if (_importedContentTypes.ContainsKey(contentTypeAlias) == false) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + _importedContentTypes.Add(contentTypeAlias, contentType); + } + + //Create and add the child to the list + var content = CreateContentFromXml(child, _importedContentTypes[contentTypeAlias], parent, default(int), isLegacySchema); + list.Add(content); + + //Recursive call + XElement child1 = child; + var grandChildren = from grand in child1.Elements() + where (string)grand.Attribute("isDoc") == "" + select grand; + + if (grandChildren.Any()) + list.AddRange(CreateContentFromXml(grandChildren, content, isLegacySchema)); + } + + return list; + } + + private IContent CreateContentFromXml(XElement element, IContentType contentType, IContent parent, int parentId, bool isLegacySchema) + { + var id = element.Attribute("id").Value; + var level = element.Attribute("level").Value; + var sortOrder = element.Attribute("sortOrder").Value; + var nodeName = element.Attribute("nodeName").Value; + var path = element.Attribute("path").Value; + var template = element.Attribute("template").Value; + + var properties = from property in element.Elements() + where property.Attribute("isDoc") == null + select property; + + IContent content = parent == null + ? new Content(nodeName, parentId, contentType) + { + Level = int.Parse(level), + SortOrder = int.Parse(sortOrder) + } + : new Content(nodeName, parent, contentType) + { + Level = int.Parse(level), + SortOrder = int.Parse(sortOrder) + }; + + foreach (var property in properties) + { + string propertyTypeAlias = isLegacySchema ? property.Attribute("alias").Value : property.Name.LocalName; + if (content.HasProperty(propertyTypeAlias)) + content.SetValue(propertyTypeAlias, property.Value); + } + + return content; + } + + public XElement Export(IContent content, bool deep = false) + { + throw new NotImplementedException(); + } + #endregion + + #region ContentTypes + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// + /// An enumrable list of generated ContentTypes + public IEnumerable ImportContentTypes(XElement element, int userId = 0) + { + var name = element.Name.LocalName; + if (name.Equals("DocumentTypes") == false) + { + throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DocumentTypes'."); + } + + _importedContentTypes = new Dictionary(); + var documentTypes = (from doc in element.Elements("DocumentType") select doc).ToList(); + //NOTE it might be an idea to sort the doctype XElements based on dependencies + //before creating the doc types - should also allow for a better structure/inheritance support. + foreach (var documentType in documentTypes) + { + var alias = documentType.Element("Info").Element("Alias").Value; + if (_importedContentTypes.ContainsKey(alias) == false) + { + var contentType = _contentTypeService.GetContentType(alias); + _importedContentTypes.Add(alias, contentType == null + ? CreateContentTypeFromXml(documentType) + : UpdateContentTypeFromXml(documentType, contentType)); + } + } + + var list = _importedContentTypes.Select(x => x.Value).ToList(); + _contentTypeService.Save(list, userId); + + var updatedContentTypes = new List(); + //Update the structure here - we can't do it untill all DocTypes have been created + foreach (var documentType in documentTypes) + { + var alias = documentType.Element("Info").Element("Alias").Value; + var structureElement = documentType.Element("Structure"); + //Ensure that we only update ContentTypes which has actual structure-elements + if (structureElement == null || structureElement.Elements("DocumentType").Any() == false) continue; + + var updated = UpdateContentTypesStructure(_importedContentTypes[alias], structureElement); + updatedContentTypes.Add(updated); + } + //Update ContentTypes with a newly added structure/list of allowed children + if(updatedContentTypes.Any()) + _contentTypeService.Save(updatedContentTypes, userId); + + return list; + } + + private IContentType CreateContentTypeFromXml(XElement documentType) + { + var infoElement = documentType.Element("Info"); + + //Name of the master corresponds to the parent + var masterElement = infoElement.Element("Master"); + IContentType parent = null; + if (masterElement != null) + { + var masterAlias = masterElement.Value; + parent = _importedContentTypes.ContainsKey(masterAlias) + ? _importedContentTypes[masterAlias] + : _contentTypeService.GetContentType(masterAlias); + } + + var contentType = parent == null + ? new ContentType(-1) + { + Alias = infoElement.Element("Alias").Value + } + : new ContentType(parent) + { + Alias = infoElement.Element("Alias").Value + }; + + return UpdateContentTypeFromXml(documentType, contentType); + } + + private IContentType UpdateContentTypeFromXml(XElement documentType, IContentType contentType) + { + var infoElement = documentType.Element("Info"); + var defaultTemplateElement = infoElement.Element("DefaultTemplate"); + + contentType.Name = infoElement.Element("Name").Value; + contentType.Icon = infoElement.Element("Icon").Value; + contentType.Thumbnail = infoElement.Element("Thumbnail").Value; + contentType.Description = infoElement.Element("Description").Value; + contentType.AllowedAsRoot = infoElement.Element("AllowAtRoot").Value.ToLowerInvariant().Equals("true"); + + UpdateContentTypesAllowedTemplates(contentType, infoElement.Element("AllowedTemplates"), defaultTemplateElement); + UpdateContentTypesTabs(contentType, documentType.Element("Tab")); + UpdateContentTypesProperties(contentType, documentType.Element("GenericProperties")); + + return contentType; + } + + private void UpdateContentTypesAllowedTemplates(IContentType contentType, + XElement allowedTemplatesElement, XElement defaultTemplateElement) + { + if (allowedTemplatesElement != null && allowedTemplatesElement.Elements("Template").Any()) + { + var allowedTemplates = contentType.AllowedTemplates.ToList(); + foreach (var templateElement in allowedTemplatesElement.Elements("Template")) + { + var alias = templateElement.Value; + var template = _fileService.GetTemplate(alias); + if (template != null) + { + allowedTemplates.Add(template); + } + else + { + LogHelper.Warn( + string.Format( + "Packager: Error handling allowed templates. Template with alias '{0}' could not be found.", + alias)); + } + } + + contentType.AllowedTemplates = allowedTemplates; + } + + var defaultTemplate = _fileService.GetTemplate(defaultTemplateElement.Value); + if (defaultTemplate != null) + { + contentType.SetDefaultTemplate(defaultTemplate); + } + else + { + LogHelper.Warn( + string.Format( + "Packager: Error handling default template. Default template with alias '{0}' could not be found.", + defaultTemplateElement.Value)); + } + } + + private void UpdateContentTypesTabs(IContentType contentType, XElement tabElement) + { + if(tabElement == null) + return; + + var tabs = tabElement.Elements("Tab"); + foreach (var tab in tabs) + { + var id = tab.Element("Id").Value;//Do we need to use this for tracking? + var caption = tab.Element("Caption").Value; + if (contentType.PropertyGroups.Contains(caption) == false) + { + contentType.AddPropertyGroup(caption); + } + } + } + + private void UpdateContentTypesProperties(IContentType contentType, XElement genericPropertiesElement) + { + var properties = genericPropertiesElement.Elements("GenericProperty"); + foreach (var property in properties) + { + var dataTypeId = new Guid(property.Element("Type").Value);//The DataType's Control Id + var dataTypeDefinitionId = new Guid(property.Element("Definition").Value);//Unique Id for a DataTypeDefinition + + var dataTypeDefinition = _dataTypeService.GetDataTypeDefinitionById(dataTypeDefinitionId); + //If no DataTypeDefinition with the guid from the xml wasn't found OR the ControlId on the DataTypeDefinition didn't match the DataType Id + //We look up a DataTypeDefinition that matches + if (dataTypeDefinition == null || dataTypeDefinition.ControlId != dataTypeId) + { + var dataTypeDefinitions = _dataTypeService.GetDataTypeDefinitionByControlId(dataTypeId); + if (dataTypeDefinitions != null && dataTypeDefinitions.Any()) + { + dataTypeDefinition = dataTypeDefinitions.First(); + } + else + { + throw new Exception( + String.Format( + "Packager: Error handling creation of PropertyType '{0}'. Could not find DataTypeDefintion with unique id '{1}' nor one referencing the DataType with control id '{2}'.", + property.Element("Name").Value, dataTypeDefinitionId, dataTypeId)); + } + } + + var propertyType = new PropertyType(dataTypeDefinition) + { + Alias = property.Element("Alias").Value, + Name = property.Element("Name").Value, + Description = property.Element("Description").Value, + Mandatory = property.Element("Mandatory").Value.ToLowerInvariant().Equals("true"), + ValidationRegExp = property.Element("Validation").Value + }; + + var helpTextElement = property.Element("HelpText"); + if (helpTextElement != null) + { + propertyType.HelpText = helpTextElement.Value; + } + + var tab = property.Element("Tab").Value; + if (string.IsNullOrEmpty(tab)) + { + contentType.AddPropertyType(propertyType); + } + else + { + contentType.AddPropertyType(propertyType, tab); + } + } + } + + private IContentType UpdateContentTypesStructure(IContentType contentType, XElement structureElement) + { + var allowedChildren = contentType.AllowedContentTypes.ToList(); + int sortOrder = allowedChildren.Any() ? allowedChildren.Last().SortOrder : 0; + foreach (var element in structureElement.Elements("DocumentType")) + { + var alias = element.Value; + if (_importedContentTypes.ContainsKey(alias)) + { + var allowedChild = _importedContentTypes[alias]; + allowedChildren.Add(new ContentTypeSort(new Lazy(() => allowedChild.Id), sortOrder, allowedChild.Alias)); + sortOrder++; + } + else + { + LogHelper.Warn( + string.Format( + "Packager: Error handling DocumentType structure. DocumentType with alias '{0}' could not be found and was not added to the structure for '{1}'.", + alias, contentType.Alias)); + } + } + + contentType.AllowedContentTypes = allowedChildren; + return contentType; + } + + private IContentType FindContentTypeByAlias(string contentTypeAlias) + { + using (var repository = _repositoryFactory.CreateContentTypeRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); + var types = repository.GetByQuery(query); + + if (!types.Any()) + throw new Exception( + string.Format("No ContentType matching the passed in Alias: '{0}' was found", + contentTypeAlias)); + + var contentType = types.First(); + + if (contentType == null) + throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", + contentTypeAlias)); + + return contentType; + } + } + #endregion + + #region DataTypes + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// + /// An enumrable list of generated DataTypeDefinitions + public IEnumerable ImportDataTypeDefinitions(XElement element, int userId = 0) + { + var name = element.Name.LocalName; + if (name.Equals("DataTypes") == false) + { + throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DataTypes'."); + } + + var dataTypes = new Dictionary(); + var dataTypeElements = element.Elements("DataType").ToList(); + foreach (var dataTypeElement in dataTypeElements) + { + var dataTypeDefinitionName = dataTypeElement.Attribute("Name").Value; + var dataTypeId = new Guid(dataTypeElement.Attribute("Id").Value); + var dataTypeDefinitionId = new Guid(dataTypeElement.Attribute("Definition").Value); + + var definition = _dataTypeService.GetDataTypeDefinitionById(dataTypeDefinitionId); + if (definition == null) + { + var dataTypeDefinition = new DataTypeDefinition(-1, dataTypeId) { Key = dataTypeDefinitionId, Name = dataTypeDefinitionName }; + dataTypes.Add(dataTypeDefinitionName, dataTypeDefinition); + } + } + + var list = dataTypes.Select(x => x.Value).ToList(); + _dataTypeService.Save(list, userId); + + SavePrevaluesFromXml(list, dataTypeElements); + + return list; + } + + private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) + { + foreach (var dataTypeElement in dataTypeElements) + { + var prevaluesElement = dataTypeElement.Element("PreValues"); + if (prevaluesElement == null) continue; + + var dataTypeDefinitionName = dataTypeElement.Attribute("Name").Value; + var dataTypeDefinition = dataTypes.First(x => x.Name == dataTypeDefinitionName); + + var values = prevaluesElement.Elements("PreValue").Select(prevalue => prevalue.Attribute("Value").Value).ToList(); + _dataTypeService.SavePreValues(dataTypeDefinition.Id, values); + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 9406fb015e..d9267de965 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -20,6 +20,7 @@ namespace Umbraco.Core.Services private Lazy _dataTypeService; private Lazy _fileService; private Lazy _localizationService; + private Lazy _packagingService; /// /// Constructor @@ -70,6 +71,9 @@ namespace Umbraco.Core.Services if(_localizationService == null) _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory.Value)); + + if(_packagingService == null) + _packagingService = new Lazy(() => new PackagingService(_contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _fileService.Value, repositoryFactory.Value, provider)); } /// @@ -120,6 +124,14 @@ namespace Umbraco.Core.Services get { return _mediaService.Value; } } + /// + /// Gets the + /// + public PackagingService PackagingService + { + get { return _packagingService.Value; } + } + /// /// Gets the /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 64136cb2d7..52951c8642 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -654,6 +654,7 @@ + diff --git a/src/Umbraco.Tests/Services/Importing/ContentImportTests.cs b/src/Umbraco.Tests/Services/Importing/ContentImportTests.cs index 4ec48504a5..590d221bc9 100644 --- a/src/Umbraco.Tests/Services/Importing/ContentImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/ContentImportTests.cs @@ -26,16 +26,21 @@ namespace Umbraco.Tests.Services.Importing string strXml = ImportResources.package; var xml = XElement.Parse(strXml); var element = xml.Descendants("DocumentTypes").First(); - var contentTypeService = ServiceContext.ContentTypeService; + var dataTypeElement = xml.Descendants("DataTypes").First(); + var packagingService = ServiceContext.PackagingService; // Act - var contentTypes = contentTypeService.Import(element); + var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement); + var contentTypes = packagingService.ImportContentTypes(element); var numberOfDocTypes = (from doc in element.Elements("DocumentType") select doc).Count(); // Assert + Assert.That(dataTypeDefinitions, Is.Not.Null); + Assert.That(dataTypeDefinitions.Any(), Is.True); Assert.That(contentTypes, Is.Not.Null); Assert.That(contentTypes.Any(), Is.True); Assert.That(contentTypes.Count(), Is.EqualTo(numberOfDocTypes)); + Assert.That(contentTypes.Count(x => x.ParentId == -1), Is.EqualTo(1)); } [Test] @@ -44,20 +49,22 @@ namespace Umbraco.Tests.Services.Importing // Arrange string strXml = ImportResources.package; var xml = XElement.Parse(strXml); + var dataTypeElement = xml.Descendants("DataTypes").First(); var docTypesElement = xml.Descendants("DocumentTypes").First(); var element = xml.Descendants("DocumentSet").First(); - var contentService = ServiceContext.ContentService; - var contentTypeService = ServiceContext.ContentTypeService; + var packagingService = ServiceContext.PackagingService; // Act - var contentTypes = contentTypeService.Import(docTypesElement); - var contents = contentService.Import(element); + var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement); + var contentTypes = packagingService.ImportContentTypes(docTypesElement); + var contents = packagingService.ImportContent(element); var numberOfDocs = (from doc in element.Descendants() where (string) doc.Attribute("isDoc") == "" select doc).Count(); // Assert Assert.That(contents, Is.Not.Null); + Assert.That(dataTypeDefinitions.Any(), Is.True); Assert.That(contentTypes.Any(), Is.True); Assert.That(contents.Any(), Is.True); Assert.That(contents.Count(), Is.EqualTo(numberOfDocs)); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 10e36c517f..1c1c41e517 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -424,7 +424,9 @@ - + + Designer + diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 69b1c5899a..6d60107b84 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -762,7 +762,7 @@ namespace umbraco.cms.businesslogic.packager tabNames += tabs[t].Caption + ";"; - + //So the Tab is added to the DocumentType and then to this Hashtable, but its never used anywhere - WHY? Hashtable ht = new Hashtable(); foreach (XmlNode t in n.SelectNodes("Tabs/Tab")) {