diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 9621ed5791..fca0d420c8 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -52,7 +52,7 @@ namespace Umbraco.Core.Models internal string NodePath { get; set; } [DataMember] - internal int MasterTemplateId { get; set; } + internal Lazy MasterTemplateId { get; set; } [DataMember] internal string MasterTemplateAlias { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index b2b1fea52f..c96d5090ea 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -28,17 +28,22 @@ namespace Umbraco.Core.Persistence.Factories public Template BuildEntity(TemplateDto dto) { - return new Template(string.Empty, dto.NodeDto.Text, dto.Alias) - { - CreateDate = dto.NodeDto.CreateDate, - Id = dto.NodeId, - Key = dto.NodeDto.UniqueId.Value, - CreatorId = dto.NodeDto.UserId.Value, - Level = dto.NodeDto.Level, - ParentId = dto.NodeDto.ParentId, - SortOrder = dto.NodeDto.SortOrder, - NodePath = dto.NodeDto.Path - }; + var template = new Template(string.Empty, dto.NodeDto.Text, dto.Alias) + { + CreateDate = dto.NodeDto.CreateDate, + Id = dto.NodeId, + Key = dto.NodeDto.UniqueId.Value, + CreatorId = dto.NodeDto.UserId.Value, + Level = dto.NodeDto.Level, + ParentId = dto.NodeDto.ParentId, + SortOrder = dto.NodeDto.SortOrder, + NodePath = dto.NodeDto.Path + }; + + if(dto.Master.HasValue) + template.MasterTemplateId = new Lazy(() => dto.Master.Value); + + return template; } public TemplateDto BuildDto(Template entity) @@ -50,6 +55,11 @@ namespace Umbraco.Core.Persistence.Factories NodeDto = BuildNodeDto(entity) }; + if (entity.MasterTemplateId != null && entity.MasterTemplateId.Value != default(int)) + { + dto.Master = entity.MasterTemplateId.Value; + } + if (entity.HasIdentity) { dto.NodeId = entity.Id; diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index a83bdca872..818ab6f147 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -71,7 +71,7 @@ namespace Umbraco.Core.Persistence.Repositories { var masterTemplate = Get(dto.Master.Value); template.MasterTemplateAlias = masterTemplate.Alias; - template.MasterTemplateId = dto.Master.Value; + template.MasterTemplateId = new Lazy(() => dto.Master.Value); } if (_viewsFileSystem.FileExists(csViewName)) @@ -172,11 +172,13 @@ namespace Umbraco.Core.Persistence.Repositories { if (entity.GetTypeOfRenderingEngine() == RenderingEngine.Mvc) { - _viewsFileSystem.AddFile(entity.Name, stream, true); + string viewName = string.Concat(entity.Alias, ".cshtml"); + _viewsFileSystem.AddFile(viewName, stream, true); } else { - _masterpagesFileSystem.AddFile(entity.Name, stream, true); + string masterpageName = string.Concat(entity.Alias, ".master"); + _masterpagesFileSystem.AddFile(masterpageName, stream, true); } } @@ -223,11 +225,13 @@ namespace Umbraco.Core.Persistence.Repositories { if (entity.GetTypeOfRenderingEngine() == RenderingEngine.Mvc) { - _viewsFileSystem.AddFile(entity.Name, stream, true); + string viewName = string.Concat(entity.Alias, ".cshtml"); + _viewsFileSystem.AddFile(viewName, stream, true); } else { - _masterpagesFileSystem.AddFile(entity.Name, stream, true); + string masterpageName = string.Concat(entity.Alias, ".master"); + _masterpagesFileSystem.AddFile(masterpageName, stream, true); } } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 2142c5ca70..0b22d74070 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -107,11 +107,26 @@ namespace Umbraco.Core.Services /// An enumerable list of string values public IEnumerable GetPreValuesByDataTypeId(int id) { - var uow = _uowProvider.GetUnitOfWork(); - var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new {Id = id}); - var list = dtos.Select(x => x.Value).ToList(); - uow.Dispose(); - return list; + using (var uow = _uowProvider.GetUnitOfWork()) + { + var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new {Id = id}); + var list = dtos.Select(x => x.Value).ToList(); + return list; + } + } + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + public string GetPreValueAsString(int id) + { + using (var uow = _uowProvider.GetUnitOfWork()) + { + var dto = uow.Database.FirstOrDefault("WHERE id = @Id", new { Id = id }); + return dto != null ? dto.Value : string.Empty; + } } /// diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index ace5d1d612..53ac74b3a2 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -255,6 +255,31 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Save, string.Format("Save Template performed by user"), userId, template.Id); } + /// + /// Saves a collection of objects + /// + /// List of to save + /// Optional id of the user + public void SaveTemplate(IEnumerable templates, int userId = 0) + { + if (SavingTemplate.IsRaisedEventCancelled(new SaveEventArgs(templates), this)) + return; + + var uow = _dataUowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateTemplateRepository(uow)) + { + foreach (var template in templates) + { + repository.AddOrUpdate(template); + } + uow.Commit(); + + SavedTemplate.RaiseEvent(new SaveEventArgs(templates, false), this); + } + + Audit.Add(AuditTypes.Save, string.Format("Save Template performed by user"), userId, -1); + } + /// /// Deletes a template by its alias /// diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 4bef5430ae..5fac95a25c 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -89,5 +89,12 @@ namespace Umbraco.Core.Services /// Id of the DataTypeDefinition to save PreValues for /// List of string values to save void SavePreValues(int id, IEnumerable values); + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + string GetPreValueAsString(int id); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index d4ee7fd71e..baf2126b20 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -116,5 +116,12 @@ namespace Umbraco.Core.Services /// to validate /// True if Script is valid, otherwise false bool ValidateTemplate(ITemplate template); + + /// + /// Saves a collection of objects + /// + /// List of to save + /// Optional id of the user + void SaveTemplate(IEnumerable templates, int userId = 0); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index b25ff04d67..571a883b2a 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Xml.Linq; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -178,6 +180,7 @@ namespace Umbraco.Core.Services { throw new NotImplementedException(); } + #endregion #region ContentTypes @@ -191,13 +194,15 @@ namespace Umbraco.Core.Services public IEnumerable ImportContentTypes(XElement element, int userId = 0) { var name = element.Name.LocalName; - if (name.Equals("DocumentTypes") == false) + if (name.Equals("DocumentTypes") == false && name.Equals("DocumentType")) { - throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DocumentTypes'."); + throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DocumentTypes' for multiple imports or 'DocumentType' for a single import."); } _importedContentTypes = new Dictionary(); - var documentTypes = (from doc in element.Elements("DocumentType") select doc).ToList(); + var documentTypes = name.Equals("DocumentTypes") + ? (from doc in element.Elements("DocumentType") select doc).ToList() + : new List {element.Element("DocumentType")}; //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) @@ -438,6 +443,7 @@ namespace Umbraco.Core.Services return contentType; } } + #endregion #region DataTypes @@ -495,5 +501,108 @@ namespace Umbraco.Core.Services } } #endregion + + #region Dictionary Items + #endregion + + #region Files + #endregion + + #region Languages + #endregion + + #region Macros + #endregion + + #region Media + #endregion + + #region MediaTypes + #endregion + + #region Package Manifest + #endregion + + #region Templates + + /// + /// Imports and saves package xml as + /// + /// Xml to import + /// Optional user id + /// An enumrable list of generated Templates + public IEnumerable ImportTemplates(XElement element, int userId = 0) + { + var name = element.Name.LocalName; + if (name.Equals("Templates") == false && name.Equals("Template")) + { + throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'Templates' for multiple imports or 'Template' for a single import."); + } + + var templates = new List(); + var templateElements = name.Equals("Templates") + ? (from doc in element.Elements("Template") select doc).ToList() + : new List { element.Element("Template") }; + + var fields = new List>(); + foreach (XElement tempElement in templateElements) + { + var dependencies = new List(); + if(tempElement.Element("Master") != null && string.IsNullOrEmpty(tempElement.Element("Master").Value) == false) + dependencies.Add(tempElement.Element("Master").Value); + + var field = new TopologicalSorter.DependencyField + { + Alias = tempElement.Element("Alias").Value, + Item = new Lazy(() => tempElement), + DependsOn = dependencies.ToArray() + }; + + fields.Add(field); + } + //Sort templates by dependencies to a potential master template + var sortedElements = TopologicalSorter.RetrieveMappedItems(fields); + foreach (var templateElement in sortedElements) + { + var templateName = templateElement.Element("Name").Value; + var alias = templateElement.Element("Alias").Value; + var design = templateElement.Element("Design").Value; + var masterElement = templateElement.Element("Master"); + + var isMasterPage = IsMasterPageSyntax(design); + var path = isMasterPage ? MasterpagePath(alias) : ViewPath(alias); + + var template = new Template(path, templateName, alias) { Content = design }; + if (masterElement != null && string.IsNullOrEmpty(masterElement.Value) == false) + { + template.MasterTemplateAlias = masterElement.Value; + var masterTemplate = templates.FirstOrDefault(x => x.Alias == masterElement.Value); + if(masterTemplate != null) + template.MasterTemplateId = new Lazy(() => masterTemplate.Id); + } + templates.Add(template); + } + + _fileService.SaveTemplate(templates, userId); + return templates; + } + + private bool IsMasterPageSyntax(string code) + { + return Regex.IsMatch(code, @"<%@\s*Master", RegexOptions.IgnoreCase) || + code.InvariantContains(" 0) // while vertices remain, + { + // get a vertex with no successors, or -1 + int currentVertex = NoSuccessors(); + if (currentVertex == -1) // must be a cycle + throw new Exception("Graph has cycles"); + + // insert vertex label in sorted array (start at end) + _sortedArray[_numVerts - 1] = _vertices[currentVertex]; + + DeleteVertex(currentVertex); // delete vertex + } + + // vertices all gone; return sortedArray + return _sortedArray; + } + + #endregion + + #region Private Helper Methods + + // returns vert with no successors (or -1 if no such verts) + private int NoSuccessors() + { + for (int row = 0; row < _numVerts; row++) + { + bool isEdge = false; // edge from row to column in adjMat + for (int col = 0; col < _numVerts; col++) + { + if (_matrix[row, col] > 0) // if edge to another, + { + isEdge = true; + break; // this vertex has a successor try another + } + } + if (!isEdge) // if no edges, has no successors + return row; + } + return -1; // no + } + + private void DeleteVertex(int delVert) + { + // if not last vertex, delete from vertexList + if (delVert != _numVerts - 1) + { + for (int j = delVert; j < _numVerts - 1; j++) + _vertices[j] = _vertices[j + 1]; + + for (int row = delVert; row < _numVerts - 1; row++) + MoveRowUp(row, _numVerts); + + for (int col = delVert; col < _numVerts - 1; col++) + MoveColLeft(col, _numVerts - 1); + } + _numVerts--; // one less vertex + } + + private void MoveRowUp(int row, int length) + { + for (int col = 0; col < length; col++) + _matrix[row, col] = _matrix[row + 1, col]; + } + + private void MoveColLeft(int col, int length) + { + for (int row = 0; row < length; row++) + _matrix[row, col] = _matrix[row, col + 1]; + } + + #endregion + + #region Internal Staic methods + internal static IEnumerable RetrieveMappedItems(List> fields) where T : class + { + int[] sortOrder = GetTopologicalSortOrder(fields); + var list = new List(); + for (int i = 0; i < sortOrder.Length; i++) + { + var field = fields[sortOrder[i]]; + list.Add(field.Item.Value); + } + list.Reverse(); + return list; + } + + internal static int[] GetTopologicalSortOrder(List> fields) where T : class + { + var g = new TopologicalSorter(fields.Count()); + var indexes = new Dictionary(); + + //add vertices + for (int i = 0; i < fields.Count(); i++) + { + indexes[fields[i].Alias.ToLower()] = g.AddVertex(i); + } + + //add edges + for (int i = 0; i < fields.Count; i++) + { + if (fields[i].DependsOn != null) + { + for (int j = 0; j < fields[i].DependsOn.Length; j++) + { + g.AddEdge(i, + indexes[fields[i].DependsOn[j].ToLower()]); + } + } + } + + int[] result = g.Sort(); + return result; + } + #endregion + + internal class DependencyField where T : class + { + public DependencyField() + { + } + + public DependencyField(string @alias, string[] dependsOn, Lazy item) + { + Alias = alias; + DependsOn = dependsOn; + Item = item; + } + + public string Alias { get; set; } + public string[] DependsOn { get; set; } + public Lazy Item { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 52951c8642..11fd35fb97 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -657,6 +657,7 @@ + diff --git a/src/Umbraco.Tests/Services/Importing/ContentImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs similarity index 71% rename from src/Umbraco.Tests/Services/Importing/ContentImportTests.cs rename to src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index 590d221bc9..564bfc6d3e 100644 --- a/src/Umbraco.Tests/Services/Importing/ContentImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -5,7 +5,7 @@ using NUnit.Framework; namespace Umbraco.Tests.Services.Importing { [TestFixture, RequiresSTA] - public class ContentImportTests : BaseServiceTest + public class PackageImportTests : BaseServiceTest { [SetUp] public override void Initialize() @@ -20,7 +20,26 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void ContentTypeService_Can_Import_Package_Xml() + public void PackagingService_Can_Import_Template_Package_Xml() + { + // Arrange + string strXml = ImportResources.package; + var xml = XElement.Parse(strXml); + var element = xml.Descendants("Templates").First(); + var packagingService = ServiceContext.PackagingService; + + // Act + var templates = packagingService.ImportTemplates(element); + var numberOfTemplates = (from doc in element.Elements("Template") select doc).Count(); + + // Assert + Assert.That(templates, Is.Not.Null); + Assert.That(templates.Any(), Is.True); + Assert.That(templates.Count(), Is.EqualTo(numberOfTemplates)); + } + + [Test] + public void PackagingService_Can_Import_ContentType_Package_Xml() { // Arrange string strXml = ImportResources.package; @@ -44,7 +63,7 @@ namespace Umbraco.Tests.Services.Importing } [Test] - public void ContentService_Can_Import_Package_Xml() + public void PackagingService_Can_Import_Content_Package_Xml() { // Arrange string strXml = ImportResources.package; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1c1c41e517..d42783d63e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -262,7 +262,7 @@ - + True True