From a8029d1dc596e5e33f9c6362c15a6f7774932f0e Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 24 Apr 2013 13:19:30 -1000 Subject: [PATCH 01/10] Fixes: #U4-1772 - ensures that when changing a doc type alias that we only rebuild the data in the cmsContentXml table for Document types, not media types or member types, now we need to fix all of the other bugs assocated with cmsContentXml table and alias changes. --- .../controls/ContentTypeControlNew.ascx.cs | 56 ++++++++++++++----- src/umbraco.cms/businesslogic/web/Document.cs | 24 ++++---- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 638a7eb7f6..780fab6246 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -13,6 +13,7 @@ using ClientDependency.Core; using Umbraco.Core; using Umbraco.Core.Logging; using umbraco.BusinessLogic; +using umbraco.cms.businesslogic.web; using umbraco.cms.helpers; using umbraco.cms.presentation.Trees; using umbraco.controls.GenericProperties; @@ -65,9 +66,8 @@ namespace umbraco.controls override protected void OnInit(EventArgs e) { base.OnInit(e); - - int docTypeId = GetDocTypeId(); - _contentType = new cms.businesslogic.ContentType(docTypeId); + + LoadContentType(); SetupInfoPane(); if (!HideStructure) @@ -77,12 +77,7 @@ namespace umbraco.controls SetupGenericPropertiesPane(); SetupTabPane(); - } - - private int GetDocTypeId() - { - return int.Parse(Request.QueryString["id"]); - } + } protected void Page_Load(object sender, System.EventArgs e) { @@ -187,6 +182,8 @@ namespace umbraco.controls //get the args from the async state var state = (SaveAsyncState)ar.AsyncState; + // reload content type (due to caching) + LoadContentType(); BindDataGenericProperties(true); // we need to re-bind the alias as the SafeAlias method can have changed it @@ -245,9 +242,6 @@ namespace umbraco.controls SaveTabs(); SaveAllowedChildTypes(); - - // reload content type (due to caching) - _contentType = new ContentType(_contentType.Id); // Only if the doctype alias changed, cause a regeneration of the xml cache file since // the xml element names will need to be updated to reflect the new alias @@ -261,13 +255,47 @@ namespace umbraco.controls Page.ExecuteRegisteredAsyncTasks(); } + /// + /// Loads the current ContentType from the id found in the querystring. + /// The correct type is loaded based on editing location (DocumentType, MediaType or MemberType). + /// + private void LoadContentType() + { + int docTypeId = int.Parse(Request.QueryString["id"]); + LoadContentType(docTypeId); + } + + private void LoadContentType(int docTypeId) + { + //Fairly hacky code to load the ContentType as the real type instead of its base type, so it can be properly saved. + if (Request.Path.ToLowerInvariant().Contains("editnodetypenew.aspx")) + { + _contentType = new DocumentType(docTypeId); + } + else if (Request.Path.ToLowerInvariant().Contains("editmediatype.aspx")) + { + _contentType = new cms.businesslogic.media.MediaType(docTypeId); + } + else + { + _contentType = new ContentType(docTypeId); + } + } + /// /// Regenerates the XML caches. Used after a document type alias has been changed. /// + /// + /// We only regenerate any XML cache based on if this is a Document type, not a media type or + /// a member type. + /// private void RegenerateXmlCaches() { - umbraco.cms.businesslogic.web.Document.RePublishAll(); - library.RefreshContent(); + if (_contentType is DocumentType) + { + umbraco.cms.businesslogic.web.Document.RePublishAll(); + library.RefreshContent(); + } } private void UpdateTreeNode() diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index c9412a2fc5..925cdf2bdf 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -628,20 +628,24 @@ order by {1} return tmp; } + /// + /// This will clear out the cmsContentXml table for all Documents (not media or members) and then + /// rebuild the xml for each Docuemtn item and store it in this table. + /// + /// + /// This method is thread safe + /// [MethodImpl(MethodImplOptions.Synchronized)] public static void RePublishAll() { - XmlDocument xd = new XmlDocument(); + var xd = new XmlDocument(); - if (!DataLayerHelper.IsEmbeddedDatabase(SqlHelper.ConnectionString)) - { - SqlHelper.ExecuteNonQuery("truncate table cmsContentXml"); - } - else - { - SqlHelper.ExecuteNonQuery("delete from cmsContentXml"); - } - IRecordsReader dr = SqlHelper.ExecuteReader("select nodeId from cmsDocument where published = 1"); + //Remove all Documents (not media or members), only Documents are stored in the cmsDocument table + SqlHelper.ExecuteNonQuery(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN cmsDocument ON cmsContentXml.nodeId = cmsDocument.nodeId)"); + + var dr = SqlHelper.ExecuteReader("select nodeId from cmsDocument where published = 1"); while (dr.Read()) { From 0e8ffe528a46229c1aab9fce5f67af902fd3ab46 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 24 Apr 2013 14:57:20 -1000 Subject: [PATCH 02/10] Fixes: #U4-2149 - updates the logic for changing aliases for any content type. Now if an alias changes for any content type, the xml is regenerated for any entity that is of that content type. For DocumentType's this is slightly different because we regenerate the xml for the doc type and any document of the doc type's descendant types. --- .../controls/ContentTypeControlNew.ascx.cs | 8 +- src/umbraco.cms/businesslogic/Content.cs | 36 ++++----- src/umbraco.cms/businesslogic/ContentType.cs | 79 ++++++++++++++++++ .../businesslogic/member/Member.cs | 11 +-- .../businesslogic/member/MemberType.cs | 25 ++++++ src/umbraco.cms/businesslogic/web/Document.cs | 4 +- .../businesslogic/web/DocumentType.cs | 81 +++++++++++++++++++ 7 files changed, 213 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 780fab6246..a4fa8f65b3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -276,6 +276,10 @@ namespace umbraco.controls { _contentType = new cms.businesslogic.media.MediaType(docTypeId); } + else if (Request.Path.ToLowerInvariant().Contains("editmembertype.aspx")) + { + _contentType = new cms.businesslogic.member.MemberType(docTypeId); + } else { _contentType = new ContentType(docTypeId); @@ -291,9 +295,11 @@ namespace umbraco.controls /// private void RegenerateXmlCaches() { + _contentType.RebuildXmlStructuresForContent(); + + //special case for DocumentType's if (_contentType is DocumentType) { - umbraco.cms.businesslogic.web.Document.RePublishAll(); library.RefreshContent(); } } diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index d8c30138ae..3e5bfacb3d 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Data; using System.Xml; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using umbraco.cms.businesslogic.property; using umbraco.cms.businesslogic.propertytype; using umbraco.DataLayer; @@ -416,8 +417,7 @@ namespace umbraco.cms.businesslogic /// public virtual void XmlGenerate(XmlDocument xd) { - XmlNode node = generateXmlWithoutSaving(xd); - SaveXmlDocument(node); + SaveXmlDocument(generateXmlWithoutSaving(xd)); } public virtual void XmlPopulate(XmlDocument xd, ref XmlNode x, bool Deep) @@ -428,27 +428,27 @@ namespace umbraco.cms.businesslogic x.AppendChild(p.ToXml(xd)); // attributes - x.Attributes.Append(xmlHelper.addAttribute(xd, "id", this.Id.ToString())); - x.Attributes.Append(xmlHelper.addAttribute(xd, "version", this.Version.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "id", this.Id.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "version", this.Version.ToString())); if (this.Level > 1) - x.Attributes.Append(xmlHelper.addAttribute(xd, "parentID", this.Parent.Id.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "parentID", this.Parent.Id.ToString())); else - x.Attributes.Append(xmlHelper.addAttribute(xd, "parentID", "-1")); - x.Attributes.Append(xmlHelper.addAttribute(xd, "level", this.Level.ToString())); - x.Attributes.Append(xmlHelper.addAttribute(xd, "writerID", this.User.Id.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "parentID", "-1")); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "level", this.Level.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "writerID", this.User.Id.ToString())); if (this.ContentType != null) - x.Attributes.Append(xmlHelper.addAttribute(xd, "nodeType", this.ContentType.Id.ToString())); - x.Attributes.Append(xmlHelper.addAttribute(xd, "template", "0")); - x.Attributes.Append(xmlHelper.addAttribute(xd, "sortOrder", this.sortOrder.ToString())); - x.Attributes.Append(xmlHelper.addAttribute(xd, "createDate", this.CreateDateTime.ToString("s"))); - x.Attributes.Append(xmlHelper.addAttribute(xd, "updateDate", this.VersionDate.ToString("s"))); - x.Attributes.Append(xmlHelper.addAttribute(xd, "nodeName", this.Text)); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "nodeType", this.ContentType.Id.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "template", "0")); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "sortOrder", this.sortOrder.ToString())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "createDate", this.CreateDateTime.ToString("s"))); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "updateDate", this.VersionDate.ToString("s"))); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "nodeName", this.Text)); if (this.Text != null) - x.Attributes.Append(xmlHelper.addAttribute(xd, "urlName", this.Text.Replace(" ", "").ToLower())); - x.Attributes.Append(xmlHelper.addAttribute(xd, "writerName", this.User.Name)); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "urlName", this.Text.Replace(" ", "").ToLower())); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "writerName", this.User.Name)); if (this.ContentType != null) - x.Attributes.Append(xmlHelper.addAttribute(xd, "nodeTypeAlias", this.ContentType.Alias)); - x.Attributes.Append(xmlHelper.addAttribute(xd, "path", this.Path)); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "nodeTypeAlias", this.ContentType.Alias)); + x.Attributes.Append(XmlHelper.AddAttribute(xd, "path", this.Path)); if (Deep) { diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index 9634047464..93e5cede1c 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Runtime.CompilerServices; using System.Linq; +using System.Xml; using Umbraco.Core; using Umbraco.Core.Logging; using umbraco.cms.businesslogic.web; @@ -282,6 +283,84 @@ namespace umbraco.cms.businesslogic #region Public Properties + #region Regenerate Xml Structures + + /// + /// Used to rebuild all of the xml structures for content of the current content type in the cmsContentXml table + /// + [MethodImpl(MethodImplOptions.Synchronized)] + internal void RebuildXmlStructuresForContent() + { + //Clears all xml structures in the cmsContentXml table for the current content type + ClearXmlStructuresForContent(); + foreach (var i in GetContentIdsForContentType()) + { + RebuildXmlStructureForContentItem(i); + } + } + + /// + /// Returns all content ids associated with this content type + /// + /// + /// + /// This will generally just return the content ids associated with this content type but in the case + /// of a DocumentType where we can have inherited types, this will return all content Ids that are of + /// this content type or any descendant types as well. + /// + internal virtual IEnumerable GetContentIdsForContentType() + { + var ids = new List(); + //get all the content item ids of the current content type + using (var dr = SqlHelper.ExecuteReader(@"SELECT DISTINCT cmsContent.nodeId FROM cmsContent + INNER JOIN cmsContentType ON cmsContent.contentType = cmsContentType.nodeId + WHERE cmsContentType.nodeId = @nodeId", + SqlHelper.CreateParameter("@nodeId", this.Id))) + { + while (dr.Read()) + { + ids.Add(dr.GetInt("nodeId")); + } + dr.Close(); + } + return ids; + } + + /// + /// Rebuilds the xml structure for the content item by id + /// + /// + /// + /// This is not thread safe + /// + internal virtual void RebuildXmlStructureForContentItem(int contentId) + { + var xd = new XmlDocument(); + try + { + new Content(contentId).XmlGenerate(xd); + } + catch (Exception ee) + { + LogHelper.Error("Error generating xml", ee); + } + } + + /// + /// Clears all xml structures in the cmsContentXml table for the current content type + /// + internal virtual void ClearXmlStructuresForContent() + { + //Remove all items from the cmsContentXml table that are of this current content type + SqlHelper.ExecuteNonQuery(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContent.nodeId FROM cmsContent + INNER JOIN cmsContentType ON cmsContent.contentType = cmsContentType.nodeId + WHERE cmsContentType.nodeId = @nodeId)", + SqlHelper.CreateParameter("@nodeId", this.Id)); + } + + #endregion + /// /// Get or Sets the Container status of the Content Type. A Container Content Type doesn't show its children in the tree, /// but instead adds a tab when edited showing its children in a grid diff --git a/src/umbraco.cms/businesslogic/member/Member.cs b/src/umbraco.cms/businesslogic/member/Member.cs index e94ee3e642..958994e794 100644 --- a/src/umbraco.cms/businesslogic/member/Member.cs +++ b/src/umbraco.cms/businesslogic/member/Member.cs @@ -585,16 +585,7 @@ namespace umbraco.cms.businesslogic.member FireAfterSave(e); } } - - /// - /// Generates the xmlrepresentation of a member - /// - /// - public override void XmlGenerate(XmlDocument xd) - { - SaveXmlDocument(generateXmlWithoutSaving(xd)); - } - + /// /// Xmlrepresentation of a member /// diff --git a/src/umbraco.cms/businesslogic/member/MemberType.cs b/src/umbraco.cms/businesslogic/member/MemberType.cs index 30b04b5b23..3e204f512a 100644 --- a/src/umbraco.cms/businesslogic/member/MemberType.cs +++ b/src/umbraco.cms/businesslogic/member/MemberType.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Xml; +using Umbraco.Core.Logging; using umbraco.cms.businesslogic.propertytype; using System.Linq; using umbraco.BusinessLogic; @@ -27,6 +28,30 @@ namespace umbraco.cms.businesslogic.member /// MemberType id public MemberType(Guid id) : base(id) { } + #region Regenerate Xml Structures + + /// + /// Rebuilds the xml structure for the member item by id + /// + /// + /// + /// This is not thread safe + /// + internal override void RebuildXmlStructureForContentItem(int contentId) + { + var xd = new XmlDocument(); + try + { + new Member(contentId).XmlGenerate(xd); + } + catch (Exception ee) + { + LogHelper.Error("Error generating xml", ee); + } + } + + #endregion + /// /// Used to persist object changes to the database. In Version3.0 it's just a stub for future compatibility /// diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 925cdf2bdf..f2d4c47b0f 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Web; using System.Xml; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using umbraco.BusinessLogic; using umbraco.BusinessLogic.Actions; using umbraco.cms.businesslogic.property; @@ -655,8 +656,7 @@ order by {1} } catch (Exception ee) { - Log.Add(LogTypes.Error, User.GetUser(0), dr.GetInt("nodeId"), - string.Format("Error generating xml: {0}", ee)); + LogHelper.Error("Error generating xml", ee); } } dr.Close(); diff --git a/src/umbraco.cms/businesslogic/web/DocumentType.cs b/src/umbraco.cms/businesslogic/web/DocumentType.cs index f254e1af74..a8af88fd6a 100644 --- a/src/umbraco.cms/businesslogic/web/DocumentType.cs +++ b/src/umbraco.cms/businesslogic/web/DocumentType.cs @@ -4,6 +4,7 @@ using System.Data; using System.Text; using System.Xml; using System.Linq; +using Umbraco.Core.Logging; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.propertytype; using umbraco.DataLayer; @@ -294,6 +295,86 @@ namespace umbraco.cms.businesslogic.web #region Public Methods + #region Regenerate Xml Structures + + /// + /// This will return all PUBLISHED content Ids that are of this content type or any descendant types as well. + /// + /// + internal override IEnumerable GetContentIdsForContentType() + { + var ids = new List(); + int? currentContentTypeId = this.Id; + while (currentContentTypeId != null) + { + //get all the content item ids of the current content type + using (var dr = SqlHelper.ExecuteReader(@"SELECT DISTINCT cmsDocument.nodeId FROM cmsDocument + INNER JOIN cmsContent ON cmsContent.nodeId = cmsDocument.nodeId + INNER JOIN cmsContentType ON cmsContent.contentType = cmsContentType.nodeId + WHERE cmsContentType.nodeId = @contentTypeId AND cmsDocument.published = 1", + SqlHelper.CreateParameter("@contentTypeId", currentContentTypeId))) + { + while (dr.Read()) + { + ids.Add(dr.GetInt("nodeId")); + } + dr.Close(); + } + + //lookup the child content type if there is one + currentContentTypeId = SqlHelper.ExecuteScalar("SELECT nodeId FROM cmsContentType WHERE masterContentType=@contentTypeId", + SqlHelper.CreateParameter("@contentTypeId", currentContentTypeId)); + } + return ids; + } + + /// + /// Rebuilds the xml structure for the content item by id + /// + /// + /// + /// This is not thread safe + /// + internal override void RebuildXmlStructureForContentItem(int contentId) + { + var xd = new XmlDocument(); + try + { + new Document(contentId).XmlGenerate(xd); + } + catch (Exception ee) + { + LogHelper.Error("Error generating xml", ee); + } + } + + /// + /// Clears all xml structures in the cmsContentXml table for the current content type and any of it's descendant types + /// + /// + /// This is not thread safe + /// + internal override void ClearXmlStructuresForContent() + { + int? currentContentTypeId = this.Id; + while (currentContentTypeId != null) + { + //Remove all items from the cmsContentXml table that are of this current content type + SqlHelper.ExecuteNonQuery(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContent.nodeId FROM cmsContent + INNER JOIN cmsContentType ON cmsContent.contentType = cmsContentType.nodeId + WHERE cmsContentType.nodeId = @contentTypeId)", + SqlHelper.CreateParameter("@contentTypeId", currentContentTypeId)); + + //lookup the child content type if there is one + currentContentTypeId = SqlHelper.ExecuteScalar("SELECT nodeId FROM cmsContentType WHERE masterContentType=@contentTypeId", + SqlHelper.CreateParameter("@contentTypeId", currentContentTypeId)); + } + + } + + #endregion + public void RemoveTemplate(int templateId) { // remove if default template From b4caf109f71364acb60bb8c267c657c3b91600da Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 24 Apr 2013 15:17:20 -1000 Subject: [PATCH 03/10] Working on #U4-2144 - ensures that xml is re-gen'd when a property alias is changed. --- .../controls/ContentTypeControlNew.ascx.cs | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index a4fa8f65b3..d8412a5eb7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -124,23 +124,47 @@ namespace umbraco.controls /// private class SaveAsyncState { - public SaveAsyncState(SaveClickEventArgs saveArgs, string originalAlias, string originalName) + public SaveAsyncState( + SaveClickEventArgs saveArgs, + string originalAlias, + string originalName, + string[] originalPropertyAliases) { SaveArgs = saveArgs; - OriginalAlias = originalAlias; - OriginalName = originalName; + _originalAlias = originalAlias; + _originalName = originalName; + _originalPropertyAliases = originalPropertyAliases; } public SaveClickEventArgs SaveArgs { get; private set; } - public string OriginalAlias { get; private set; } - public string OriginalName { get; private set; } + private readonly string _originalAlias; + private readonly string _originalName; + private readonly string[] _originalPropertyAliases; + public bool HasAliasChanged(ContentType contentType) { - return (string.Compare(OriginalAlias, contentType.Alias, StringComparison.OrdinalIgnoreCase) != 0); + return (string.Compare(_originalAlias, contentType.Alias, StringComparison.OrdinalIgnoreCase) != 0); } public bool HasNameChanged(ContentType contentType) { - return (string.Compare(OriginalName, contentType.Text, StringComparison.OrdinalIgnoreCase) != 0); + return (string.Compare(_originalName, contentType.Text, StringComparison.OrdinalIgnoreCase) != 0); + } + + /// + /// Returns true if any property has been removed or if any alias has changed + /// + /// + /// + public bool HasAnyPropertyAliasChanged(ContentType contentType) + { + var newAliases = contentType.PropertyTypes.Select(x => x.Alias).ToArray(); + //if any have been removed, return true + if (newAliases.Length < _originalPropertyAliases.Count()) + { + return true; + } + //otherwise ensure that all of the original aliases are still existing + return newAliases.ContainsAll(_originalPropertyAliases) == false; } } @@ -221,7 +245,7 @@ namespace umbraco.controls var state = new SaveAsyncState(new SaveClickEventArgs("Saved") { IconType = BasePage.speechBubbleIcon.success - }, _contentType.Alias, _contentType.Text); + }, _contentType.Alias, _contentType.Text, _contentType.PropertyTypes.Select(x => x.Alias).ToArray()); //Add the async operation to the page Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncSaveOperation, EndAsyncSaveOperation, HandleAsyncSaveTimeout, state)); @@ -245,7 +269,7 @@ namespace umbraco.controls // Only if the doctype alias changed, cause a regeneration of the xml cache file since // the xml element names will need to be updated to reflect the new alias - if (asyncState.HasAliasChanged(_contentType)) + if (asyncState.HasAliasChanged(_contentType) || asyncState.HasAnyPropertyAliasChanged(_contentType)) RegenerateXmlCaches(); Trace.Write("ContentTypeControlNew", "task completing"); From 7ee9b9ebcc8837663cbc2d766568460b0a64d205 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 24 Apr 2013 15:31:36 -1000 Subject: [PATCH 04/10] Fixes: #U4-2144 - ensures that the xml is re-gen'd when a content type alias is changed or a content type property is removed. Also had to change the property type deletion to be async as well since it might be long running to re-gen the xml. --- .../controls/ContentTypeControlNew.ascx.cs | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index d8412a5eb7..720d31cea1 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -62,6 +62,8 @@ namespace umbraco.controls //the async saving task private Action _asyncSaveTask; + //the async delete property task + private Action _asyncDeleteTask; override protected void OnInit(EventArgs e) { @@ -737,12 +739,69 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); PropertyTypes.Controls.Add(new LiteralControl("
No properties defined on this tab. Click on the \"add a new property\" link at the top to create a new property.
")); } - protected void gpw_Delete(object sender, System.EventArgs e) + /// + /// Called asynchronously in order to delete a content type property + /// + /// + /// + /// + /// + /// + private IAsyncResult BeginAsyncDeleteOperation(object sender, EventArgs e, AsyncCallback cb, object state) { - GenericProperties.GenericPropertyWrapper gpw = (GenericProperties.GenericPropertyWrapper)sender; - gpw.GenricPropertyControl.PropertyType.delete(); - _contentType = ContentType.GetContentType(_contentType.Id); + Trace.Write("ContentTypeControlNew", "Start async operation"); + + //get the args from the async state + var args = (GenericPropertyWrapper)state; + + //start the task + var result = _asyncDeleteTask.BeginInvoke(args, cb, args); + return result; + } + + /// + /// Occurs once the async database save operation has completed + /// + /// + /// + /// This updates the UI elements + /// + private void EndAsyncDeleteOperation(IAsyncResult ar) + { + Trace.Write("ContentTypeControlNew", "ending async operation"); + + // reload content type (due to caching) + LoadContentType(); this.BindDataGenericProperties(true); + + Trace.Write("ContentTypeControlNew", "async operation ended"); + + //complete it + _asyncDeleteTask.EndInvoke(ar); + } + + protected void gpw_Delete(object sender, EventArgs e) + { + //Add the async operation to the page + Page.RegisterAsyncTask(new PageAsyncTask(BeginAsyncDeleteOperation, EndAsyncDeleteOperation, HandleAsyncSaveTimeout, (GenericPropertyWrapper)sender)); + + //create the save task to be executed async + _asyncDeleteTask = genericPropertyWrapper => + { + Trace.Write("ContentTypeControlNew", "executing task"); + + //delete the property + genericPropertyWrapper.GenricPropertyControl.PropertyType.delete(); + + //we need to re-generate the xml structures because we're removing a content type property + RegenerateXmlCaches(); + + Trace.Write("ContentTypeControlNew", "task completing"); + }; + + //execute the async tasks + Page.ExecuteRegisteredAsyncTasks(); + } private void SaveProperties(SaveClickEventArgs e) From d30c4791feb0ead3525680030c5dc0468345c03c Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 24 Apr 2013 15:46:38 -1000 Subject: [PATCH 05/10] fixed a documentation error --- .../umbraco/controls/ContentTypeControlNew.ascx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 720d31cea1..521cb8114d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -760,7 +760,7 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); } /// - /// Occurs once the async database save operation has completed + /// Occurs once the async database delete operation has completed /// /// /// From f3835d1ca3696ee109438aeaf5d3e0f027072c80 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 25 Apr 2013 11:14:09 -0200 Subject: [PATCH 06/10] Closing branch 4.11.7 From a3347c0037f6bb733cf0d0532b657c79e3b11942 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 25 Apr 2013 11:14:46 -0200 Subject: [PATCH 07/10] Opening new branch From 461f032777e2a99f907d64ccbf9c96e337a588d3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 25 Apr 2013 11:17:07 -0200 Subject: [PATCH 08/10] Added tag release-4.11.7 for changeset ce47176bc6b9 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 7a418a8da9..57d0abb601 100644 --- a/.hgtags +++ b/.hgtags @@ -28,3 +28,4 @@ e4d0c7e740555d8707ba71f55a623a3f96da8e65 Release-4.11.3.1 0000000000000000000000000000000000000000 Release-4.11.3.1 e02f5aebb87e3f07d27376647756caa9762931d8 Release-4.11.4 14338b0ab1738fbd8987677ecdb9a73d79fc229d release-4.11.6 +ce47176bc6b9298741783e20f1fe2672c811f744 release-4.11.7 From 46858af1ed6dbd1d3986f690aa2982d4bdfa8778 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 25 Apr 2013 13:25:25 -1000 Subject: [PATCH 09/10] Ensures user cookie is encrypted, removes ASP.Net headers from being returned in our response. Ensures that our auth cookie is httponly. --- src/Umbraco.Web.UI/web.Template.config | 10 ++++- src/Umbraco.Web/UmbracoApplication.cs | 4 ++ src/Umbraco.Web/UmbracoModule.cs | 9 ++++ .../BasePages/BasePage.cs | 42 ++++++------------- src/umbraco.businesslogic/StateHelper.cs | 3 ++ 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 27c815253a..2dea7c980d 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -81,7 +81,7 @@ - + @@ -220,6 +220,14 @@ + + + + + + + + diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index c3d32c2ce2..c7e5efc9c6 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Web; using System.Web.Hosting; +using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Web.Routing; @@ -34,6 +35,9 @@ namespace Umbraco.Web /// protected void Application_Start(object sender, EventArgs e) { + //don't output the MVC version header (security) + MvcHandler.DisableMvcResponseHeader = true; + //boot up the application _bootManager .Initialize() diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index f839c74a17..cc5669c64f 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -424,6 +424,15 @@ namespace Umbraco.Web LogHelper.Debug("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); } }; + + //disable asp.net headers (security) + app.PreSendRequestHeaders += (sender, args) => + { + var httpContext = ((HttpApplication)sender).Context; + httpContext.Response.Headers.Remove("Server"); + //this doesn't normally work since IIS sets it but we'll keep it here anyways. + httpContext.Response.Headers.Remove("X-Powered-By"); + }; } public void Dispose() diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index 5ff368e264..87c2ae4050 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -237,29 +237,21 @@ namespace umbraco.BasePages { get { - // zb-00004 #29956 : refactor cookies names & handling if (StateHelper.Cookies.HasCookies && StateHelper.Cookies.UserContext.HasValue) - return StateHelper.Cookies.UserContext.GetValue(); - else { try { - string encTicket = StateHelper.Cookies.UserContext.GetValue(); - if (!String.IsNullOrEmpty(encTicket)) - return FormsAuthentication.Decrypt(encTicket).UserData; + var encTicket = StateHelper.Cookies.UserContext.GetValue(); + if (string.IsNullOrEmpty(encTicket) == false) + { + return encTicket.DecryptWithMachineKey(); + } } catch (HttpException ex) { // we swallow this type of exception as it happens if a legacy (pre 4.8.1) cookie is set } - catch (ArgumentException ex) - { - // we swallow this one because it's 99.99% certaincy is legacy based. We'll still log it, though - LogHelper.Error("An error occurred reading auth cookie value", ex); - - } } - return ""; } set @@ -271,25 +263,15 @@ namespace umbraco.BasePages if (StateHelper.Cookies.UserContext.HasValue) StateHelper.Cookies.ClearAll(); - if (!String.IsNullOrEmpty(value)) - { - FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, - value, - DateTime.Now, - DateTime.Now.AddDays(1), - false, - value, - FormsAuthentication.FormsCookiePath); - - // Encrypt the ticket. - string encTicket = FormsAuthentication.Encrypt(ticket); - + if (string.IsNullOrEmpty(value) == false) + { + // Encrypt the value + var encTicket = value.EncryptWithMachineKey(); // Create new cookie. - StateHelper.Cookies.UserContext.SetValue(value, 1); - - - } else + StateHelper.Cookies.UserContext.SetValue(encTicket, 1); + } + else { StateHelper.Cookies.UserContext.Clear(); } diff --git a/src/umbraco.businesslogic/StateHelper.cs b/src/umbraco.businesslogic/StateHelper.cs index da79724f2b..387efd2ca3 100644 --- a/src/umbraco.businesslogic/StateHelper.cs +++ b/src/umbraco.businesslogic/StateHelper.cs @@ -454,6 +454,9 @@ namespace umbraco.BusinessLogic if (GlobalSettings.UseSSL) cookie.Secure = true; + //ensure http only, this should only be able to be accessed via the server + cookie.HttpOnly = true; + cookie.Expires = expires; ResponseCookie = cookie; From 385d6396b09937d93255fbe1af46d6b447413850 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 25 Apr 2013 13:28:47 -1000 Subject: [PATCH 10/10] disables auto-complete on the login page. --- src/Umbraco.Web.UI/umbraco/login.aspx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/login.aspx b/src/Umbraco.Web.UI/umbraco/login.aspx index 47f65fb716..93ca7a7034 100644 --- a/src/Umbraco.Web.UI/umbraco/login.aspx +++ b/src/Umbraco.Web.UI/umbraco/login.aspx @@ -21,7 +21,7 @@ } body { - font-size: 11px; + font-size: 11px; width: 100%; font-family: Trebuchet MS, verdana, arial, Lucida Grande; text-align: center; @@ -70,7 +70,7 @@ - @@ -85,7 +85,7 @@ -