diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 450cb62597..ed2ae80c6e 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.2.0 \ No newline at end of file +7.3.0 \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 1012ce793f..a6eaa7183e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.2.0"); + private static readonly Version Version = new Version("7.3.0"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs b/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs index ee24e4a0a6..8e2fe0b9f5 100644 --- a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs @@ -15,12 +15,7 @@ namespace Umbraco.Core.Models.Rdbms [Column("nodeId")] [Index(IndexTypes.UniqueNonClustered)] [ForeignKey(typeof(NodeDto), Name = "FK_cmsTemplate_umbracoNode")] - public int NodeId { get; set; } - - [Column("master")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(NodeDto), Name = "FK_cmsTemplate_cmsTemplate")] - public int? Master { get; set; } + public int NodeId { get; set; } [Column("alias")] [Length(100)] diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index a2019d605d..fcd146c1e5 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -187,20 +187,30 @@ namespace Umbraco.Core.Models public void SetMasterTemplate(ITemplate masterTemplate) { - MasterTemplateId = new Lazy(() => masterTemplate.Id); - MasterTemplateAlias = masterTemplate.Alias; + if (masterTemplate == null) + { + MasterTemplateId = new Lazy(() => -1); + MasterTemplateAlias = null; + } + else + { + MasterTemplateId = new Lazy(() => masterTemplate.Id); + MasterTemplateAlias = masterTemplate.Alias; + } + } public override object DeepClone() { - var clone = (Template)base.DeepClone(); + //We cannot call in to the base classes to clone because the base File class treats Alias, Name.. differently so we need to manually do the clone - //need to manually assign since they are readonly properties - clone._alias = Alias; - clone._name = Name; + //Memberwise clone on Entity will work since it doesn't have any deep elements + // for any sub class this will work for standard properties as well that aren't complex object's themselves. + var clone = (Template)MemberwiseClone(); + //Automatically deep clone ref properties that are IDeepCloneable + DeepCloneHelper.DeepCloneRefProperties(this, clone); clone.ResetDirtyProperties(false); - return clone; } diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index b94c9c0ac0..844b2052d7 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -63,8 +63,8 @@ namespace Umbraco.Core.Persistence.Factories } //TODO: Change this to ParentId: http://issues.umbraco.org/issue/U4-5846 - if(dto.Master.HasValue) - template.MasterTemplateId = new Lazy(() => dto.Master.Value); + if(dto.NodeDto.ParentId > 0) + template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -81,9 +81,9 @@ namespace Umbraco.Core.Persistence.Factories NodeDto = BuildNodeDto(entity) }; - if (entity.MasterTemplateId != null && entity.MasterTemplateId.Value != default(int)) + if (entity.MasterTemplateId != null && entity.MasterTemplateId.Value > 0) { - dto.Master = entity.MasterTemplateId.Value; + dto.NodeDto.ParentId = entity.MasterTemplateId.Value; } if (entity.HasIdentity) diff --git a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs index c498222fce..08a7de0777 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs @@ -38,8 +38,7 @@ namespace Umbraco.Core.Persistence.Mappers if(PropertyInfoCache.IsEmpty) { CacheMap(src => src.Id, dto => dto.NodeId); - - CacheMap(src => src.MasterTemplateId, dto => dto.Master); + CacheMap(src => src.MasterTemplateId, dto => dto.ParentId); CacheMap(src => src.Alias, dto => dto.Alias); CacheMap(src => src.Content, dto => dto.Design); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveTemplateMasterColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveTemplateMasterColumn.cs new file mode 100644 index 0000000000..aa6cb715be --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveTemplateMasterColumn.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero +{ + /// + /// Remove the master column after we've migrated all of the values into the 'ParentId' and Path column of Umbraco node + /// + [Migration("7.3.0", 0, GlobalSettings.UmbracoMigrationName)] + public class MigrateAndRemoveTemplateMasterColumn : MigrationBase + { + public override void Up() + { + + //update the parentId column for all templates to be correct so it matches the current 'master' template + //NOTE: we are using dynamic because we need to get the data in a column that no longer exists in the schema + var templates = Context.Database.Fetch(new Sql().Select("*").From()); + foreach (var template in templates) + { + Update.Table("umbracoNode").Set(new {parentID = template.master ?? -1}).Where(new {id = template.nodeId}); + + //now build the correct path for the template + Update.Table("umbracoNode").Set(new { path = BuildPath (template, templates)}).Where(new { id = template.nodeId }); + + } + + //now remove the master column and key + if (this.Context.CurrentDatabaseProvider == DatabaseProviders.MySql) + { + Delete.ForeignKey().FromTable("cmsTemplate").ForeignColumn("master").ToTable("umbracoUser").PrimaryColumn("id"); + } + else + { + //These are the old aliases, before removing them, check they exist + var constraints = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); + + if (constraints.Any(x => x.Item1.InvariantEquals("cmsTemplate") && x.Item3.InvariantEquals("FK_cmsTemplate_cmsTemplate"))) + { + Delete.ForeignKey("FK_cmsTemplate_cmsTemplate").OnTable("cmsTemplate"); + } + + //TODO: Hopefully it's not named something else silly in some crazy old versions + } + + var columns = SqlSyntaxContext.SqlSyntaxProvider.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + if (columns.Any(x => x.ColumnName.InvariantEquals("master") && x.TableName.InvariantEquals("cmsTemplate"))) + { + Delete.Column("master").FromTable("cmsTemplate"); + } + } + + public override void Down() + { + } + + private string BuildPath(dynamic template, IEnumerable allTemplates) + { + if (template.master == null) + { + return string.Format("-1,{0}", template.nodeId); + } + + var parent = allTemplates.FirstOrDefault(x => x.nodeId == template.master); + + if (parent == null) + { + //this shouldn't happen but i suppose it could if people have bad data + return string.Format("-1,{0}", template.nodeId); + } + + //recurse + var parentPath = BuildPath(parent, allTemplates); + + var path = parentPath + string.Format(",{0}", template.nodeId); + return path; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index b66aad7f3f..caed3928ef 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -85,17 +85,21 @@ namespace Umbraco.Core.Persistence.Repositories var dtos = Database.Fetch(sql); + if (dtos.Count == 0) return Enumerable.Empty(); + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties var childIdsSql = new Sql() - .Select("nodeId,alias," + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("master")) + .Select("nodeId,alias,parentID") .From() - .Where(t => t.Master > 0); + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(t => t.ParentId > 0); var childIds = Database.Fetch(childIdsSql) .Select(x => new UmbracoEntity { Id = x.nodeId, - ParentId = x.master, + ParentId = x.parentID, Name = x.alias }); @@ -110,17 +114,21 @@ namespace Umbraco.Core.Persistence.Repositories var dtos = Database.Fetch(sql); + if (dtos.Count == 0) return Enumerable.Empty(); + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties var childIdsSql = new Sql() - .Select("nodeId,alias," + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("master")) + .Select("nodeId,alias,parentID") .From() - .Where(t => t.Master > 0); + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(t => t.ParentId > 0); var childIds = Database.Fetch(childIdsSql) .Select(x => new UmbracoEntity { Id = x.nodeId, - ParentId = x.master, + ParentId = x.parentID, Name = x.alias }); @@ -309,9 +317,10 @@ namespace Umbraco.Core.Persistence.Repositories // to it, then in the PersistDeletedTemplate we wouldn't recurse the underlying function, we'd just call // PersistDeletedItem with a Template object and clear it's cache. - var sql = new Sql(); - sql.Select("*").From().Where(dto => dto.Master != null || dto.NodeId == entity.Id); - var dtos = Database.Fetch(sql); + var sql = GetBaseQuery(false).Where(dto => dto.ParentId > 0 || dto.NodeId == entity.Id); + + var dtos = Database.Fetch(sql); + var self = dtos.Single(x => x.NodeId == entity.Id); var allChildren = dtos.Except(new[] { self }); var hierarchy = GenerateTemplateHierarchy(self, allChildren); @@ -353,14 +362,14 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new TemplateFactory(_viewsFileSystem, _masterpagesFileSystem, _templateConfig); var template = factory.BuildEntity(dto, childDefinitions); - if (dto.Master.HasValue) + if (dto.NodeDto.ParentId > 0) { //TODO: Fix this n+1 query! - var masterTemplate = Get(dto.Master.Value); + var masterTemplate = Get(dto.NodeDto.ParentId); if (masterTemplate != null) { template.MasterTemplateAlias = masterTemplate.Alias; - template.MasterTemplateId = new Lazy(() => dto.Master.Value); + template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); } } @@ -409,7 +418,7 @@ namespace Umbraco.Core.Persistence.Repositories private static List GenerateTemplateHierarchy(TemplateDto template, IEnumerable allChildTemplates) { var hierarchy = new List { template }; - foreach (var t in allChildTemplates.Where(x => x.Master == template.NodeId)) + foreach (var t in allChildTemplates.Where(x => x.NodeDto.ParentId == template.NodeId)) { hierarchy.AddRange(GenerateTemplateHierarchy(t, allChildTemplates)); } @@ -418,27 +427,20 @@ namespace Umbraco.Core.Persistence.Repositories private void PopulateViewTemplate(ITemplate template, string fileName) { - string content = string.Empty; - string path = string.Empty; + string content; using (var stream = _viewsFileSystem.OpenFile(fileName)) using (var reader = new StreamReader(stream, Encoding.UTF8)) { content = reader.ReadToEnd(); } - - template.UpdateDate = _viewsFileSystem.GetLastModified(path).UtcDateTime; - //Currently set with db values, but will eventually be changed - //template.CreateDate = _viewsFileSystem.GetCreated(path).UtcDateTime; - //template.Key = new FileInfo(path).Name.EncodeAsGuid(); - + template.UpdateDate = _viewsFileSystem.GetLastModified(fileName).UtcDateTime; template.Content = content; } private void PopulateMasterpageTemplate(ITemplate template, string fileName) { - string content = string.Empty; - string path = string.Empty; + string content; using (var stream = _masterpagesFileSystem.OpenFile(fileName)) using (var reader = new StreamReader(stream, Encoding.UTF8)) @@ -446,12 +448,7 @@ namespace Umbraco.Core.Persistence.Repositories content = reader.ReadToEnd(); } - template.UpdateDate = _masterpagesFileSystem.GetLastModified(path).UtcDateTime; - //Currently set with db values, but will eventually be changed - //template.CreateDate = _masterpagesFileSystem.GetCreated(path).UtcDateTime; - //template.Key = new FileInfo(path).Name.EncodeAsGuid(); - - template.Path = path; + template.UpdateDate = _masterpagesFileSystem.GetLastModified(fileName).UtcDateTime; template.Content = content; } @@ -459,8 +456,7 @@ namespace Umbraco.Core.Persistence.Repositories public ITemplate Get(string alias) { - var sql = GetBaseQuery(false) - .Where(x => x.Alias == alias); + var sql = GetBaseQuery(false).Where(x => x.Alias == alias); var dto = Database.Fetch(sql).FirstOrDefault(); @@ -497,12 +493,12 @@ namespace Umbraco.Core.Persistence.Repositories List found; if (masterTemplateId == -1) { - var sql = GetBaseQuery(false).Where(x => x.Master == null); + var sql = GetBaseQuery(false).Where(x => x.ParentId <= 0); found = Database.Fetch(sql); } else { - var sql = GetBaseQuery(false).Where(x => x.Master == masterTemplateId); + var sql = GetBaseQuery(false).Where(x => x.ParentId == masterTemplateId); found = Database.Fetch(sql); } @@ -534,20 +530,19 @@ namespace Umbraco.Core.Persistence.Repositories } //then we need to get all template Dto's because those contain the master property - var sql = new Sql(); - sql.Select("*").From(); - var allDtos = Database.Fetch(sql).ToArray(); + var sql = GetBaseQuery(false); + var allDtos = Database.Fetch(sql).ToArray(); var selfDto = allDtos.Single(x => x.NodeId == selfTemplate.Id); //need to get the top-most node of the current tree var top = selfDto; - while (top.Master.HasValue && top.Master.Value != -1) + while (top.NodeDto.ParentId > 0) { - top = allDtos.Single(x => x.NodeId == top.Master.Value); + top = allDtos.Single(x => x.NodeId == top.NodeDto.ParentId); } var topNode = new TemplateNode(allTemplates.Single(x => x.Id == top.NodeId)); - var childIds = allDtos.Where(x => x.Master == top.NodeId).Select(x => x.NodeId); + var childIds = allDtos.Where(x => x.NodeDto.ParentId == top.NodeId).Select(x => x.NodeId); //This now creates the hierarchy recursively topNode.Children = CreateChildren(topNode, childIds, allTemplates, allDtos); @@ -602,7 +597,7 @@ namespace Umbraco.Core.Persistence.Repositories children.Add(child); //get this node's children - var kids = allDtos.Where(x => x.Master == i).Select(x => x.NodeId).ToArray(); + var kids = allDtos.Where(x => x.NodeDto.ParentId == i).Select(x => x.NodeId).ToArray(); //recurse child.Children = CreateChildren(child, kids, allTemplates, allDtos); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0416c7e8f3..ffb99f2d43 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -390,6 +390,7 @@ + diff --git a/src/Umbraco.Tests/Models/TemplateTests.cs b/src/Umbraco.Tests/Models/TemplateTests.cs index 6d933792a3..a6ba4c9eae 100644 --- a/src/Umbraco.Tests/Models/TemplateTests.cs +++ b/src/Umbraco.Tests/Models/TemplateTests.cs @@ -2,23 +2,25 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Models { [TestFixture] - public class TemplateTests + public class TemplateTests : BaseUmbracoConfigurationTest { [Test] public void Can_Deep_Clone() { - var item = new Template("-1,2,3", "Test", "test") + var item = new Template("Test", "test") { Id = 3, CreateDate = DateTime.Now, Key = Guid.NewGuid(), UpdateDate = DateTime.Now, Content = "blah", - + Path = "-1,3", + IsMasterTemplate = true, MasterTemplateAlias = "master", MasterTemplateId = new Lazy(() => 88) }; @@ -27,6 +29,8 @@ namespace Umbraco.Tests.Models Assert.AreNotSame(clone, item); Assert.AreEqual(clone, item); + Assert.AreEqual(clone.Path, item.Path); + Assert.AreEqual(clone.IsMasterTemplate, item.IsMasterTemplate); Assert.AreEqual(clone.CreateDate, item.CreateDate); Assert.AreEqual(clone.Alias, item.Alias); Assert.AreEqual(clone.Id, item.Id); @@ -49,7 +53,7 @@ namespace Umbraco.Tests.Models { var ss = new SerializationService(new JsonNetSerializer()); - var item = new Template("-1,2,3", "Test", "test") + var item = new Template("Test", "test") { Id = 3, CreateDate = DateTime.Now, diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs index f8493cdea9..7b614ad55c 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs @@ -33,8 +33,8 @@ namespace Umbraco.Tests.Persistence.Querying DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("umbracoNode")))); DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsTemplate")))); - DatabaseContext.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55554, Alias = "testTemplate1", Design = "", Master = null, PrimaryKey = 22221}); - DatabaseContext.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55555, Alias = "testTemplate2", Design = "", Master = null, PrimaryKey = 22222 }); + DatabaseContext.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55554, Alias = "testTemplate1", Design = "", PrimaryKey = 22221}); + DatabaseContext.Database.Insert("cmsTemplate", "pk", false, new TemplateDto { NodeId = 55555, Alias = "testTemplate2", Design = "", PrimaryKey = 22222 }); DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsTemplate")))); DatabaseContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsContentType")))); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index fa41a70892..7cd2d08917 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2544,7 +2544,7 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True 7200 / - http://localhost:7200 + http://localhost:7300 False False diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 849807098f..288e8f19a3 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -163,6 +163,8 @@ namespace Umbraco.Web.WebServices [HttpPost] public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId) { + //TODO: Change this over to use the new API - Also this will be migrated to a TemplateEditor or ViewEditor when it's all moved to angular + Template t; bool pathChanged = false; try @@ -174,11 +176,12 @@ namespace Umbraco.Web.WebServices Design = templateContents }; - //check if the master page has changed - if (t.MasterTemplate != masterTemplateId) + //check if the master page has changed - we need to normalize both - if it's 0 or -1, then make it 0... this is easy + // to do with Math.Max + if (Math.Max(t.MasterTemplate, 0) != Math.Max(masterTemplateId, 0)) { - pathChanged = true; - t.MasterTemplate = masterTemplateId; + t.MasterTemplate = Math.Max(masterTemplateId, 0); + pathChanged = true; } } catch (ArgumentException ex) diff --git a/src/umbraco.cms/businesslogic/template/Template.cs b/src/umbraco.cms/businesslogic/template/Template.cs index 106a808100..bda3805ca4 100644 --- a/src/umbraco.cms/businesslogic/template/Template.cs +++ b/src/umbraco.cms/businesslogic/template/Template.cs @@ -88,6 +88,7 @@ namespace umbraco.cms.businesslogic.template #region Constructors internal Template(ITemplate template) + : base(template.Id, true) { TemplateEntity = template; } @@ -116,7 +117,7 @@ namespace umbraco.cms.businesslogic.template public string GetRawText() { - return TemplateEntity.Content; + return TemplateEntity.Name; //return base.Text; } @@ -149,7 +150,7 @@ namespace umbraco.cms.businesslogic.template set { FlushCache(); - base.Text = value; + TemplateEntity.Name = value; } }