From e34b0cc6fe217e4cbf0867de12346d697bca85b6 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Fri, 14 Dec 2012 09:11:53 +0500 Subject: [PATCH 1/3] Fixes: #U4-1272 which now enables the hidden feature of razor macros (this feature also works in MVC) --- .../RazorDynamicNode/DynamicNode.cs | 76 ++----------------- 1 file changed, 6 insertions(+), 70 deletions(-) diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs index 3c92cfa223..4fc8b76db6 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs @@ -406,44 +406,6 @@ namespace umbraco.MacroEngines } return result; } - private List GetAncestorOrSelfNodeTypeAlias(DynamicBackingItem node) - { - List list = new List(); - if (node != null) - { - if (node.Type == DynamicBackingItemType.Content) - { - //find the doctype node, so we can walk it's parent's tree- not the working.parent content tree - CMSNode working = ContentType.GetByAlias(node.NodeTypeAlias); - while (working != null) - { - //NOTE: I'm not sure if anyone has ever tested this but if you get working.Parent it will return a CMSNode and - // it will never be castable to a 'ContentType' object - // pretty sure the only reason why this method works for the one place that it is used is that it returns - // the current node's alias which is all that is actually requried, this is just added overhead for no - // reason - - if ((working as ContentType) != null) - { - list.Add((working as ContentType).Alias); - } - try - { - working = working.Parent; - } - catch (ArgumentException) - { - break; - } - } - } - else - { - return null; - } - } - return list; - } private static Dictionary, Type> _razorDataTypeModelTypes = null; private static readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); @@ -485,14 +447,6 @@ namespace umbraco.MacroEngines } }); - //NOTE: We really dont need to log this? - //var i = 1; - //foreach (var item in foundTypes) - //{ - // HttpContext.Current.Trace.Write(string.Format("{0}/{1}: {2}@{4} => {3}", i, foundTypes.Count, item.Key.Item1, item.Value.FullName, item.Key.Item2)); - // i++; - //} - //there is no error, so set the collection _razorDataTypeModelTypes = foundTypes; @@ -605,21 +559,8 @@ namespace umbraco.MacroEngines LogHelper.Warn(string.Format("Could not get the dataTypeType for the RazorDataTypeModel")); } } - else - { - //NOTE: Do we really want to log this? I'm not sure. - //if (RazorDataTypeModelTypes == null) - //{ - // HttpContext.Current.Trace.Write(string.Format("RazorDataTypeModelTypes is null, probably an exception while building the cache, falling back to ConvertPropertyValueByDataType", dataType)); - //} - //else - //{ - // HttpContext.Current.Trace.Write(string.Format("GUID {0} does not have a DataTypeModel, falling back to ConvertPropertyValueByDataType", dataType)); - //} - - } - - //convert the string value to a known type + + //convert the string value to a known type return ConvertPropertyValueByDataType(ref result, name, dataType); } @@ -629,15 +570,10 @@ namespace umbraco.MacroEngines var typeChildren = n.ChildrenAsList; if (typeChildren != null) { - var filteredTypeChildren = typeChildren.Where(x => - { - List ancestorAliases = GetAncestorOrSelfNodeTypeAlias(x); - if (ancestorAliases == null) - { - return false; - } - return ancestorAliases.Any(alias => alias == name || MakePluralName(alias) == name); - }); + + var filteredTypeChildren = typeChildren + .Where(x => x.NodeTypeAlias.InvariantEquals(name) || x.NodeTypeAlias.MakePluralName().InvariantEquals(binder.Name)) + .ToArray(); if (filteredTypeChildren.Any()) { result = new DynamicNodeList(filteredTypeChildren); From 5fba1a775e1f187bc28786d3c3af3b94d36d9506 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Fri, 14 Dec 2012 09:22:28 +0500 Subject: [PATCH 2/3] Fixes: #U4-1300 --- src/Umbraco.Web/UmbracoHelper.cs | 40 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index db5323eede..ebd3e74a7d 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -470,17 +470,17 @@ namespace Umbraco.Web public dynamic Content(object id) { - return DocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore); + return DocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore, new DynamicNull()); } public dynamic Content(int id) { - return DocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore); + return DocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore, new DynamicNull()); } public dynamic Content(string id) { - return DocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore); + return DocumentById(id, PublishedContentStoreResolver.Current.PublishedContentStore, new DynamicNull()); } public dynamic Content(params object[] ids) @@ -574,17 +574,17 @@ namespace Umbraco.Web public dynamic Media(object id) { - return DocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore); + return DocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore, new DynamicNull()); } public dynamic Media(int id) { - return DocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore); + return DocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore, new DynamicNull()); } public dynamic Media(string id) { - return DocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore); + return DocumentById(id, PublishedMediaStoreResolver.Current.PublishedMediaStore, new DynamicNull()); } public dynamic Media(params object[] ids) @@ -651,7 +651,7 @@ namespace Umbraco.Web { int docId; return int.TryParse(id, out docId) - ? DocumentById(docId, store) + ? DocumentById(docId, store, null) : null; } @@ -686,35 +686,36 @@ namespace Umbraco.Web /// /// /// + /// /// /// /// We accept an object type because GetPropertyValue now returns an 'object', we still want to allow people to pass /// this result in to this method. /// This method will throw an exception if the value is not of type int or string. /// - private dynamic DocumentById(object id, IPublishedStore store) + private dynamic DocumentById(object id, IPublishedStore store, object ifNotFound) { if (id is string) - return DocumentById((string)id, store); + return DocumentById((string)id, store, ifNotFound); if (id is int) - return DocumentById((int)id, store); + return DocumentById((int)id, store, ifNotFound); throw new InvalidOperationException("The value of parameter 'id' must be either a string or an integer"); } - private dynamic DocumentById(int id, IPublishedStore store) + private dynamic DocumentById(int id, IPublishedStore store, object ifNotFound) { var doc = store.GetDocumentById(UmbracoContext.Current, id); return doc == null - ? new DynamicNull() + ? ifNotFound : new DynamicPublishedContent(doc).AsDynamic(); } - private dynamic DocumentById(string id, IPublishedStore store) + private dynamic DocumentById(string id, IPublishedStore store, object ifNotFound) { int docId; return int.TryParse(id, out docId) - ? DocumentById(docId, store) - : new DynamicNull(); + ? DocumentById(docId, store, ifNotFound) + : ifNotFound; } /// @@ -730,7 +731,8 @@ namespace Umbraco.Web /// private dynamic DocumentByIds(IPublishedStore store, params object[] ids) { - var nodes = ids.Select(eachId => DocumentById(eachId, store)) + var dNull = new DynamicNull(); + var nodes = ids.Select(eachId => DocumentById(eachId, store, dNull)) .Where(x => !TypeHelper.IsTypeAssignableFrom(x)) .Cast(); return new DynamicPublishedContentList(nodes); @@ -738,7 +740,8 @@ namespace Umbraco.Web private dynamic DocumentByIds(IPublishedStore store, params int[] ids) { - var nodes = ids.Select(eachId => DocumentById(eachId, store)) + var dNull = new DynamicNull(); + var nodes = ids.Select(eachId => DocumentById(eachId, store, dNull)) .Where(x => !TypeHelper.IsTypeAssignableFrom(x)) .Cast(); return new DynamicPublishedContentList(nodes); @@ -746,7 +749,8 @@ namespace Umbraco.Web private dynamic DocumentByIds(IPublishedStore store, params string[] ids) { - var nodes = ids.Select(eachId => DocumentById(eachId, store)) + var dNull = new DynamicNull(); + var nodes = ids.Select(eachId => DocumentById(eachId, store, dNull)) .Where(x => !TypeHelper.IsTypeAssignableFrom(x)) .Cast(); return new DynamicPublishedContentList(nodes); From 547dab07619873bbee3f8783d0e698a9d517b619 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Fri, 14 Dec 2012 10:40:11 +0500 Subject: [PATCH 3/3] Fixes: #U4-1274, U4-1304 --- .../PublishedContentExtensions.cs | 30 +++++ .../PublishedContent/PublishedContentTests.cs | 13 ++ src/Umbraco.Web/UmbracoHelper.cs | 3 +- src/Umbraco.Web/umbraco.presentation/item.cs | 111 ++++++++++++------ src/Umbraco.Web/umbraco.presentation/page.cs | 30 ++++- .../umbraco/templateControls/ItemRenderer.cs | 11 +- 6 files changed, 160 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index 9d54198982..c039cf881c 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -75,6 +75,36 @@ namespace Umbraco.Core } #endregion + /// + /// Returns the recursive value of a field by iterating up the parent chain but starting at the publishedContent passed in + /// + /// + /// + /// + public static string GetRecursiveValue(this IPublishedContent publishedContent, string fieldname) + { + var contentValue = ""; + var currentContent = publishedContent; + + while (contentValue.IsNullOrWhiteSpace()) + { + var val = currentContent[fieldname]; + if (val == null || val.ToString().IsNullOrWhiteSpace()) + { + if (currentContent.Parent == null) + { + break; //we've reached the top + } + currentContent = currentContent.Parent; + } + else + { + contentValue = val.ToString(); //we've found a recursive val + } + } + return contentValue; + } + public static bool IsVisible(this IPublishedContent doc) { var umbracoNaviHide = doc.GetProperty("umbracoNaviHide"); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index bf3a410e51..d2570dacfd 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -35,13 +35,16 @@ namespace Umbraco.Tests.PublishedContent 1 + This is some content]]> + + @@ -105,6 +108,16 @@ namespace Umbraco.Tests.PublishedContent return doc; } + [Test] + public void Test_Get_Recursive_Val() + { + var doc = GetNode(1174); + var rVal = doc.GetRecursiveValue("testRecursive"); + var nullVal = doc.GetRecursiveValue("DoNotFindThis"); + Assert.AreEqual("This is the recursive val", rVal); + Assert.AreEqual("", nullVal); + } + [Test] public void Get_Property_Value_Uses_Converter() { diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index ebd3e74a7d..7a9ba39cf3 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -300,8 +300,7 @@ namespace Umbraco.Web var item = new Item() - { - //NodeId = currentPage.Id.ToString(); + { Field = fieldAlias, TextIfEmpty = altText, LegacyAttributes = attributesForItem diff --git a/src/Umbraco.Web/umbraco.presentation/item.cs b/src/Umbraco.Web/umbraco.presentation/item.cs index 30c5a2c217..fe6c8faea7 100644 --- a/src/Umbraco.Web/umbraco.presentation/item.cs +++ b/src/Umbraco.Web/umbraco.presentation/item.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Web; using System.Xml; using Umbraco.Core; +using Umbraco.Core.Models; namespace umbraco { @@ -12,7 +13,7 @@ namespace umbraco public class item { private String _fieldContent = ""; - private String _fieldName; + private readonly String _fieldName; public String FieldContent { @@ -22,64 +23,108 @@ namespace umbraco public item(string itemValue, IDictionary attributes) { _fieldContent = itemValue; - parseItem(attributes); + ParseItem(attributes); } /// - /// + /// Creates a new Legacy item /// - /// + /// /// - /// public item(IDictionary elements, IDictionary attributes) + : this(null, elements, attributes) + { + } + + /// + /// Creates an Item with a publishedContent item in order to properly recurse and return the value. + /// + /// + /// + /// + /// + /// THIS ENTIRE CLASS WILL BECOME LEGACY, THE FIELD RENDERING NEEDS TO BE REPLACES SO THAT IS WHY THIS + /// CTOR IS INTERNAL. + /// + internal item(IPublishedContent publishedContent, IDictionary elements, IDictionary attributes) { _fieldName = helper.FindAttribute(attributes, "field"); - if(_fieldName.StartsWith("#")) + if (_fieldName.StartsWith("#")) { _fieldContent = library.GetDictionaryItem(_fieldName.Substring(1, _fieldName.Length - 1)); } else { // Loop through XML children we need to find the fields recursive - if(helper.FindAttribute(attributes, "recursive") == "true") + if (helper.FindAttribute(attributes, "recursive") == "true") { - - XmlDocument umbracoXML = presentation.UmbracoContext.Current.GetXml(); - - String[] splitpath = (String[]) elements["splitpath"]; - for(int i = 0; i < splitpath.Length - 1; i++) + if (publishedContent == null) { - XmlNode element = umbracoXML.GetElementById(splitpath[splitpath.Length - i - 1].ToString()); - if (element == null) - continue; - string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./data [@alias = '{0}']" : "./{0}"; - XmlNode currentNode = element.SelectSingleNode(string.Format(xpath, - _fieldName)); - if(currentNode != null && currentNode.FirstChild != null && - !string.IsNullOrEmpty(currentNode.FirstChild.Value) && - !string.IsNullOrEmpty(currentNode.FirstChild.Value.Trim())) - { - HttpContext.Current.Trace.Write("item.recursive", "Item loaded from " + splitpath[splitpath.Length - i - 1]); - _fieldContent = currentNode.FirstChild.Value; - break; - } + var recursiveVal = GetRecursiveValueLegacy(elements); + _fieldContent = recursiveVal.IsNullOrWhiteSpace() ? _fieldContent : recursiveVal; + } + else + { + var recursiveVal = publishedContent.GetRecursiveValue(_fieldName); + _fieldContent = recursiveVal.IsNullOrWhiteSpace() ? _fieldContent : recursiveVal; } } else { - if (elements[_fieldName] != null && !string.IsNullOrEmpty(elements[_fieldName].ToString())) - _fieldContent = elements[_fieldName].ToString().Trim(); - else if(!string.IsNullOrEmpty(helper.FindAttribute(attributes, "useIfEmpty"))) - if (elements[helper.FindAttribute(attributes, "useIfEmpty")] != null && !string.IsNullOrEmpty(elements[helper.FindAttribute(attributes, "useIfEmpty")].ToString())) - _fieldContent = elements[helper.FindAttribute(attributes, "useIfEmpty")].ToString().Trim(); + if (elements[_fieldName] != null && !string.IsNullOrEmpty(elements[_fieldName].ToString())) + { + _fieldContent = elements[_fieldName].ToString().Trim(); + } + else if (!string.IsNullOrEmpty(helper.FindAttribute(attributes, "useIfEmpty"))) + { + if (elements[helper.FindAttribute(attributes, "useIfEmpty")] != null && !string.IsNullOrEmpty(elements[helper.FindAttribute(attributes, "useIfEmpty")].ToString())) + { + _fieldContent = elements[helper.FindAttribute(attributes, "useIfEmpty")].ToString().Trim(); + } + } + } } - parseItem(attributes); + ParseItem(attributes); + } + + /// + /// Returns the recursive value using a legacy strategy of looking at the xml cache and the splitPath in the elements collection + /// + /// + /// + private string GetRecursiveValueLegacy(IDictionary elements) + { + var content = ""; + + var umbracoXml = presentation.UmbracoContext.Current.GetXml(); + + var splitpath = (String[])elements["splitpath"]; + for (int i = 0; i < splitpath.Length - 1; i++) + { + XmlNode element = umbracoXml.GetElementById(splitpath[splitpath.Length - i - 1]); + + if (element == null) + continue; + + var xpath = UmbracoSettings.UseLegacyXmlSchema ? "./data [@alias = '{0}']" : "./{0}"; + var currentNode = element.SelectSingleNode(string.Format(xpath, _fieldName)); + + //continue if all is null + if (currentNode == null || currentNode.FirstChild == null || string.IsNullOrEmpty(currentNode.FirstChild.Value) || string.IsNullOrEmpty(currentNode.FirstChild.Value.Trim())) + continue; + + HttpContext.Current.Trace.Write("item.recursive", "Item loaded from " + splitpath[splitpath.Length - i - 1]); + content = currentNode.FirstChild.Value; + break; + } + + return content; } - private void parseItem(IDictionary attributes) + private void ParseItem(IDictionary attributes) { HttpContext.Current.Trace.Write("item", "Start parsing '" + _fieldName + "'"); if(helper.FindAttribute(attributes, "textIfEmpty") != "" && _fieldContent == "") diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index efba296b44..e98efd8e5e 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -87,9 +87,14 @@ namespace umbraco } /// - /// Initializes a new instance of the class for a published document. + /// Initializes a new instance of the class for a published document request. /// /// The pointing to the document. + /// + /// The difference between creating the page with PublishedContentRequest vs an IPublishedContent item is + /// that the PublishedContentRequest takes into account how a template is assigned during the routing process whereas + /// with an IPublishedContent item, the template id is asssigned purely based on the default. + /// internal page(PublishedContentRequest docreq) { @@ -112,6 +117,29 @@ namespace umbraco } + /// + /// Initializes a new instance of the page for a published document + /// + /// + internal page(IPublishedContent doc) + { + if (doc == null) throw new ArgumentNullException("doc"); + + populatePageData(doc.Id, + doc.Name, doc.DocumentTypeId, doc.DocumentTypeAlias, + doc.WriterName, doc.CreatorName, doc.CreateDate, doc.UpdateDate, + doc.Path, doc.Version, doc.Parent == null ? -1 : doc.Parent.Id); + + if (doc.TemplateId > 0) + { + //set the template to whatever is assigned to the doc + _template = doc.TemplateId; + _elements["template"] = _template.ToString(); + } + + PopulateElementData(doc); + } + /// /// Initializes a new instance of the class for a published document. /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/ItemRenderer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/ItemRenderer.cs index de2149b1c4..a1d423bfa3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/ItemRenderer.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/ItemRenderer.cs @@ -8,6 +8,8 @@ using System.Web; using System.Web.UI; using System.Xml; using Umbraco.Core.Macros; +using Umbraco.Web; +using Umbraco.Web.Routing; using Umbraco.Web.Templates; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.property; @@ -99,8 +101,13 @@ namespace umbraco.presentation.templateControls //moved the following from the catch block up as this will allow fallback options alt text etc to work - page itemPage = new page(content.Instance.XmlContent.GetElementById(tempNodeId.ToString())); - tempElementContent = new item(itemPage.Elements, item.LegacyAttributes).FieldContent; + //get the publishedcontent item + var publishedContent = PublishedContentStoreResolver.Current.PublishedContentStore.GetDocumentById( + Umbraco.Web.UmbracoContext.Current, + tempNodeId.Value); + + var itemPage = new page(publishedContent); + tempElementContent = new item(publishedContent, itemPage.Elements, item.LegacyAttributes).FieldContent; /*removed as would fail as there is a incorrect cast in the method called. Also the following code does not respect any of Umbraco Items fallback and formatting options */