From f95cfc2ee0e57669080d8d2a66763d5fee2891de Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 29 Jan 2013 21:18:17 +0600 Subject: [PATCH 01/16] Changes v4.x config transforms --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ---- src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index f5c7a726b0..09d081e51f 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2303,10 +2303,6 @@ Web.Template.config - - Web.Template.config - Designer - Web.Template.config diff --git a/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config b/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config index ff4fbbd217..30a793e0e0 100644 --- a/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.ShandemVaio.Debug.config @@ -19,7 +19,7 @@ + value="4.11.4"/> Date: Tue, 29 Jan 2013 21:19:16 +0600 Subject: [PATCH 02/16] Fixes: #U4-1170 - template with hyphen or dash --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index fab342df7b..30bac69666 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -271,9 +271,10 @@ namespace Umbraco.Web.Mvc // to the index Action if (publishedContentRequest.HasTemplate) { - //check if the custom controller has an action with the same name as the template name (we convert ToUmbracoAlias since the template name might have invalid chars). - //NOTE: This also means that all custom actions MUST be PascalCase.. but that should be standard. - var templateName = publishedContentRequest.Template.Alias.Split('.')[0].ToUmbracoAlias(StringAliasCaseType.PascalCase); + //the template Alias should always be already saved with a safe name. + //if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed + // with the action name attribute. + var templateName = publishedContentRequest.Template.Alias.Split('.')[0]; def.ActionName = templateName; } From 3fda0308ac640afcf52d5e74191867ba97b8d5fb Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 30 Jan 2013 04:56:28 +0600 Subject: [PATCH 03/16] Fixes: #U4-1594 - SQL for SqlOptimizedSingle not correct for published state Fixes: #U4-1596 - new GetPathPublishedDescendants method on Document for optimizations Fixes: #U4-1595 - Obsoletes HasPublishedVersion() method and ensures lazy loading of Published property, as well as ensuring that the backing property is set correctly on first load (optmization) Fixes: #U4-1597 - PathPublished property should be set in an optimized way and/or lazy loaded if it cannot be --- .../umbraco/editContent.aspx.cs | 9 +- src/umbraco.cms/businesslogic/web/Document.cs | 249 +++++++++++++----- 2 files changed, 191 insertions(+), 67 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs index b342ce9080..e80d1d7263 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs @@ -82,7 +82,7 @@ namespace umbraco.cms.presentation } // we need to check if there's a published version of this document - _documentHasPublishedVersion = _document.HasPublishedVersion(); + _documentHasPublishedVersion = _document.Published; // Check publishing permissions if (!base.getUser().GetPermissions(_document.Path).Contains(ActionPublish.Instance.Letter.ToString())) @@ -322,10 +322,13 @@ namespace umbraco.cms.presentation if (base.getUser().GetPermissions(_document.Path).IndexOf("U") > -1) UnPublish.Visible = true; - _documentHasPublishedVersion = _document.HasPublishedVersion(); + _documentHasPublishedVersion = _document.Published; - foreach (var descendant in _document.GetDescendants().Cast().Where(descendant => descendant.HasPublishedVersion())) + foreach (var descendant in _document.GetPathPublishedDescendants()) + { library.UpdateDocumentCache(descendant.Id); + } + } else { diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 703207644e..ac36db5294 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -85,16 +85,13 @@ namespace umbraco.cms.businesslogic.web { using (IRecordsReader dr = - SqlHelper.ExecuteReader(string.Format(m_SQLOptimizedSingle.Trim(), "umbracoNode.id = @id", "cmsContentVersion.id desc"), + SqlHelper.ExecuteReader(string.Format(SqlOptimizedSingle.Trim(), "umbracoNode.id = @id", "cmsContentVersion.id desc"), SqlHelper.CreateParameter("@nodeObjectType", Document._objectType), SqlHelper.CreateParameter("@id", id))) { if (dr.Read()) { // Initialize node and basic document properties - bool _hc = false; - if (dr.GetInt("children") > 0) - _hc = true; int? masterContentType = null; if (!dr.IsNull("masterContentType")) masterContentType = dr.GetInt("masterContentType"); @@ -103,14 +100,14 @@ namespace umbraco.cms.businesslogic.web , dr.GetInt("parentId") , dr.GetInt("nodeUser") , dr.GetInt("documentUser") - , dr.GetBoolean("published") + , dr.GetInt("published") > 0 , dr.GetString("path") , dr.GetString("text") , dr.GetDateTime("createDate") , dr.GetDateTime("updateDate") , dr.GetDateTime("versionDate") , dr.GetString("icon") - , _hc + , dr.GetInt("children") > 0 , dr.GetString("alias") , dr.GetString("thumbnail") , dr.GetString("description") @@ -139,7 +136,7 @@ namespace umbraco.cms.businesslogic.web tmpReleaseDate, tmpExpireDate, dr.GetDateTime("updateDate"), - dr.GetBoolean("published") + dr.GetInt("published") > 0 ); } } @@ -149,31 +146,33 @@ namespace umbraco.cms.businesslogic.web #endregion #region Constants and Static members + + // NH: Modified to support SQL CE 4 (doesn't support nested selects) - private const string m_SQLOptimizedSingle = @" - Select - CASE WHEN (childrenTable.total>0) THEN childrenTable.total ELSE 0 END as Children, - CASE WHEN (publishedTable.publishedTotal>0) THEN publishedTable.publishedTotal ELSE 0 END as Published, - cmsContentVersion.VersionId, - cmsContentVersion.versionDate, - contentTypeNode.uniqueId as ContentTypeGuid, - cmsContent.ContentType, cmsContentType.icon, cmsContentType.alias, cmsContentType.thumbnail, cmsContentType.description, cmsContentType.masterContentType, cmsContentType.nodeId as contentTypeId, - published, documentUser, coalesce(templateId, cmsDocumentType.templateNodeId) as templateId, cmsDocument.text as DocumentText, releaseDate, expireDate, updateDate, - umbracoNode.createDate, umbracoNode.trashed, umbracoNode.parentId, umbracoNode.nodeObjectType, umbracoNode.nodeUser, umbracoNode.level, umbracoNode.path, umbracoNode.sortOrder, umbracoNode.uniqueId, umbracoNode.text - from - umbracoNode - inner join cmsContentVersion on cmsContentVersion.contentID = umbracoNode.id - inner join cmsDocument on cmsDocument.versionId = cmsContentVersion.versionId - inner join cmsContent on cmsDocument.nodeId = cmsContent.NodeId - inner join cmsContentType on cmsContentType.nodeId = cmsContent.ContentType - inner join umbracoNode contentTypeNode on contentTypeNode.id = cmsContentType.nodeId - left join cmsDocumentType on cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1 - /* SQL CE support */ - left outer join (select count(id) as total, parentId from umbracoNode where parentId = @id group by parentId) as childrenTable on childrenTable.parentId = umbracoNode.id - left outer join (select Count(published) as publishedTotal, nodeId from cmsDocument where published = 1 And nodeId = @id group by nodeId) as publishedTable on publishedTable.nodeId = umbracoNode.id - /* end SQL CE support */ - where umbracoNode.nodeObjectType = @nodeObjectType AND {0} - order by {1} + private const string SqlOptimizedSingle = @" +Select + CASE WHEN (childrenTable.total>0) THEN childrenTable.total ELSE 0 END as Children, + CASE WHEN (publishedTable.publishedTotal>0) THEN publishedTable.publishedTotal ELSE 0 END as Published, + cmsContentVersion.VersionId, + cmsContentVersion.versionDate, + contentTypeNode.uniqueId as ContentTypeGuid, + cmsContent.ContentType, cmsContentType.icon, cmsContentType.alias, cmsContentType.thumbnail, cmsContentType.description, cmsContentType.masterContentType, cmsContentType.nodeId as contentTypeId, + documentUser, coalesce(templateId, cmsDocumentType.templateNodeId) as templateId, cmsDocument.text as DocumentText, releaseDate, expireDate, updateDate, + umbracoNode.createDate, umbracoNode.trashed, umbracoNode.parentId, umbracoNode.nodeObjectType, umbracoNode.nodeUser, umbracoNode.level, umbracoNode.path, umbracoNode.sortOrder, umbracoNode.uniqueId, umbracoNode.text +from + umbracoNode + inner join cmsContentVersion on cmsContentVersion.contentID = umbracoNode.id + inner join cmsDocument on cmsDocument.versionId = cmsContentVersion.versionId + inner join cmsContent on cmsDocument.nodeId = cmsContent.NodeId + inner join cmsContentType on cmsContentType.nodeId = cmsContent.ContentType + inner join umbracoNode contentTypeNode on contentTypeNode.id = cmsContentType.nodeId + left join cmsDocumentType on cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1 + /* SQL CE support */ + left outer join (select count(id) as total, parentId from umbracoNode where parentId = @id group by parentId) as childrenTable on childrenTable.parentId = umbracoNode.id + left outer join (select Count(published) as publishedTotal, nodeId from cmsDocument where published = 1 And nodeId = @id group by nodeId) as publishedTable on publishedTable.nodeId = umbracoNode.id + /* end SQL CE support */ +where umbracoNode.nodeObjectType = @nodeObjectType AND {0} +order by {1} "; // NH: Had to modify this for SQL CE 4. Only change is that the "coalesce(publishCheck.published,0) as published" didn't work in SQL CE 4 @@ -182,7 +181,7 @@ namespace umbraco.cms.businesslogic.web // zb-00010 #29443 : removed the following lines + added constraint on cmsDocument.newest in where clause (equivalent + handles duplicate dates) // inner join (select contentId, max(versionDate) as versionDate from cmsContentVersion group by contentId) temp // on cmsContentVersion.contentId = temp.contentId and cmsContentVersion.versionDate = temp.versionDate - private const string m_SQLOptimizedMany = @" + private const string SqlOptimizedMany = @" select count(children.id) as children, umbracoNode.id, umbracoNode.uniqueId, umbracoNode.level, umbracoNode.parentId, cmsDocument.documentUser, coalesce(cmsDocument.templateId, cmsDocumentType.templateNodeId) as templateId, umbracoNode.path, umbracoNode.sortOrder, coalesce(publishCheck.published,0) as isPublished, umbracoNode.createDate, @@ -208,7 +207,7 @@ namespace umbraco.cms.businesslogic.web order by {1} "; - private const string m_SQLOptimizedForPreview = @" + private const string SqlOptimizedForPreview = @" select umbracoNode.id, umbracoNode.parentId, umbracoNode.level, umbracoNode.sortOrder, cmsDocument.versionId, cmsPreviewXml.xml from cmsDocument inner join umbracoNode on umbracoNode.id = cmsDocument.nodeId inner join cmsPreviewXml on cmsPreviewXml.nodeId = cmsDocument.nodeId and cmsPreviewXml.versionId = cmsDocument.versionId @@ -226,7 +225,17 @@ namespace umbraco.cms.businesslogic.web private DateTime _release; private DateTime _expire; private int _template; - private bool _published; + + /// + /// a backing property for the 'Published' property + /// + private bool? _published; + + /// + /// Used as a value flag to indicate that we've already executed the sql for IsPathPublished() + /// + private bool? _pathPublished; + private XmlNode _xml; private User _creator; private User _writer; @@ -521,7 +530,7 @@ namespace umbraco.cms.businesslogic.web var tmp = new List(); using (IRecordsReader dr = SqlHelper.ExecuteReader( - string.Format(m_SQLOptimizedMany.Trim(), "cmsContent.contentType = @contentTypeId", "umbracoNode.sortOrder"), + string.Format(SqlOptimizedMany.Trim(), "cmsContent.contentType = @contentTypeId", "umbracoNode.sortOrder"), SqlHelper.CreateParameter("@nodeObjectType", Document._objectType), SqlHelper.CreateParameter("@contentTypeId", docTypeId))) { @@ -552,7 +561,7 @@ namespace umbraco.cms.businesslogic.web var tmp = new List(); using (IRecordsReader dr = SqlHelper.ExecuteReader( - string.Format(m_SQLOptimizedMany.Trim(), "umbracoNode.parentID = @parentId", "umbracoNode.sortOrder"), + string.Format(SqlOptimizedMany.Trim(), "umbracoNode.parentID = @parentId", "umbracoNode.sortOrder"), SqlHelper.CreateParameter("@nodeObjectType", Document._objectType), SqlHelper.CreateParameter("@parentId", NodeId))) { @@ -572,7 +581,7 @@ namespace umbraco.cms.businesslogic.web var tmp = new List(); using (IRecordsReader dr = SqlHelper.ExecuteReader( - string.Format(m_SQLOptimizedMany.Trim(), "umbracoNode.parentID = @parentId and umbracoNode.text like @search", "umbracoNode.sortOrder"), + string.Format(SqlOptimizedMany.Trim(), "umbracoNode.parentID = @parentId and umbracoNode.text like @search", "umbracoNode.sortOrder"), SqlHelper.CreateParameter("@nodeObjectType", Document._objectType), SqlHelper.CreateParameter("@search", searchString), SqlHelper.CreateParameter("@parentId", NodeId))) @@ -767,8 +776,20 @@ namespace umbraco.cms.businesslogic.web /// the node and all its parents are published, and therefore whether the node is visible. public bool Published { - get { return _published; } + get + { + if (!_published.HasValue) + { + var count = SqlHelper.ExecuteScalar(@" +select Count(published) as CountOfPublished +from cmsDocument +inner join umbracoNode on cmsDocument.nodeId = umbracoNode.id +where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParameter("@nodeId", Id)); + _published = count > 0; + } + return _published.Value; + } set { _published = value; @@ -777,23 +798,50 @@ namespace umbraco.cms.businesslogic.web } } - /// - /// Gets a value indicating whether the document and all its parents are published. - /// + /// + /// Returns true if the document has a front-end published version (all documents in it's path are published) + /// + /// + /// + /// A document may be marked as published in the database but not published on the front-end. This occurs when you have a node structure such as + /// - Home + /// -- Page 1 + /// --- Sub 1 + /// --- Sub 2 + /// + /// And they are all published, but then you unpublish 'Page 1'. What happens is that Page 1 and all of it's descendants are removed from the front-end + /// published cache and the 'Page 1' item is flagged as not published in the database, however both "Sub" pages will still be flagged in the database + /// as published. This is for performance and tracking reasons since these Sub pages have not physically been unpublished, they just cannot be seen on the + /// front -end. + /// + /// This method will return true or false based on whether or not there is published version on the front-end but this lookup is based purely on the databse + /// by comparing the ancesctors published count of the current node. + /// + /// If a node is in the recycle bin it will also be deemed not published + /// public bool PathPublished { get { - // get all nodes in the path to the document, and get all matching published documents - // the difference should be zero if everything is published - // test nodeObjectType to make sure we only count _content_ nodes - int x = SqlHelper.ExecuteScalar(@"select count(node.id) - count(doc.nodeid) + //check our cached value for this object + if (!_pathPublished.HasValue) + { + // get all nodes in the path to the document, and get all matching published documents + // the difference should be zero if everything is published + // test nodeObjectType to make sure we only count _content_ nodes + var sql = @"select count(node.id) - count(doc.nodeid) from umbracoNode as node left join cmsDocument as doc on (node.id=doc.nodeId and doc.published=1) where '" + Path + ",' like " + SqlHelper.Concat("node.path", "',%'") + @" -and node.nodeObjectType='C66BA18E-EAF3-4CFF-8A22-41B16D66A972'"); - return (x == 0); +and node.nodeObjectType=@nodeObjectType"; + + var count = SqlHelper.ExecuteScalar(sql, SqlHelper.CreateParameter("@nodeObjectType", Document._objectType)); + _pathPublished = (count == 0); + } + + return _pathPublished.Value; } + internal set { _pathPublished = value; } } public override string Text @@ -1236,21 +1284,20 @@ and node.nodeObjectType='C66BA18E-EAF3-4CFF-8A22-41B16D66A972'"); } /// - /// Returns true if the document is published + /// Returns true if the document has a published item in the database but is not in the recycle bin /// /// /// - /// If the document is in the trash then this will return false + /// This will still return true if this document is not published on the front-end in some cases if one of it's ancestors are + /// not-published. If you have a published document and unpublish one of it's ancestors, it will retain it's published flag in the + /// database. + /// + /// If you are wanting to check if this document is published on the front end use the IsPathPublished() method. /// + [Obsolete("Use the Published property instead")] public bool HasPublishedVersion() { - var count = SqlHelper.ExecuteScalar(@" -select Count(published) as CountOfPublished -from cmsDocument -inner join umbracoNode on cmsDocument.nodeId = umbracoNode.id -where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParameter("@nodeId", Id)); - - return count > 0; + return Published; } /// @@ -1463,26 +1510,55 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam } } + /// + /// Returns all descendants that are published on the front-end (hava a full published path) + /// + /// + public IEnumerable GetPathPublishedDescendants() + { + var documents = new List(); + using (var dr = SqlHelper.ExecuteReader( + string.Format(SqlOptimizedMany.Trim(), "umbracoNode.path LIKE '%," + this.Id + ",%'", "umbracoNode.level"), + SqlHelper.CreateParameter("@nodeObjectType", Document._objectType))) + { + while (dr.Read()) + { + var d = new Document(dr.GetInt("id"), true); + d.PopulateDocumentFromReader(dr); + documents.Add(d); + } + } + + //update the PathPublished correctly for all documents added to this list + UpdatePathPublishedOnDescendants(documents); + + //now, we only want to return any descendants that have a PathPublished = true + return documents.Where(x => x.PathPublished); + } + /// /// Returns all decendants of the current document /// /// public override IEnumerable GetDescendants() { - var tmp = new List(); + var documents = new List(); using (IRecordsReader dr = SqlHelper.ExecuteReader( - string.Format(m_SQLOptimizedMany.Trim(), "umbracoNode.path LIKE '%," + this.Id + ",%'", "umbracoNode.level"), + string.Format(SqlOptimizedMany.Trim(), "umbracoNode.path LIKE '%," + this.Id + ",%'", "umbracoNode.level"), SqlHelper.CreateParameter("@nodeObjectType", Document._objectType))) { while (dr.Read()) { - Document d = new Document(dr.GetInt("id"), true); + var d = new Document(dr.GetInt("id"), true); d.PopulateDocumentFromReader(dr); - tmp.Add(d); + documents.Add(d); } } - return tmp.ToArray(); + //update the PathPublished correctly for all documents added to this list + UpdatePathPublishedOnDescendants(documents); + + return documents.ToArray(); } /// @@ -1662,7 +1738,7 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam string pathExp = childrenOnly ? Path + ",%" : Path; - IRecordsReader dr = SqlHelper.ExecuteReader(String.Format(m_SQLOptimizedForPreview, pathExp)); + IRecordsReader dr = SqlHelper.ExecuteReader(String.Format(SqlOptimizedForPreview, pathExp)); while (dr.Read()) nodes.Add(new CMSPreviewNode(dr.GetInt("id"), dr.GetGuid("versionId"), dr.GetInt("parentId"), dr.GetShort("level"), dr.GetInt("sortOrder"), dr.GetString("xml"))); dr.Close(); @@ -1717,8 +1793,6 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam throw new ArgumentException(string.Format("No Document exists with Version '{0}'", Version)); } } - - _published = HasPublishedVersion(); } protected void InitializeDocument(User InitUser, User InitWriter, string InitText, int InitTemplate, @@ -1743,6 +1817,10 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam _published = InitPublished; } + /// + /// Updates this document object based on the data in the IRecordsReader for data returned from the SqlOptimizedMany SQL call + /// + /// protected void PopulateDocumentFromReader(IRecordsReader dr) { var hc = dr.GetInt("children") > 0; @@ -1776,6 +1854,7 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam _release = dr.GetDateTime("releaseDate"); if (!dr.IsNull("expireDate")) _expire = dr.GetDateTime("expireDate"); + } protected void SaveXmlPreview(XmlDocument xd) @@ -1786,6 +1865,48 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam #endregion #region Private Methods + + /// + /// Updates this the PathPublished property for all pre-populated descendant nodes in list format + /// + /// The pre-populated list of descendants of the root node passed in + /// + /// This method will ensure that the document's PathPublished is automatically set based on this (the root ancestor) document. + /// It will set the PathPublished based on the documents with the shortest path first since the parent document to those documents + /// are 'this' document. Then we will go to the next level and set the PathPublished based on their parent documents... since they will + /// now have the PathPublished property set. and so on. + /// + private void UpdatePathPublishedOnDescendants(List descendantsList) + { + //create a new list containing 'this' so the list becomes DescendantsAndSelf + var descendantsAndSelf = descendantsList.Concat(new[] {this}).ToList(); + + //determine all path lengths in the list + var pathLengths = descendantsList.Select(x => x.Path.Split(',').Length).Distinct(); + //start with the shortest paths + foreach (var pathLength in pathLengths.OrderBy(x => x)) + { + var length = pathLength; + var docsWithPathLength = descendantsList.Where(x => x.Path.Split(',').Length == length); + //iterate over all documents with the current path length + foreach (var d in docsWithPathLength) + { + //we need to find the current doc's parent doc in the descendantsOrSelf list + var parent = descendantsAndSelf.SingleOrDefault(x => x.Id == d.ParentId); + if (parent != null) + { + //here we jsut check what the parent document's PathPublished property is. + // If it is false then the current 'd' document's PathPublished is also false. + // If it is true and if the current 'd' document's Published is true, the the 'd' document's PathPublished is also true. + // If it is true and if the current 'd' document's Published is false, the the 'd' document's PathPublished is false. + d.PathPublished = parent.PathPublished && d.Published; + } + } + } + + + } + private void SetupDocumentForTree(Guid uniqueId, int level, int parentId, int creator, int writer, bool publish, string path, string text, DateTime createDate, DateTime updateDate, DateTime versionDate, string icon, bool hasChildren, string contentTypeAlias, string contentTypeThumb, From d0ace55d032df9942037f8ccd63e5712df87cef2 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 30 Jan 2013 05:04:31 +0600 Subject: [PATCH 04/16] Fixes: #U4-1593 - Content tree doesn't check for correct publish state. --- .../umbraco.presentation/umbraco/Trees/BaseContentTree.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs index f2c267f8e4..12b3d776e3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs @@ -113,7 +113,7 @@ function openContent(id) { node.OpenIcon = dd.ContentTypeIcon; } - if (dd.Published == false) + if (!dd.PathPublished) node.Style.DimNode(); if (dd.HasPendingChanges()) @@ -193,7 +193,7 @@ function openContent(id) { protected void SetNonPublishedAttribute(ref XmlTreeNode treeElement, Document dd) { treeElement.NotPublished = false; - if (dd.Published) + if (dd.PathPublished) { //if (Math.Round(new TimeSpan(dd.UpdateDate.Ticks - dd.VersionDate.Ticks).TotalSeconds, 0) > 1) // treeElement.NotPublished = true; From a734a0cff773ba3c688207bee2defc96fd09a5fb Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 30 Jan 2013 05:37:32 +0600 Subject: [PATCH 05/16] Fixes unit tests for Umbraco_Route_User_Defined_Controller_Action --- src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs | 7 +++++-- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 306a02bb5b..b4f5d19ba6 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -64,7 +64,8 @@ namespace Umbraco.Tests.Routing } //test all template name styles to match the ActionName - [TestCase("home-page")] + [TestCase("home-\\234^^*32page")] + [TestCase("home-page")] [TestCase("Home-Page")] [TestCase("HomePage")] [TestCase("homePage")] @@ -84,7 +85,9 @@ namespace Umbraco.Tests.Routing handler.GetHandlerForRoute(routingContext.UmbracoContext.HttpContext.Request.RequestContext, docRequest); Assert.AreEqual("CustomDocument", routeData.Values["controller"].ToString()); - Assert.AreEqual("HomePage", routeData.Values["action"].ToString()); + Assert.AreEqual( + global::umbraco.cms.helpers.Casing.SafeAlias(templateName), + routeData.Values["action"].ToString()); } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 30bac69666..53047d3c3a 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -274,7 +274,7 @@ namespace Umbraco.Web.Mvc //the template Alias should always be already saved with a safe name. //if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed // with the action name attribute. - var templateName = publishedContentRequest.Template.Alias.Split('.')[0]; + var templateName = global::umbraco.cms.helpers.Casing.SafeAlias(publishedContentRequest.Template.Alias.Split('.')[0]); def.ActionName = templateName; } From 2d87f933d5c243be4bd9de03fbf7193dd246c9bf Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 30 Jan 2013 07:57:31 +0600 Subject: [PATCH 06/16] Updates Document's Published property to return true if the document is 100% published (viewable on the front-end) Reverts Document's HasPublishedVersion() to return true if the document has any published version (may or may not be viewable on the front-end) Updates the Document's new UpdatePublishedOnDescendants to set both the Published and PathPublished fields. Reverts some stuff in the editContent to use HasPublishedContent() to set the _documentHasPublishedVersion field. Reverts the BaseContentTree to use the Published property now that it returns the correct value. Updates the editContent method to use the UpdateDocumentCache overload accepting an object so there's not another N+1 SQL call made. --- .../umbraco/Trees/BaseContentTree.cs | 8 +- .../umbraco/editContent.aspx.cs | 8 +- src/umbraco.cms/businesslogic/web/Document.cs | 164 ++++++++++++------ 3 files changed, 115 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs index 12b3d776e3..1170bb8d33 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs @@ -35,7 +35,7 @@ namespace umbraco.cms.presentation.Trees public BaseContentTree(string application) : base(application) { } - private User m_user; + private User _user; /// /// Returns the current User. This ensures that we don't instantiate a new User object @@ -45,7 +45,7 @@ namespace umbraco.cms.presentation.Trees { get { - return (m_user == null ? (m_user = UmbracoEnsuredPage.CurrentUser) : m_user); + return (_user == null ? (_user = UmbracoEnsuredPage.CurrentUser) : _user); } } @@ -113,7 +113,7 @@ function openContent(id) { node.OpenIcon = dd.ContentTypeIcon; } - if (!dd.PathPublished) + if (!dd.Published) node.Style.DimNode(); if (dd.HasPendingChanges()) @@ -193,7 +193,7 @@ function openContent(id) { protected void SetNonPublishedAttribute(ref XmlTreeNode treeElement, Document dd) { treeElement.NotPublished = false; - if (dd.PathPublished) + if (dd.Published) { //if (Math.Round(new TimeSpan(dd.UpdateDate.Ticks - dd.VersionDate.Ticks).TotalSeconds, 0) > 1) // treeElement.NotPublished = true; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs index e80d1d7263..c074e1cbad 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs @@ -82,7 +82,7 @@ namespace umbraco.cms.presentation } // we need to check if there's a published version of this document - _documentHasPublishedVersion = _document.Published; + _documentHasPublishedVersion = _document.HasPublishedVersion(); // Check publishing permissions if (!base.getUser().GetPermissions(_document.Path).Contains(ActionPublish.Instance.Letter.ToString())) @@ -322,11 +322,11 @@ namespace umbraco.cms.presentation if (base.getUser().GetPermissions(_document.Path).IndexOf("U") > -1) UnPublish.Visible = true; - _documentHasPublishedVersion = _document.Published; + _documentHasPublishedVersion = _document.HasPublishedVersion(); - foreach (var descendant in _document.GetPathPublishedDescendants()) + foreach (var descendant in _document.GetPublishedDescendants()) { - library.UpdateDocumentCache(descendant.Id); + library.UpdateDocumentCache(descendant); } } diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index ac36db5294..60f4b8024d 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -230,7 +230,12 @@ order by {1} /// a backing property for the 'Published' property /// private bool? _published; - + + /// + /// a backing property for the 'HasPublishedVersion()' method + /// + private bool? _hasPublishedVersion; + /// /// Used as a value flag to indicate that we've already executed the sql for IsPathPublished() /// @@ -558,24 +563,49 @@ order by {1} /// public static Document[] GetChildrenForTree(int NodeId) { - var tmp = new List(); - using (IRecordsReader dr = + var documents = GetChildrenForTreeInternal(NodeId).ToList(); + if (NodeId > 0) + { + var parent = new Document(NodeId); + //update the Published/PathPublished correctly for all documents added to this list + UpdatePublishedOnDescendants(documents, parent); + } + return documents.ToArray(); + } + + /// + /// Performance tuned method for use in the tree + /// + /// The parent document + /// + public static Document[] GetChildrenForTree(Document parent) + { + var documents = GetChildrenForTreeInternal(parent.Id).ToList(); + //update the Published/PathPublished correctly for all documents added to this list + UpdatePublishedOnDescendants(documents, parent); + return documents.ToArray(); + } + + public static IEnumerable GetChildrenForTreeInternal(int nodeId) + { + var documents = new List(); + using (var dr = SqlHelper.ExecuteReader( string.Format(SqlOptimizedMany.Trim(), "umbracoNode.parentID = @parentId", "umbracoNode.sortOrder"), SqlHelper.CreateParameter("@nodeObjectType", Document._objectType), - SqlHelper.CreateParameter("@parentId", NodeId))) + SqlHelper.CreateParameter("@parentId", nodeId))) { while (dr.Read()) { - Document d = new Document(dr.GetInt("id"), true); + var d = new Document(dr.GetInt("id"), true); d.PopulateDocumentFromReader(dr); - tmp.Add(d); + documents.Add(d); } } - - return tmp.ToArray(); + return documents; } + public static List GetChildrenBySearch(int NodeId, string searchString) { var tmp = new List(); @@ -780,13 +810,18 @@ order by {1} { if (!_published.HasValue) { - var count = SqlHelper.ExecuteScalar(@" -select Count(published) as CountOfPublished -from cmsDocument -inner join umbracoNode on cmsDocument.nodeId = umbracoNode.id -where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParameter("@nodeId", Id)); + // get all nodes in the path to the document, and get all matching published documents + // the difference should be zero if everything is published + // test nodeObjectType to make sure we only count _content_ nodes + var sql = @"select count(node.id) - count(doc.nodeid) +from umbracoNode as node +left join cmsDocument as doc on (node.id=doc.nodeId and doc.published=1) +where (('" + Path + ",' like " + SqlHelper.Concat("node.path", "',%'") + @") + or ('" + Path + @"' = node.path)) and node.id <> -1 +and node.nodeObjectType=@nodeObjectType"; - _published = count > 0; + var count = SqlHelper.ExecuteScalar(sql, SqlHelper.CreateParameter("@nodeObjectType", Document._objectType)); + _published = (count == 0); } return _published.Value; } @@ -799,25 +834,10 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam } /// - /// Returns true if the document has a front-end published version (all documents in it's path are published) + /// Returns true if the document's ancestors are all published /// /// /// - /// A document may be marked as published in the database but not published on the front-end. This occurs when you have a node structure such as - /// - Home - /// -- Page 1 - /// --- Sub 1 - /// --- Sub 2 - /// - /// And they are all published, but then you unpublish 'Page 1'. What happens is that Page 1 and all of it's descendants are removed from the front-end - /// published cache and the 'Page 1' item is flagged as not published in the database, however both "Sub" pages will still be flagged in the database - /// as published. This is for performance and tracking reasons since these Sub pages have not physically been unpublished, they just cannot be seen on the - /// front -end. - /// - /// This method will return true or false based on whether or not there is published version on the front-end but this lookup is based purely on the databse - /// by comparing the ancesctors published count of the current node. - /// - /// If a node is in the recycle bin it will also be deemed not published /// public bool PathPublished { @@ -957,7 +977,7 @@ and node.nodeObjectType=@nodeObjectType"; { //cache the documents children so that this db call doesn't have to occur again if (this._children == null) - this._children = Document.GetChildrenForTree(this.Id); + this._children = GetChildrenForTree(this); return this._children.ToArray(); } @@ -1291,13 +1311,20 @@ and node.nodeObjectType=@nodeObjectType"; /// This will still return true if this document is not published on the front-end in some cases if one of it's ancestors are /// not-published. If you have a published document and unpublish one of it's ancestors, it will retain it's published flag in the /// database. - /// - /// If you are wanting to check if this document is published on the front end use the IsPathPublished() method. /// - [Obsolete("Use the Published property instead")] public bool HasPublishedVersion() { - return Published; + if (!_hasPublishedVersion.HasValue) + { + var count = SqlHelper.ExecuteScalar(@" +select Count(published) as CountOfPublished +from cmsDocument +inner join umbracoNode on cmsDocument.nodeId = umbracoNode.id +where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParameter("@nodeId", Id)); + + _hasPublishedVersion = count > 0; + } + return _hasPublishedVersion.Value; } /// @@ -1514,7 +1541,7 @@ and node.nodeObjectType=@nodeObjectType"; /// Returns all descendants that are published on the front-end (hava a full published path) /// /// - public IEnumerable GetPathPublishedDescendants() + public IEnumerable GetPublishedDescendants() { var documents = new List(); using (var dr = SqlHelper.ExecuteReader( @@ -1529,11 +1556,11 @@ and node.nodeObjectType=@nodeObjectType"; } } - //update the PathPublished correctly for all documents added to this list - UpdatePathPublishedOnDescendants(documents); + //update the Published/PathPublished correctly for all documents added to this list + UpdatePublishedOnDescendants(documents, this); - //now, we only want to return any descendants that have a PathPublished = true - return documents.Where(x => x.PathPublished); + //now, we only want to return any descendants that have a Published = true (full published path) + return documents.Where(x => x.Published); } /// @@ -1555,8 +1582,8 @@ and node.nodeObjectType=@nodeObjectType"; } } - //update the PathPublished correctly for all documents added to this list - UpdatePathPublishedOnDescendants(documents); + //update the Published/PathPublished correctly for all documents added to this list + UpdatePublishedOnDescendants(documents, this); return documents.ToArray(); } @@ -1867,19 +1894,20 @@ and node.nodeObjectType=@nodeObjectType"; #region Private Methods /// - /// Updates this the PathPublished property for all pre-populated descendant nodes in list format + /// Updates this the Published property for all pre-populated descendant nodes in list format /// - /// The pre-populated list of descendants of the root node passed in + /// The pre-populated list of descendants of the root node passed in + /// The very root document retreiving the ancestors /// - /// This method will ensure that the document's PathPublished is automatically set based on this (the root ancestor) document. - /// It will set the PathPublished based on the documents with the shortest path first since the parent document to those documents - /// are 'this' document. Then we will go to the next level and set the PathPublished based on their parent documents... since they will - /// now have the PathPublished property set. and so on. + /// This method will ensure that the document's Published is automatically set based on this (the root ancestor) document. + /// It will set the Published based on the documents with the shortest path first since the parent document to those documents + /// are 'this' document. Then we will go to the next level and set the Published based on their parent documents... since they will + /// now have the Published property set. and so on. /// - private void UpdatePathPublishedOnDescendants(List descendantsList) + private static void UpdatePublishedOnDescendants(List descendantsList, Document root) { //create a new list containing 'this' so the list becomes DescendantsAndSelf - var descendantsAndSelf = descendantsList.Concat(new[] {this}).ToList(); + var descendantsAndSelf = descendantsList.Concat(new[] { root }).ToList(); //determine all path lengths in the list var pathLengths = descendantsList.Select(x => x.Path.Split(',').Length).Distinct(); @@ -1895,11 +1923,11 @@ and node.nodeObjectType=@nodeObjectType"; var parent = descendantsAndSelf.SingleOrDefault(x => x.Id == d.ParentId); if (parent != null) { - //here we jsut check what the parent document's PathPublished property is. - // If it is false then the current 'd' document's PathPublished is also false. - // If it is true and if the current 'd' document's Published is true, the the 'd' document's PathPublished is also true. - // If it is true and if the current 'd' document's Published is false, the the 'd' document's PathPublished is false. - d.PathPublished = parent.PathPublished && d.Published; + //we are published if our parent is published and we have a published version + d.Published = parent.Published && d.HasPublishedVersion(); + + //our path is published if our parent is published + d.PathPublished = parent.Published; } } } @@ -1907,7 +1935,29 @@ and node.nodeObjectType=@nodeObjectType"; } - private void SetupDocumentForTree(Guid uniqueId, int level, int parentId, int creator, int writer, bool publish, string path, + /// + /// Sets properties on this object based on the parameters + /// + /// + /// + /// + /// + /// + /// If this document has a published version + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private void SetupDocumentForTree(Guid uniqueId, int level, int parentId, int creator, int writer, bool hasPublishedVersion, string path, string text, DateTime createDate, DateTime updateDate, DateTime versionDate, string icon, bool hasChildren, string contentTypeAlias, string contentTypeThumb, string contentTypeDesc, int? masterContentType, int contentTypeId, int templateId) @@ -1915,7 +1965,7 @@ and node.nodeObjectType=@nodeObjectType"; SetupNodeForTree(uniqueId, _objectType, level, parentId, creator, path, text, createDate, hasChildren); _writerId = writer; - _published = publish; + _hasPublishedVersion = hasPublishedVersion; _updated = updateDate; _template = templateId; ContentType = new ContentType(contentTypeId, contentTypeAlias, icon, contentTypeThumb, masterContentType); From d38d7b1ff41d328571fa7b3cb70fdac76e2194dd Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 30 Jan 2013 07:59:03 +0600 Subject: [PATCH 07/16] Updates all code calling UnPublishSingleNode & UpdateDocumentCache to pass in the already existing Document object if there is one instead of the ID... this'll save a another ton of SQL calls. --- .../umbraco.presentation/content.cs | 30 ++++++++++--------- .../umbraco.presentation/library.cs | 21 ++++++++++--- .../umbraco.presentation/publishingService.cs | 2 +- .../Modules/CreateModule/CreateModule.cs | 2 +- .../umbraco/actions/publish.aspx.cs | 2 +- .../umbraco/channels/UmbracoMetaWeblogAPI.cs | 4 +-- .../umbraco/dialogs/moveOrCopy.aspx.cs | 4 +-- .../umbraco/dialogs/publish.aspx.cs | 6 ++-- .../umbraco/uQuery/DocumentExtensions.cs | 2 +- .../documents/documentService.cs | 8 ++--- 10 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index ee5da1467f..5161c14119 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -580,40 +580,42 @@ namespace umbraco ThreadPool.QueueUserWorkItem(delegate { UpdateDocumentCache(documentId); }); } - - [Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)] /// /// Clears the document cache async. /// /// The document id. + [Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)] public virtual void ClearDocumentCacheAsync(int documentId) { ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); }); } + public virtual void ClearDocumentCache(int documentId) + { + // Get the document + var d = new Document(documentId); + ClearDocumentCache(d); + } /// /// Clears the document cache and removes the document from the xml db cache. /// This means the node gets unpublished from the website. /// - /// The document id. - public virtual void ClearDocumentCache(int documentId) + /// The document + public virtual void ClearDocumentCache(Document doc) { - // Get the document - var d = new Document(documentId); - var e = new DocumentCacheEventArgs(); - FireBeforeClearDocumentCache(d, e); + FireBeforeClearDocumentCache(doc, e); if (!e.Cancel) { XmlNode x; // remove from xml db cache - d.XmlRemoveFromDB(); + doc.XmlRemoveFromDB(); // Check if node present, before cloning - x = XmlContentInternal.GetElementById(d.Id.ToString()); + x = XmlContentInternal.GetElementById(doc.Id.ToString()); if (x == null) return; @@ -626,7 +628,7 @@ namespace umbraco XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal); // Find the document in the xml cache - x = xmlContentCopy.GetElementById(d.Id.ToString()); + x = xmlContentCopy.GetElementById(doc.Id.ToString()); if (x != null) { // The document already exists in cache, so repopulate it @@ -639,17 +641,17 @@ namespace umbraco if (x != null) { // Run Handler - Action.RunActionHandlers(d, ActionUnPublish.Instance); + Action.RunActionHandlers(doc, ActionUnPublish.Instance); } // update sitemapprovider if (SiteMap.Provider is UmbracoSiteMapProvider) { var prov = (UmbracoSiteMapProvider)SiteMap.Provider; - prov.RemoveNode(d.Id); + prov.RemoveNode(doc.Id); } - FireAfterClearDocumentCache(d, e); + FireAfterClearDocumentCache(doc, e); } } diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 8a78f77168..9a081380e3 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -187,9 +187,7 @@ namespace umbraco /// /// The Id of the Document to be unpublished public static void UnPublishSingleNode(int DocumentId) - { - - //PPH Added dispatcher support + { if (UmbracoSettings.UseDistributedCalls) dispatcher.Remove( new Guid("27ab3022-3dfa-47b6-9119-5945bc88fd66"), @@ -198,6 +196,21 @@ namespace umbraco content.Instance.ClearDocumentCache(DocumentId); } + /// + /// Unpublish a node, by removing it from the runtime xml index. Note, prior to this the Document should be + /// marked unpublished by setting the publish property on the document object to false + /// + /// The Document to be unpublished + public static void UnPublishSingleNode(Document document) + { + if (UmbracoSettings.UseDistributedCalls) + dispatcher.Remove( + new Guid("27ab3022-3dfa-47b6-9119-5945bc88fd66"), + document.Id); + else + content.Instance.ClearDocumentCache(document); + } + /// /// Publishes a Document by adding it to the runtime xml index. Note, prior to this the Document should be /// marked published by calling Publish(User u) on the document object. @@ -219,7 +232,7 @@ namespace umbraco /// which means we have to re-look up the document in the db again when we already have it, this should save on a few /// dozen sql calls when publishing. /// - internal static void UpdateDocumentCache(Document doc) + public static void UpdateDocumentCache(Document doc) { if (UmbracoSettings.UseDistributedCalls) dispatcher.Refresh( diff --git a/src/Umbraco.Web/umbraco.presentation/publishingService.cs b/src/Umbraco.Web/umbraco.presentation/publishingService.cs index f45c6d5916..163da00565 100644 --- a/src/Umbraco.Web/umbraco.presentation/publishingService.cs +++ b/src/Umbraco.Web/umbraco.presentation/publishingService.cs @@ -38,7 +38,7 @@ namespace umbraco.presentation d.ReleaseDate = DateTime.MinValue; //new DateTime(1, 1, 1); // Causes release date to be null d.Publish(d.User); - library.UpdateDocumentCache(d.Id); + library.UpdateDocumentCache(d); } catch(Exception ee) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/LiveEditing/Modules/CreateModule/CreateModule.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/LiveEditing/Modules/CreateModule/CreateModule.cs index 18b0e22a72..fd353d09cc 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/LiveEditing/Modules/CreateModule/CreateModule.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/LiveEditing/Modules/CreateModule/CreateModule.cs @@ -132,7 +132,7 @@ namespace umbraco.presentation.LiveEditing.Modules.CreateModule DocumentType typeToCreate = new DocumentType(Convert.ToInt32(m_AllowedDocTypesDropdown.SelectedValue)); Document newDoc = Document.MakeNew(m_NameTextBox.Text, typeToCreate, new global::umbraco.BusinessLogic.User(userid), (int)UmbracoContext.Current.PageId); newDoc.Publish(new global::umbraco.BusinessLogic.User(userid)); - library.UpdateDocumentCache(newDoc.Id); + library.UpdateDocumentCache(newDoc); Page.Response.Redirect(library.NiceUrl(newDoc.Id), false); break; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/actions/publish.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/actions/publish.aspx.cs index 94c88425ac..d58e002785 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/actions/publish.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/actions/publish.aspx.cs @@ -39,7 +39,7 @@ namespace umbraco.presentation.actions confirm.Visible = false; d.Publish(getUser()); - library.UpdateDocumentCache(d.Id); + library.UpdateDocumentCache(d); deleted.Text = ui.Text("editContentPublishedHeader") + " ('" + d.Text + "') " + ui.Text("editContentPublishedText") + "

" + ui.Text("view") + " " + d.Text + ""; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs index afe99ac4d2..c1089d77be 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/channels/UmbracoMetaWeblogAPI.cs @@ -84,7 +84,7 @@ namespace umbraco.presentation.channels if (publish) { doc.Publish(new User(username)); - library.UpdateDocumentCache(doc.Id); + library.UpdateDocumentCache(doc); } return true; } @@ -403,7 +403,7 @@ namespace umbraco.presentation.channels if (publish) { doc.Publish(new User(username)); - library.UpdateDocumentCache(doc.Id); + library.UpdateDocumentCache(doc); } return doc.Id.ToString(); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs index b4e966021c..3bd289e1e0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/moveOrCopy.aspx.cs @@ -152,7 +152,7 @@ namespace umbraco.dialogs if (cd.Published) { cd.Publish(new umbraco.BusinessLogic.User(0)); //using library.publish to support load balancing. - umbraco.library.UpdateDocumentCache(cd.Id); + umbraco.library.UpdateDocumentCache(cd); if (cd.HasChildren) { @@ -299,7 +299,7 @@ namespace umbraco.dialogs d.Publish(new umbraco.BusinessLogic.User(0)); //using library.publish to support load balancing. //umbraco.library.PublishSingleNode(d.Id); - umbraco.library.UpdateDocumentCache(d.Id); + umbraco.library.UpdateDocumentCache(d); //PPH added handling of load balanced moving of multiple nodes... if (d.HasChildren) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/publish.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/publish.aspx.cs index 9feee1ecd9..7d05314df7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/publish.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/publish.aspx.cs @@ -92,7 +92,7 @@ namespace umbraco.dialogs { if (doc.Published) { - library.UpdateDocumentCache(doc.Id); + library.UpdateDocumentCache(doc); } } @@ -116,7 +116,7 @@ namespace umbraco.dialogs { if (d.PublishWithResult(base.getUser())) { - library.UpdateDocumentCache(d.Id); + library.UpdateDocumentCache(d); feedbackMsg.type = umbraco.uicontrols.Feedback.feedbacktype.success; feedbackMsg.Text = ui.Text("publish", "nodePublish", d.Text, base.getUser()) + "

" + ui.Text("closeThisWindow") + ""; } @@ -141,7 +141,7 @@ namespace umbraco.dialogs { // Needed for supporting distributed calls if (UmbracoSettings.UseDistributedCalls) - library.UpdateDocumentCache(d.Id); + library.UpdateDocumentCache(d); else documents.Add(d); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/DocumentExtensions.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/DocumentExtensions.cs index 7c7b17c3a2..de897b6209 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/DocumentExtensions.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/DocumentExtensions.cs @@ -250,7 +250,7 @@ namespace umbraco } } - library.UpdateDocumentCache(document.Id); + library.UpdateDocumentCache(document); return document; } diff --git a/src/umbraco.webservices/documents/documentService.cs b/src/umbraco.webservices/documents/documentService.cs index caea93c368..cea5a61d7e 100644 --- a/src/umbraco.webservices/documents/documentService.cs +++ b/src/umbraco.webservices/documents/documentService.cs @@ -246,13 +246,13 @@ namespace umbraco.webservices.documents case documentCarrier.EPublishAction.Publish: if (doc.PublishWithResult(user)) { - umbraco.library.UpdateDocumentCache(doc.Id); + umbraco.library.UpdateDocumentCache(doc); } break; case documentCarrier.EPublishAction.Unpublish: if (doc.PublishWithResult(user)) { - umbraco.library.UnPublishSingleNode(doc.Id); + umbraco.library.UnPublishSingleNode(doc); } break; case documentCarrier.EPublishAction.Ignore: @@ -260,14 +260,14 @@ namespace umbraco.webservices.documents { if (doc.PublishWithResult(user)) { - umbraco.library.UpdateDocumentCache(doc.Id); + umbraco.library.UpdateDocumentCache(doc); } } else { if (doc.PublishWithResult(user)) { - umbraco.library.UpdateDocumentCache(doc.Id); + umbraco.library.UpdateDocumentCache(doc); } } break; From 0cb9cc6840599d22f0442d61256a92f9da2944a0 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 30 Jan 2013 07:59:15 +0600 Subject: [PATCH 08/16] just cleaned up some code formatting. --- .../umbraco/users/UserPermissions.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserPermissions.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserPermissions.cs index e2ee304244..018e6d9ff5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserPermissions.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/UserPermissions.cs @@ -102,11 +102,11 @@ namespace umbraco.cms.presentation.user ///

/// Returns the current user permissions for the node specified /// - /// + /// /// - public List GetExistingNodePermission(int nodeID) + public List GetExistingNodePermission(int nodeId) { - string path = GetNodePath(nodeID); + var path = GetNodePath(nodeId); if (path != "") { //get the user and their permissions @@ -119,13 +119,13 @@ namespace umbraco.cms.presentation.user /// /// gets path attribute for node id passed /// - /// + /// /// - private string GetNodePath(int iNodeID) + private static string GetNodePath(int iNodeId) { - if (Document.IsDocument(iNodeID)) + if (Document.IsDocument(iNodeId)) { - Document doc = new Document(iNodeID); + var doc = new Document(iNodeId); return doc.Path; } @@ -135,14 +135,13 @@ namespace umbraco.cms.presentation.user /// /// Finds all child node IDs /// - /// - /// + /// /// - private List FindChildNodes(int nodeID) + private static IEnumerable FindChildNodes(int nodeId) { - Document[] docs = Document.GetChildrenForTree(nodeID); - List nodeIds = new List(); - foreach (Document doc in docs) + var docs = Document.GetChildrenForTree(nodeId); + var nodeIds = new List(); + foreach (var doc in docs) { nodeIds.Add(doc.Id); if (doc.HasChildren) @@ -153,16 +152,16 @@ namespace umbraco.cms.presentation.user return nodeIds; } - private void InsertPermissions(int[] nodeIDs, IAction permission) + private void InsertPermissions(IEnumerable nodeIDs, IAction permission) { foreach (int i in nodeIDs) InsertPermission(i, permission); } - private void InsertPermission(int nodeID, IAction permission) + private void InsertPermission(int nodeId, IAction permission) { //create a new CMSNode object but don't initialize (this prevents a db query) - CMSNode node = new CMSNode(nodeID, false); + var node = new CMSNode(nodeId, false); Permission.MakeNew(m_user, node, permission.Letter); } From cc5ca0b5fe64ef21e9bdfdcc62745cd04cbfdf41 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 30 Jan 2013 09:21:43 -0100 Subject: [PATCH 09/16] Fixing U4-359 Previewing new nested pages doesn't work --- .../umbraco/preview/PreviewContent.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs index 92f6203beb..38c376591d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Data; using System.Configuration; +using System.Globalization; using System.Linq; using System.Web; using System.Web.Security; @@ -52,13 +54,30 @@ namespace umbraco.presentation.preview // clone xml XmlContent = (XmlDocument)content.Instance.XmlContent.Clone(); - // inject current document xml - int parentId = documentObject.Level == 1 ? -1 : documentObject.Parent.Id; - XmlContent = content.AppendDocumentXml(documentObject.Id, documentObject.Level, parentId, documentObject.ToPreviewXml(XmlContent), XmlContent); + var previewNodes = new List(); + + var parentId = documentObject.Level == 1 ? -1 : documentObject.Parent.Id; + + while (parentId > 0 && XmlContent.GetElementById(parentId.ToString(CultureInfo.InvariantCulture)) == null) + { + var document = new Document(parentId); + previewNodes.Insert(0, document); + parentId = document.ParentId; + } + + previewNodes.Add(documentObject); + + foreach (var document in previewNodes) + { + //Inject preview xml + parentId = document.Level == 1 ? -1 : document.Parent.Id; + var previewXml = document.ToPreviewXml(XmlContent); + content.AppendDocumentXml(document.Id, document.Level, parentId, previewXml, XmlContent); + } if (includeSubs) { - foreach (CMSPreviewNode prevNode in documentObject.GetNodesForPreview(true)) + foreach (var prevNode in documentObject.GetNodesForPreview(true)) { XmlContent = content.AppendDocumentXml(prevNode.NodeId, prevNode.Level, prevNode.ParentId, XmlContent.ReadNode(XmlReader.Create(new StringReader(prevNode.Xml))), XmlContent); } From d5bde0d8dfacce04f60fdeeff4f2d0bc57ac5db7 Mon Sep 17 00:00:00 2001 From: netaddicts Date: Thu, 24 Jan 2013 11:00:15 -0100 Subject: [PATCH 10/16] Makes sure preview is not using cached versions of any macro --- src/Umbraco.Web/umbraco.presentation/macro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 2b2d2f9a16..53fc5b940b 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -311,7 +311,7 @@ namespace umbraco Model.CacheIdentifier = GetCacheIdentifier(Model, pageElements, pageId); - if (Model.CacheDuration > 0) + if (!UmbracoContext.Current.InPreviewMode && Model.CacheDuration > 0) { if (cacheMacroAsString(Model)) { From 97556de447652691b196eb1128921edc04c28da2 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 30 Jan 2013 18:58:42 +0600 Subject: [PATCH 11/16] Changed methods created yesterday to internal so we can port to 6+ nicely. --- src/Umbraco.Web/Properties/AssemblyInfo.cs | 3 ++- src/Umbraco.Web/umbraco.presentation/content.cs | 2 +- src/Umbraco.Web/umbraco.presentation/library.cs | 4 ++-- src/umbraco.cms/businesslogic/web/Document.cs | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 88d65e23cc..aa897d6340 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -28,4 +28,5 @@ using System.Security; [assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1)] [assembly: InternalsVisibleTo("Umbraco.Tests")] -[assembly: InternalsVisibleTo("umbraco.MacroEngines")] \ No newline at end of file +[assembly: InternalsVisibleTo("umbraco.MacroEngines")] +[assembly: InternalsVisibleTo("umbraco.webservices")] diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 5161c14119..a8a7b60192 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -602,7 +602,7 @@ namespace umbraco /// This means the node gets unpublished from the website. ///
/// The document - public virtual void ClearDocumentCache(Document doc) + internal void ClearDocumentCache(Document doc) { var e = new DocumentCacheEventArgs(); FireBeforeClearDocumentCache(doc, e); diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 9a081380e3..dfba87492e 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -201,7 +201,7 @@ namespace umbraco /// marked unpublished by setting the publish property on the document object to false ///
/// The Document to be unpublished - public static void UnPublishSingleNode(Document document) + internal static void UnPublishSingleNode(Document document) { if (UmbracoSettings.UseDistributedCalls) dispatcher.Remove( @@ -232,7 +232,7 @@ namespace umbraco /// which means we have to re-look up the document in the db again when we already have it, this should save on a few /// dozen sql calls when publishing. /// - public static void UpdateDocumentCache(Document doc) + internal static void UpdateDocumentCache(Document doc) { if (UmbracoSettings.UseDistributedCalls) dispatcher.Refresh( diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 60f4b8024d..b4ebcf3ca6 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1541,7 +1541,7 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam /// Returns all descendants that are published on the front-end (hava a full published path) ///
/// - public IEnumerable GetPublishedDescendants() + internal IEnumerable GetPublishedDescendants() { var documents = new List(); using (var dr = SqlHelper.ExecuteReader( From b9be5c25615f0f69ae0b91169106ba40d0f42044 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 30 Jan 2013 15:01:38 -0100 Subject: [PATCH 12/16] Also implement optimization in fdfa687e6f41 to v4: no need to try and republish the child pages if the page you are trying to publish was already published --- .../umbraco/editContent.aspx.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs index c074e1cbad..0b971a189a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/editContent.aspx.cs @@ -309,6 +309,8 @@ namespace umbraco.cms.presentation { if (_document.Level == 1 || new cms.businesslogic.web.Document(_document.Parent.Id).PathPublished) { + var previouslyPublished = _document.Published; + Trace.Warn("before d.publish"); if (_document.PublishWithResult(base.getUser())) @@ -322,13 +324,17 @@ namespace umbraco.cms.presentation if (base.getUser().GetPermissions(_document.Path).IndexOf("U") > -1) UnPublish.Visible = true; - _documentHasPublishedVersion = _document.HasPublishedVersion(); - - foreach (var descendant in _document.GetPublishedDescendants()) + + if (previouslyPublished == false) { - library.UpdateDocumentCache(descendant); + _documentHasPublishedVersion = _document.HasPublishedVersion(); + + foreach (var descendant in _document.GetPublishedDescendants()) + { + library.UpdateDocumentCache(descendant); + } } - + } else { From a5bea7fc59596afe19a80065556d2156e874358f Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 31 Jan 2013 04:26:37 +0600 Subject: [PATCH 13/16] Added the ability to automate any c# scripts for an upgrade process. I realize this is superceded already in 6.0 but we need a way to do this in 4.x too especially for this release since we need to run a script to fix some db issues. I've added a framework using an UpgradeScriptManager and another install step + unit tests for some of the UpgradeScriptManager methods. --- .../Configuration/GlobalSettings.cs | 25 ++++ .../Install/UpgradeScriptsTests.cs | 66 +++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 ++ .../install/steps/UpgradeScripts.ascx.cs | 28 ++++ .../steps/UpgradeScripts.ascx.designer.cs | 24 ++++ .../Install/UpgradeScripts/ContentPathFix.cs | 130 ++++++++++++++++++ .../Install/UpgradeScripts/IUpgradeScript.cs | 7 + .../UpgradeScripts/UpgradeScriptManager.cs | 84 +++++++++++ .../UpgradeScripts/UpgradeScriptRegistrar.cs | 28 ++++ .../Install/UpgradeScripts/VersionRange.cs | 40 ++++++ src/Umbraco.Web/Properties/AssemblyInfo.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 10 +- src/Umbraco.Web/WebBootManager.cs | 2 + .../install/default.aspx.cs | 5 +- .../install/steps/Definitions/Database.cs | 23 ++-- .../steps/Definitions/FilePermissions.cs | 3 +- .../steps/Definitions/UpgradeScripts.cs | 78 +++++++++++ .../install/utills/p.aspx.cs | 7 + .../Installer/DefaultInstallerUtility.cs | 42 +----- .../Utility/Installer/VersionSpecs.cs | 44 ++++++ .../umbraco.datalayer.csproj | 1 + 22 files changed, 601 insertions(+), 56 deletions(-) create mode 100644 src/Umbraco.Tests/Install/UpgradeScriptsTests.cs create mode 100644 src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.cs create mode 100644 src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.designer.cs create mode 100644 src/Umbraco.Web/Install/UpgradeScripts/ContentPathFix.cs create mode 100644 src/Umbraco.Web/Install/UpgradeScripts/IUpgradeScript.cs create mode 100644 src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptManager.cs create mode 100644 src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptRegistrar.cs create mode 100644 src/Umbraco.Web/Install/UpgradeScripts/VersionRange.cs create mode 100644 src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/UpgradeScripts.cs create mode 100644 src/umbraco.datalayer/Utility/Installer/VersionSpecs.cs diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 3c15ce75cf..8728bb30ff 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -181,6 +181,31 @@ namespace Umbraco.Core.Configuration SaveSetting("umbracoConfigurationStatus", value); } } + + /// + /// Returns the configuration status version as a versoin object or null if it cannot parse + /// + /// + internal static Version GetConfigurationVersion() + { + //create a real version out of the one stored in the settings + var configVersion = ConfigurationStatus.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + if (configVersion.Length == 0) + return null; + + int major; + var minor = 0; + var patch = 0; + int currentPart; + if (configVersion.Length > 0 && int.TryParse(configVersion[0], out currentPart)) + major = currentPart; + else + return null; //couldn't parse, no valid version + if (configVersion.Length > 1 && int.TryParse(configVersion[1], out currentPart)) minor = currentPart; + if (configVersion.Length > 2 && int.TryParse(configVersion[2], out currentPart)) patch = currentPart; + + return new Version(major, minor, patch); + } /// /// Saves a setting into the configuration file. diff --git a/src/Umbraco.Tests/Install/UpgradeScriptsTests.cs b/src/Umbraco.Tests/Install/UpgradeScriptsTests.cs new file mode 100644 index 0000000000..04a3f81dfb --- /dev/null +++ b/src/Umbraco.Tests/Install/UpgradeScriptsTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Umbraco.Web.Install.UpgradeScripts; + +namespace Umbraco.Tests.Install +{ + [TestFixture] + public class UpgradeScriptsTests + { + [TearDown] + public void TearDown() + { + UpgradeScriptManager.Clear(); + } + + [TestCase("0.0.0", "6.0.0", "4.10.0", true)] + [TestCase("4.10.0", "6.0.0", "4.10.0", true)] + [TestCase("4.10.0", "4.11.4", "4.10.0", true)] + [TestCase("4.11.0", "4.11.4", "4.10.0", false)] + [TestCase("4.11.0", "6.0.0", "4.10.0", false)] + [TestCase("6.0.0", "6.0.0", "6.0.0", false)] //this is not in range because it is up to 6.0 but not including 6.0 + [TestCase("6.0.0", "6.0.0", "6.0.1", false)] + public void Test_Version_Range(string startVersion, string endVersion, string current, bool inRange) + { + var currVersionParts = current.Split('.').Select(int.Parse).ToArray(); + var currentVersion = new Version(currVersionParts[0], currVersionParts[1], currVersionParts[2]); + + var startVersionParts = startVersion.Split('.').Select(int.Parse).ToArray(); + var endVersionParts = endVersion.Split('.').Select(int.Parse).ToArray(); + + UpgradeScriptManager.AddUpgradeScript( + new VersionRange( + new Version(startVersionParts[0], startVersionParts[1], startVersionParts[2]), + new Version(endVersionParts[0], endVersionParts[1], endVersionParts[2]))); + + Assert.AreEqual(inRange, UpgradeScriptManager.HasScriptsForVersion(currentVersion)); + } + + [Test] + public void Test_Specific_Version() + { + var currentVersion = new Version(4, 10, 0); + + UpgradeScriptManager.AddUpgradeScript( + new VersionRange( + new Version(4, 10, 0))); + + Assert.IsTrue(UpgradeScriptManager.HasScriptsForVersion(currentVersion)); + Assert.IsFalse(UpgradeScriptManager.HasScriptsForVersion(new Version(4, 10, 11))); + Assert.IsFalse(UpgradeScriptManager.HasScriptsForVersion(new Version(4, 11, 0))); + } + + public class UpgradeScript1 : IUpgradeScript + { + public void Execute() + { + Debug.WriteLine("Executing!"); + } + } + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index c7bfb8cba0..0634e58db7 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -63,6 +63,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 09d081e51f..b99a660c02 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -289,6 +289,13 @@ loadStarterKits.ascx + + UpgradeScripts.ascx + ASPXCodeBehind + + + UpgradeScripts.ascx + editMacro.aspx @@ -418,6 +425,7 @@ UI.xml + diff --git a/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.cs b/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.cs new file mode 100644 index 0000000000..0d94b30821 --- /dev/null +++ b/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using Umbraco.Core.Configuration; +using umbraco.presentation.install; +using Umbraco.Web.Install.UpgradeScripts; + +namespace Umbraco.Web.UI.Install.Steps +{ + public partial class UpgradeScripts : System.Web.UI.UserControl + { + protected void Page_Load(object sender, EventArgs e) + { + + } + + protected void RunScripts(object sender, EventArgs e) + { + //run the scripts and then go to the next step + UpgradeScriptManager.ExecuteScriptsForVersion(GlobalSettings.GetConfigurationVersion()); + + Helper.RedirectToNextStep(Page); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.designer.cs b/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.designer.cs new file mode 100644 index 0000000000..a55927fd35 --- /dev/null +++ b/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx.designer.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Umbraco.Web.UI.Install.Steps { + + + public partial class UpgradeScripts { + + /// + /// btnNext control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnNext; + } +} diff --git a/src/Umbraco.Web/Install/UpgradeScripts/ContentPathFix.cs b/src/Umbraco.Web/Install/UpgradeScripts/ContentPathFix.cs new file mode 100644 index 0000000000..f52ae5078d --- /dev/null +++ b/src/Umbraco.Web/Install/UpgradeScripts/ContentPathFix.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.IO; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.web; + +namespace Umbraco.Web.Install.UpgradeScripts +{ + /// + /// An upgrade script to fix a moving issue in 4.10+ + /// http://issues.umbraco.org/issue/U4-1491 + /// + public class ContentPathFix : IUpgradeScript + { + private readonly StringBuilder _report = new StringBuilder(); + + public void Execute() + { + //return; + if (ApplicationContext.Current == null) return; + if (HasBeenFixed()) return; + Fix(); + WriteReport(); + } + + private void Fix() + { + AddReportLine("Starting fix paths script"); + + //fix content + AddReportLine("Fixing content"); + foreach (var d in Document.GetRootDocuments()) + { + FixPathsForChildren(d, content => ((Document)content).Children); + } + AddReportLine("Fixing content recycle bin"); + var contentRecycleBin = new RecycleBin(RecycleBin.RecycleBinType.Content); + foreach (var d in contentRecycleBin.Children) + { + FixPathsForChildren(new Document(d.Id), content => ((Document)content).Children); + } + + //fix media + AddReportLine("Fixing media"); + foreach (var d in global::umbraco.cms.businesslogic.media.Media.GetRootMedias()) + { + FixPathsForChildren(d, media => ((global::umbraco.cms.businesslogic.media.Media)media).Children); + } + AddReportLine("Fixing media recycle bin"); + var mediaRecycleBin = new RecycleBin(RecycleBin.RecycleBinType.Media); + foreach (var d in mediaRecycleBin.Children) + { + FixPathsForChildren(new global::umbraco.cms.businesslogic.media.Media(d.Id), media => ((global::umbraco.cms.businesslogic.media.Media)media).Children); + } + AddReportLine("Complete!"); + } + + /// + /// Returns true if this script has run based on a temp file written to + /// ~/App_Data/TEMP/FixPaths/report.txt + /// + /// + private bool HasBeenFixed() + { + return File.Exists(IOHelper.MapPath("~/App_Data/TEMP/FixPaths/report.txt")); + } + + /// + /// Creates the report + /// + private void WriteReport() + { + var filePath = IOHelper.MapPath("~/App_Data/TEMP/FixPaths/report.txt"); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + using (var writer = File.CreateText(IOHelper.MapPath("~/App_Data/TEMP/FixPaths/report.txt"))) + { + writer.Write(_report.ToString()); + } + } + + /// + /// Recursively iterates over the children of the document and fixes the path + /// + /// + /// Callback to get the children of the conent item + /// + /// We cannot use GetDescendants() because that is based on the paths of documents and if they are invalid then + /// we cannot use that method. + /// + private void FixPathsForChildren(Content d, Func> getChildren) + { + AddReportLine("Fixing paths for children of " + d.Id); + foreach (var c in getChildren(d)) + { + FixPath(c); + if (c.HasChildren) + { + FixPathsForChildren(c, getChildren); + } + } + } + + /// + /// Check if the path is correct based on the document's parent if it is not correct, then fix it + /// + /// + private void FixPath(CMSNode d) + { + AddReportLine("Checking path for " + d.Id + ". Current = " + d.Path); + //check if the path is correct + var correctpath = d.Parent.Path + "," + d.Id.ToString(); + if (d.Path != correctpath) + { + AddReportLine(" INVALID PATH DETECTED. Path for " + d.Id + " changed to: " + d.Parent.Path + "," + d.Id.ToString()); + d.Path = correctpath; + d.Level = d.Parent.Level + 1; + } + } + + private void AddReportLine(string str) + { + _report.AppendLine(string.Format("{0} - " + str, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"))); + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/UpgradeScripts/IUpgradeScript.cs b/src/Umbraco.Web/Install/UpgradeScripts/IUpgradeScript.cs new file mode 100644 index 0000000000..339dbd4ac2 --- /dev/null +++ b/src/Umbraco.Web/Install/UpgradeScripts/IUpgradeScript.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Web.Install.UpgradeScripts +{ + internal interface IUpgradeScript + { + void Execute(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptManager.cs b/src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptManager.cs new file mode 100644 index 0000000000..472415598d --- /dev/null +++ b/src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptManager.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Configuration; +using umbraco.DataLayer.Utility.Installer; + +namespace Umbraco.Web.Install.UpgradeScripts +{ + /// + /// Class used to register and execute upgrade scripts during install if they are required. + /// + internal static class UpgradeScriptManager + { + /// + /// Returns true if there are scripts to execute for the version + /// + /// + /// + public static bool HasScriptsForVersion(Version version) + { + return Scripts.Any(x => x.Item2.InRange(version)); + } + + /// + /// Executes all of the scripts for a database version + /// + /// + /// + public static void ExecuteScriptsForVersion(Version version) + { + var types = Scripts.Where(x => x.Item2.InRange(version)).Select(x => x.Item1); + foreach (var instance in types.Select(x => x())) + { + instance.Execute(); + } + } + + public static void AddUpgradeScript(Func script, VersionRange version) + { + Scripts.Add(new Tuple, VersionRange>(script, version)); + } + + ///// + ///// Adds a script to execute for a database version + ///// + ///// + ///// + //public static void AddUpgradeScript(string assemblyQualifiedTypeName, VersionRange version) + //{ + // AddUpgradeScript(new Lazy(() => Type.GetType(assemblyQualifiedTypeName)), version); + //} + + ///// + ///// Adds a script to execute for a database version + ///// + ///// + ///// + //public static void AddUpgradeScript(VersionRange version) + //{ + // AddUpgradeScript(new Lazy(() => typeof(T)), version); + //} + + /// + /// Used for testing + /// + internal static void Clear() + { + Scripts.Clear(); + } + + ///// + ///// Adds a script to execute for a database version + ///// + ///// + ///// + //public static void AddUpgradeScript(Lazy type, VersionRange version) + //{ + // Scripts.Add(new Tuple, VersionRange>(type, version)); + //} + + private static readonly List, VersionRange>> Scripts = new List, VersionRange>>(); + //private static readonly List, VersionRange>> Scripts = new List, VersionRange>>(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptRegistrar.cs b/src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptRegistrar.cs new file mode 100644 index 0000000000..611703b7d8 --- /dev/null +++ b/src/Umbraco.Web/Install/UpgradeScripts/UpgradeScriptRegistrar.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core; + +namespace Umbraco.Web.Install.UpgradeScripts +{ + internal class UpgradeScriptRegistrar : IApplicationEventHandler + { + public void OnApplicationInitialized(UmbracoApplication httpApplication, ApplicationContext applicationContext) + { + //Add contnet path fixup for any version from 4.10 up to 4.11.4 + UpgradeScriptManager.AddUpgradeScript( + () => new ContentPathFix(), + new VersionRange( + new Version(4, 10), + new Version(4, 11, 4))); + } + + public void OnApplicationStarting(UmbracoApplication httpApplication, ApplicationContext applicationContext) + { + + } + + public void OnApplicationStarted(UmbracoApplication httpApplication, ApplicationContext applicationContext) + { + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/UpgradeScripts/VersionRange.cs b/src/Umbraco.Web/Install/UpgradeScripts/VersionRange.cs new file mode 100644 index 0000000000..adafd7691a --- /dev/null +++ b/src/Umbraco.Web/Install/UpgradeScripts/VersionRange.cs @@ -0,0 +1,40 @@ +using System; + +namespace Umbraco.Web.Install.UpgradeScripts +{ + internal class VersionRange + { + private readonly Version _specificVersion; + private readonly Version _startVersion; + private readonly Version _endVersion; + + public VersionRange(Version specificVersion) + { + _specificVersion = specificVersion; + } + + public VersionRange(Version startVersion, Version endVersion) + { + _startVersion = startVersion; + _endVersion = endVersion; + } + + /// + /// Checks if the versionCheck is in the range (in between) the start and end version + /// + /// + /// + /// + /// For example if our version range is 4.10 -> 4.11.4, we want to return true if the version being checked is: + /// greater than or equal to the start version but less than the end version. + /// + public bool InRange(Version versionCheck) + { + //if it is a specific version + if (_specificVersion != null) + return versionCheck == _specificVersion; + + return versionCheck >= _startVersion && versionCheck < _endVersion; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index aa897d6340..03623505f9 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -30,3 +30,4 @@ using System.Security; [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("umbraco.MacroEngines")] [assembly: InternalsVisibleTo("umbraco.webservices")] +[assembly: InternalsVisibleTo("Umbraco.Web.UI")] diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c7e6b7336b..5c7a263b12 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -252,6 +252,11 @@ + + + + + @@ -329,6 +334,7 @@ ASPXCodeBehind + ASPXCodeBehind @@ -1894,7 +1900,9 @@ ASPXCodeBehind - + + ASPXCodeBehind + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index b4aed9339d..915144efcf 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Dictionary; +using Umbraco.Web.Install.UpgradeScripts; using Umbraco.Web.Media; using Umbraco.Web.Media.ThumbnailProviders; using Umbraco.Web.Models; @@ -90,6 +91,7 @@ namespace Umbraco.Web //add the internal types since we don't want to mark these public ApplicationEventsResolver.Current.AddType(); ApplicationEventsResolver.Current.AddType(); + ApplicationEventsResolver.Current.AddType(); //now we need to call the initialize methods ApplicationEventsResolver.Current.ApplicationEventHandlers diff --git a/src/Umbraco.Web/umbraco.presentation/install/default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/install/default.aspx.cs index 1bd81f7df3..16ceb1195f 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/default.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/default.aspx.cs @@ -9,7 +9,7 @@ using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.Collections.Specialized; -using umbraco.IO; +using Umbraco.Core.IO; using umbraco.cms.businesslogic.installer; using System.Collections.Generic; @@ -36,7 +36,7 @@ namespace umbraco.presentation.install private void loadContent(InstallerStep currentStep) { PlaceHolderStep.Controls.Clear(); - PlaceHolderStep.Controls.Add(new System.Web.UI.UserControl().LoadControl(IOHelper.ResolveUrl(currentStep.UserControl))); + PlaceHolderStep.Controls.Add(LoadControl(IOHelper.ResolveUrl(currentStep.UserControl))); step.Value = currentStep.Alias; currentStepClass = currentStep.Alias; } @@ -136,6 +136,7 @@ namespace umbraco.presentation.install ics.Add(new install.steps.Definitions.Welcome()); ics.Add(new install.steps.Definitions.License()); ics.Add(new install.steps.Definitions.FilePermissions()); + ics.Add(new install.steps.Definitions.UpgradeScripts()); ics.Add(new install.steps.Definitions.Database()); ics.Add(new install.steps.Definitions.DefaultUser()); ics.Add(new install.steps.Definitions.Skinning()); diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs index 9293d0e84b..9c3cadd748 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Web; +using Umbraco.Core.IO; using umbraco.cms.businesslogic.installer; -using umbraco.IO; + using umbraco.DataLayer.Utility.Installer; using umbraco.DataLayer; @@ -18,7 +18,7 @@ namespace umbraco.presentation.install.steps.Definitions public override string Name { - get { return "Database"; } + get { return "Database"; } } public override string UserControl @@ -26,7 +26,7 @@ namespace umbraco.presentation.install.steps.Definitions get { return SystemDirectories.Install + "/steps/database.ascx"; } } - + public override bool MoveToNextStepAutomaticly { get @@ -38,13 +38,14 @@ namespace umbraco.presentation.install.steps.Definitions //here we determine if the installer should skip this step... public override bool Completed() { - bool retval = false; + bool retval; try { - IInstallerUtility m_Installer = BusinessLogic.Application.SqlHelper.Utility.CreateInstaller(); - retval = m_Installer.IsLatestVersion; - m_Installer = null; - } catch { + var installer = BusinessLogic.Application.SqlHelper.Utility.CreateInstaller(); + retval = installer.IsLatestVersion; + } + catch + { // this step might fail due to missing connectionstring return false; } @@ -52,6 +53,6 @@ namespace umbraco.presentation.install.steps.Definitions return retval; } - + } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/FilePermissions.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/FilePermissions.cs index ef3827225f..f0f6887ed3 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/FilePermissions.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/FilePermissions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using Umbraco.Core.IO; using umbraco.cms.businesslogic.installer; namespace umbraco.presentation.install.steps.Definitions @@ -20,7 +21,7 @@ namespace umbraco.presentation.install.steps.Definitions public override string UserControl { - get { return IO.SystemDirectories.Install + "/steps/validatepermissions.ascx"; } + get { return SystemDirectories.Install + "/steps/validatepermissions.ascx"; } } public override bool HideFromNavigation { diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/UpgradeScripts.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/UpgradeScripts.cs new file mode 100644 index 0000000000..57d4cf9d9b --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/UpgradeScripts.cs @@ -0,0 +1,78 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Web.Install.UpgradeScripts; +using umbraco.DataLayer.Utility.Installer; +using umbraco.cms.businesslogic.installer; + +namespace umbraco.presentation.install.steps.Definitions +{ + internal class UpgradeScripts : InstallerStep + { + + + + public override string Alias + { + get { return "upgradeScripts"; } + } + + public override bool HideFromNavigation + { + get { return true; } + } + + /// + /// If there are no scripts for this version the skip + /// + /// + public override bool Completed() + { + var canConnect = CanConnectToDb(); + //if we cannot connect to the db, then we cannot run the script and most likely the database doesn't exist yet anyways. + if (!canConnect) return true; //skip + + //if the version is empty then it's probably a new installation, we cannot run scripts + if (GlobalSettings.CurrentVersion.IsNullOrWhiteSpace()) return true; //skip + var currentUmbVersion = Umbraco.Core.Configuration.GlobalSettings.GetConfigurationVersion(); + if (currentUmbVersion == null) + return true; //skip, could not get a version + + //check if we have upgrade script to run for this version + var hasScripts = UpgradeScriptManager.HasScriptsForVersion(currentUmbVersion); + return !hasScripts; + } + + public override string Name + { + get { return "Upgrade scripts"; } + } + + public override string UserControl + { + get { return SystemDirectories.Install + "/steps/UpgradeScripts.ascx"; } + } + + public override bool MoveToNextStepAutomaticly + { + get + { + return true; + } + } + + private bool CanConnectToDb() + { + try + { + var installer = BusinessLogic.Application.SqlHelper.Utility.CreateInstaller(); + var latest = installer.IsLatestVersion; + return true; //if we got this far, we can connect. + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/install/utills/p.aspx.cs b/src/Umbraco.Web/umbraco.presentation/install/utills/p.aspx.cs index 67fb8c6d78..8576f12366 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/utills/p.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/utills/p.aspx.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; +using Umbraco.Web.Install.UpgradeScripts; using umbraco.DataLayer.Utility.Installer; using umbraco.DataLayer; @@ -97,6 +98,8 @@ namespace umbraco.presentation.install.utills Helper.setProgress(100, "Database is up-to-date", ""); + return "upgraded"; + } else { @@ -136,6 +139,7 @@ namespace umbraco.presentation.install.utills installer = null; library.RefreshContent(); + return "upgraded"; } } @@ -146,5 +150,8 @@ namespace umbraco.presentation.install.utills return "no connection;"; } + + + } } \ No newline at end of file diff --git a/src/umbraco.datalayer/Utility/Installer/DefaultInstallerUtility.cs b/src/umbraco.datalayer/Utility/Installer/DefaultInstallerUtility.cs index 16c51bae78..b88f7ac157 100644 --- a/src/umbraco.datalayer/Utility/Installer/DefaultInstallerUtility.cs +++ b/src/umbraco.datalayer/Utility/Installer/DefaultInstallerUtility.cs @@ -261,45 +261,5 @@ namespace umbraco.DataLayer.Utility.Installer #endregion } - /// - /// A triple (Field, Table, Version) meaning: - /// if a SELECT statement of Field FROM Table succeeds, - /// the database version is at least Version. - /// - /// - /// This also supports checking for a value in a table. - /// - public struct VersionSpecs - { - /// The SQL statament to execute in order to test for the specified version - public readonly string Sql; - - /// An integer identifying the expected row count from the Sql statement - public readonly int ExpectedRows; - - /// The minimum version number of a database that contains the specified field. - public readonly DatabaseVersion Version; - - /// - /// Initializes a new instance of the struct. - /// - /// The sql statement to execute. - /// The version. - public VersionSpecs(string sql, DatabaseVersion version) - : this(sql, -1, version) - { } - - /// - /// Initializes a new instance of the struct. - /// - /// The sql statement to execute. - /// The expected row count. - /// The version. - public VersionSpecs(string sql, int expectedRows, DatabaseVersion version) - { - Sql = sql; - ExpectedRows = expectedRows; - Version = version; - } - } + } diff --git a/src/umbraco.datalayer/Utility/Installer/VersionSpecs.cs b/src/umbraco.datalayer/Utility/Installer/VersionSpecs.cs new file mode 100644 index 0000000000..c1237e1e97 --- /dev/null +++ b/src/umbraco.datalayer/Utility/Installer/VersionSpecs.cs @@ -0,0 +1,44 @@ +namespace umbraco.DataLayer.Utility.Installer +{ + /// + /// A triple (Field, Table, Version) meaning: + /// if a SELECT statement of Field FROM Table succeeds, + /// the database version is at least Version. + /// + /// + /// This also supports checking for a value in a table. + /// + public struct VersionSpecs + { + /// The SQL statament to execute in order to test for the specified version + public readonly string Sql; + + /// An integer identifying the expected row count from the Sql statement + public readonly int ExpectedRows; + + /// The minimum version number of a database that contains the specified field. + public readonly DatabaseVersion Version; + + /// + /// Initializes a new instance of the struct. + /// + /// The sql statement to execute. + /// The version. + public VersionSpecs(string sql, DatabaseVersion version) + : this(sql, -1, version) + { } + + /// + /// Initializes a new instance of the struct. + /// + /// The sql statement to execute. + /// The expected row count. + /// The version. + public VersionSpecs(string sql, int expectedRows, DatabaseVersion version) + { + Sql = sql; + ExpectedRows = expectedRows; + Version = version; + } + } +} \ No newline at end of file diff --git a/src/umbraco.datalayer/umbraco.datalayer.csproj b/src/umbraco.datalayer/umbraco.datalayer.csproj index 73d169333e..6297b07bc4 100644 --- a/src/umbraco.datalayer/umbraco.datalayer.csproj +++ b/src/umbraco.datalayer/umbraco.datalayer.csproj @@ -117,6 +117,7 @@ + From 61f2647b6222d165317038f861f0b8e7356d3dc8 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 31 Jan 2013 04:26:47 +0600 Subject: [PATCH 14/16] missing file --- .../install/steps/UpgradeScripts.ascx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx diff --git a/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx b/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx new file mode 100644 index 0000000000..2728a12f66 --- /dev/null +++ b/src/Umbraco.Web.UI/install/steps/UpgradeScripts.ascx @@ -0,0 +1,14 @@ +<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="UpgradeScripts.ascx.cs" Inherits="Umbraco.Web.UI.Install.Steps.UpgradeScripts" %> +
+
+

Upgrade scripts

+

+ We need to run a few upgrade scripts, press Continue to execute these scripts and continue to the next step. +

+
+ +
+
 
+ Continue +
+
\ No newline at end of file From fbc10be3815f2e9d775bda25ef189d795343f2d1 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 31 Jan 2013 04:46:51 +0600 Subject: [PATCH 15/16] Reverted logic of Document to be what it used to be... Published and HasPublishedVersion() are the same and will return the same thing, though we've kept the lazy loading and optimizations. --- .../Install/UpgradeScriptsTests.cs | 6 +- src/umbraco.cms/businesslogic/web/Document.cs | 59 +++++++++++-------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Tests/Install/UpgradeScriptsTests.cs b/src/Umbraco.Tests/Install/UpgradeScriptsTests.cs index 04a3f81dfb..daf95b2241 100644 --- a/src/Umbraco.Tests/Install/UpgradeScriptsTests.cs +++ b/src/Umbraco.Tests/Install/UpgradeScriptsTests.cs @@ -32,7 +32,8 @@ namespace Umbraco.Tests.Install var startVersionParts = startVersion.Split('.').Select(int.Parse).ToArray(); var endVersionParts = endVersion.Split('.').Select(int.Parse).ToArray(); - UpgradeScriptManager.AddUpgradeScript( + UpgradeScriptManager.AddUpgradeScript( + () => new UpgradeScript1(), new VersionRange( new Version(startVersionParts[0], startVersionParts[1], startVersionParts[2]), new Version(endVersionParts[0], endVersionParts[1], endVersionParts[2]))); @@ -45,7 +46,8 @@ namespace Umbraco.Tests.Install { var currentVersion = new Version(4, 10, 0); - UpgradeScriptManager.AddUpgradeScript( + UpgradeScriptManager.AddUpgradeScript( + () => new UpgradeScript1(), new VersionRange( new Version(4, 10, 0))); diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index b4ebcf3ca6..5d79309f4a 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -227,9 +227,9 @@ order by {1} private int _template; /// - /// a backing property for the 'Published' property + /// a backing property for the 'IsDocumentLive()' method /// - private bool? _published; + private bool? _isDocumentLive; /// /// a backing property for the 'HasPublishedVersion()' method @@ -808,29 +808,39 @@ order by {1} { get { - if (!_published.HasValue) - { - // get all nodes in the path to the document, and get all matching published documents - // the difference should be zero if everything is published - // test nodeObjectType to make sure we only count _content_ nodes - var sql = @"select count(node.id) - count(doc.nodeid) + //this is always the same as HasPublishedVersion in 4.x + return HasPublishedVersion(); + } + set + { + _hasPublishedVersion = value; + SqlHelper.ExecuteNonQuery( + string.Format("update cmsDocument set published = {0} where nodeId = {1}", Id, value ? 1 : 0)); + } + } + + /// + /// Will return true if the document is published and live on the front-end. + /// + /// + internal bool IsDocumentLive() + { + if (!_isDocumentLive.HasValue) + { + // get all nodes in the path to the document, and get all matching published documents + // the difference should be zero if everything is published + // test nodeObjectType to make sure we only count _content_ nodes + var sql = @"select count(node.id) - count(doc.nodeid) from umbracoNode as node left join cmsDocument as doc on (node.id=doc.nodeId and doc.published=1) where (('" + Path + ",' like " + SqlHelper.Concat("node.path", "',%'") + @") or ('" + Path + @"' = node.path)) and node.id <> -1 and node.nodeObjectType=@nodeObjectType"; - var count = SqlHelper.ExecuteScalar(sql, SqlHelper.CreateParameter("@nodeObjectType", Document._objectType)); - _published = (count == 0); - } - return _published.Value; - } - set - { - _published = value; - SqlHelper.ExecuteNonQuery( - string.Format("update cmsDocument set published = {0} where nodeId = {1}", Id, value ? 1 : 0)); + var count = SqlHelper.ExecuteScalar(sql, SqlHelper.CreateParameter("@nodeObjectType", Document._objectType)); + _isDocumentLive = (count == 0); } + return _isDocumentLive.Value; } /// @@ -1093,7 +1103,7 @@ and node.nodeObjectType=@nodeObjectType"; _template = new DocumentType(this.ContentType.Id).DefaultTemplate; } - _published = true; + _hasPublishedVersion = true; string tempVersion = Version.ToString(); DateTime versionDate = DateTime.Now; Guid newVersion = createNewVersion(versionDate); @@ -1231,7 +1241,7 @@ and node.nodeObjectType=@nodeObjectType"; if (!e.Cancel) { - _published = true; + _hasPublishedVersion = true; string tempVersion = Version.ToString(); DateTime versionDate = DateTime.Now; Guid newVersion = createNewVersion(versionDate); @@ -1259,7 +1269,7 @@ and node.nodeObjectType=@nodeObjectType"; public void UnPublish() { - UnPublishEventArgs e = new UnPublishEventArgs(); + var e = new UnPublishEventArgs(); FireBeforeUnPublish(e); @@ -1267,7 +1277,7 @@ and node.nodeObjectType=@nodeObjectType"; { SqlHelper.ExecuteNonQuery(string.Format("update cmsDocument set published = 0 where nodeId = {0}", Id)); - _published = false; + _hasPublishedVersion = false; FireAfterUnPublish(e); } @@ -1278,7 +1288,7 @@ and node.nodeObjectType=@nodeObjectType"; /// public override void Save() { - SaveEventArgs e = new SaveEventArgs(); + var e = new SaveEventArgs(); FireBeforeSave(e); if (!e.Cancel) @@ -1314,6 +1324,7 @@ and node.nodeObjectType=@nodeObjectType"; /// public bool HasPublishedVersion() { + //lazy load the value if it is not set. if (!_hasPublishedVersion.HasValue) { var count = SqlHelper.ExecuteScalar(@" @@ -1841,7 +1852,7 @@ where published = 1 And nodeId = @nodeId And trashed = 0", SqlHelper.CreateParam _release = InitReleaseDate; _expire = InitExpireDate; _updated = InitUpdateDate; - _published = InitPublished; + _hasPublishedVersion = InitPublished; } /// From a909b7904ed6fc0d778b9d7ab20d69711fc16b28 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Fri, 1 Feb 2013 05:29:54 +0600 Subject: [PATCH 16/16] Fixes uGoLive ascx --- src/Umbraco.Web.UI/umbraco/plugins/uGoLive/Dashboard.ascx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/plugins/uGoLive/Dashboard.ascx b/src/Umbraco.Web.UI/umbraco/plugins/uGoLive/Dashboard.ascx index 973bdc1028..a22af5a4f4 100644 --- a/src/Umbraco.Web.UI/umbraco/plugins/uGoLive/Dashboard.ascx +++ b/src/Umbraco.Web.UI/umbraco/plugins/uGoLive/Dashboard.ascx @@ -1,4 +1,4 @@ -<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Dashboard.ascx.cs" Inherits="Our.Umbraco.uGoLive.Web.Umbraco.Plugins.uGoLive.Dashboard" %> +<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Dashboard.ascx.cs" Inherits="Our.Umbraco.uGoLive.Web.Umbraco.Plugins.uGoLive.Dashboard" %> <%@ Import Namespace="umbraco.IO" %> <%@ Import Namespace="Our.Umbraco.uGoLive.Web" %> @@ -24,7 +24,7 @@ -
+