diff --git a/src/Umbraco.Web/Media/ImageUrl.cs b/src/Umbraco.Web/Media/ImageUrl.cs index f3c151f7aa..8f56d8bfa4 100644 --- a/src/Umbraco.Web/Media/ImageUrl.cs +++ b/src/Umbraco.Web/Media/ImageUrl.cs @@ -47,9 +47,9 @@ namespace Umbraco.Web.Media } else { - var itemPage = new page(content.Instance.XmlContent.GetElementById(nodeId.GetValueOrDefault().ToString(CultureInfo.InvariantCulture))); - var value = itemPage.Elements[field]; - fieldValue = value != null ? value.ToString() : string.Empty; + var p = UmbracoContext.Current.ContentCache.GetById(nodeId.GetValueOrDefault()); + var v = p.GetPropertyValue(field); + fieldValue = v == null ? string.Empty : v.ToString(); } } else diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 6fc59eb0ed..6fa92d6840 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -95,13 +95,20 @@ namespace Umbraco.Web.Routing // can't go beyond that point without a PublishedContent to render // it's ok not to have a template, in order to give MVC a chance to hijack routes - // assign the legacy page back to the docrequest - // handlers like default.aspx will want it and most macros currently need it - _pcr.UmbracoPage = new page(_pcr); + // note - the page() ctor below will cause the "page" to get the value of all its + // "elements" ie of all the IPublishedContent property. If we use the object value, + // that will trigger macro execution - which can't happen because macro execution + // requires that _pcr.UmbracoPage is already initialized = catch-22. The "legacy" + // pipeline did _not_ evaluate the macros, ie it is using the data value, and we + // have to keep doing it because of that catch-22. - // these two are used by many legacy objects - _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContent.Id; - _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements; + // assign the legacy page back to the docrequest + // handlers like default.aspx will want it and most macros currently need it + _pcr.UmbracoPage = new page(_pcr); + + // used by many legacy objects + _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContent.Id; + _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements; } /// @@ -138,6 +145,8 @@ namespace Umbraco.Web.Routing return; } + // see note in PrepareRequest() + // assign the legacy page back to the docrequest // handlers like default.aspx will want it and most macros currently need it _pcr.UmbracoPage = new page(_pcr); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a994dbabe2..c508c3fc92 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -383,6 +383,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/CompatibilityHelper.cs b/src/Umbraco.Web/umbraco.presentation/CompatibilityHelper.cs new file mode 100644 index 0000000000..90674a0d4b --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/CompatibilityHelper.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Web; +using Umbraco.Core.Models; +using umbraco.interfaces; + +namespace Umbraco.Web.umbraco.presentation +{ + static class CompatibilityHelper + { + // NOTE - this is all already in umbraco.MacroEngines + // which references Umbraco.Web - so we can't reference it without + // creating circular references + // fixme - there has to be a better way? + + public static INode ConvertToNode(IPublishedContent doc) + { + var node = new ConvertedNode(doc); + return node; + } + + public static IProperty ConvertToNodeProperty(IPublishedProperty prop) + { + return new ConvertedProperty(prop.PropertyTypeAlias, prop.Value.ToString()); + } + + private class ConvertedNode : INode + { + private readonly IPublishedContent _doc; + + public ConvertedNode(IPublishedContent doc) + { + _doc = doc; + + if (doc == null) + { + Id = 0; + return; + } + + template = doc.TemplateId; + Id = doc.Id; + Path = doc.Path; + CreatorName = doc.CreatorName; + SortOrder = doc.SortOrder; + UpdateDate = doc.UpdateDate; + Name = doc.Name; + NodeTypeAlias = doc.DocumentTypeAlias; + CreateDate = doc.CreateDate; + CreatorID = doc.CreatorId; + Level = doc.Level; + UrlName = doc.UrlName; + Version = doc.Version; + WriterID = doc.WriterId; + WriterName = doc.WriterName; + } + + public INode Parent + { + get { return ConvertToNode(_doc.Parent); } + } + public int Id { get; private set; } + public int template { get; private set; } + public int SortOrder { get; private set; } + public string Name { get; private set; } + public string UrlName { get; private set; } + public string NodeTypeAlias { get; private set; } + public string WriterName { get; private set; } + public string CreatorName { get; private set; } + public int WriterID { get; private set; } + public int CreatorID { get; private set; } + public string Path { get; private set; } + public DateTime CreateDate { get; private set; } + public DateTime UpdateDate { get; private set; } + public Guid Version { get; private set; } + + public string NiceUrl + { + get { return _doc.Url; } + } + + public string Url + { + get { return _doc.Url; } + } + + public int Level { get; private set; } + public List PropertiesAsList + { + get { return _doc.Properties.Select(ConvertToNodeProperty).ToList(); } + } + public List ChildrenAsList + { + get { return _doc.Children.Select(ConvertToNode).ToList(); } + } + public IProperty GetProperty(string Alias) + { + return PropertiesAsList.Cast().FirstOrDefault(p => p.Alias == Alias); + } + + public IProperty GetProperty(string Alias, out bool propertyExists) + { + foreach (var p in from global::umbraco.NodeFactory.Property p in PropertiesAsList where p.Alias == Alias select p) + { + propertyExists = true; + return p; + } + propertyExists = false; + return null; + } + + public DataTable ChildrenAsTable() + { + return _doc.ChildrenAsTable(); + } + + public DataTable ChildrenAsTable(string nodeTypeAliasFilter) + { + return _doc.ChildrenAsTable(nodeTypeAliasFilter); + } + } + + private class ConvertedProperty : IProperty, IHtmlString + { + private readonly string _alias; + private readonly string _value; + + public ConvertedProperty(string alias, string value) + { + _alias = alias; + _value = value; + } + + public string Alias + { + get { return _alias; } + } + + public string Value + { + get { return _value; } + } + + public Guid Version + { + get { return Guid.Empty; } + } + + public bool IsNull() + { + return Value == null; + } + + public bool HasValue() + { + return !string.IsNullOrWhiteSpace(Value); + } + + public int ContextId { get; set; } + public string ContextAlias { get; set; } + + // implements IHtmlString.ToHtmlString + public string ToHtmlString() + { + return Value; + } + } + } +} diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 9e4350de77..f97cd789eb 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -1,9 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Data; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Net.Security; using System.Reflection; @@ -20,11 +22,14 @@ using System.Xml.Xsl; using StackExchange.Profiling; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Dynamics; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Xml.XPath; using Umbraco.Core.Profiling; +using umbraco.interfaces; using Umbraco.Web; using Umbraco.Web.Cache; using Umbraco.Web.Macros; @@ -32,13 +37,16 @@ using Umbraco.Web.Templates; using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.macro; -using umbraco.cms.businesslogic.member; using umbraco.DataLayer; using umbraco.NodeFactory; using umbraco.presentation.templateControls; +using Umbraco.Web.umbraco.presentation; using Content = umbraco.cms.businesslogic.Content; +using File = System.IO.File; using Macro = umbraco.cms.businesslogic.macro.Macro; using MacroErrorEventArgs = Umbraco.Core.Events.MacroErrorEventArgs; +using Member = umbraco.cms.businesslogic.member.Member; +using Property = umbraco.NodeFactory.Property; namespace umbraco { @@ -1455,7 +1463,7 @@ namespace umbraco IMacroEngine engine = null; engine = MacroEngineFactory.GetEngine(PartialViewMacroEngine.EngineName); - var ret = engine.Execute(macro, Node.GetCurrent()); + var ret = engine.Execute(macro, CompatibilityHelper.ConvertToNode(UmbracoContext.Current.PublishedContentRequest.PublishedContent)); // if the macro engine supports success reporting and executing failed, then return an empty control so it's not cached if (engine is IMacroEngineResultStatus) @@ -1480,13 +1488,13 @@ namespace umbraco engine = MacroEngineFactory.GetByExtension(macro.ScriptLanguage); ret = engine.Execute( macro, - Node.GetCurrent()); + CompatibilityHelper.ConvertToNode(UmbracoContext.Current.PublishedContentRequest.PublishedContent)); } else { string path = IOHelper.MapPath(SystemDirectories.MacroScripts + "/" + macro.ScriptName); engine = MacroEngineFactory.GetByFilename(path); - ret = engine.Execute(macro, Node.GetCurrent()); + ret = engine.Execute(macro, CompatibilityHelper.ConvertToNode(UmbracoContext.Current.PublishedContentRequest.PublishedContent)); } // if the macro engine supports success reporting and executing failed, then return an empty control so it's not cached @@ -1513,13 +1521,13 @@ namespace umbraco engine = MacroEngineFactory.GetByExtension(macro.ScriptLanguage); ret.Text = engine.Execute( macro, - Node.GetCurrent()); + CompatibilityHelper.ConvertToNode(UmbracoContext.Current.PublishedContentRequest.PublishedContent)); } else { string path = IOHelper.MapPath(SystemDirectories.MacroScripts + "/" + macro.ScriptName); engine = MacroEngineFactory.GetByFilename(path); - ret.Text = engine.Execute(macro, Node.GetCurrent()); + ret.Text = engine.Execute(macro, CompatibilityHelper.ConvertToNode(UmbracoContext.Current.PublishedContentRequest.PublishedContent)); } // if the macro engine supports success reporting and executing failed, then return an empty control so it's not cached diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index 36b8abfdb7..6694ab7c68 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -273,9 +273,17 @@ namespace umbraco { foreach(var p in node.Properties) { - if (!_elements.ContainsKey(p.PropertyTypeAlias)) + if (_elements.ContainsKey(p.PropertyTypeAlias) == false) { - _elements[p.PropertyTypeAlias] = p.Value; + // note: legacy used the raw value (see populating from an Xml node below) + // so we're doing the same here, using DataValue. If we use Value then every + // value will be converted NOW - including RTEs that may contain macros that + // require that the 'page' is already initialized = catch-22. + + // to properly fix this, we'd need to turn the elements collection into some + // sort of collection of lazy values. + + _elements[p.PropertyTypeAlias] = p.DataValue; } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/nodeFactory/Node.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/nodeFactory/Node.cs index 57d701e45b..e25754b0a5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/nodeFactory/Node.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/nodeFactory/Node.cs @@ -10,6 +10,7 @@ using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.propertytype; using umbraco.interfaces; using Umbraco.Core; +using Umbraco.Web; namespace umbraco.NodeFactory { @@ -541,11 +542,10 @@ namespace umbraco.NodeFactory public static int getCurrentNodeId() { - XmlNode n = ((IHasXmlNode)library.GetXmlNodeCurrent().Current).GetNode(); - if (n.Attributes == null || n.Attributes.GetNamedItem("id") == null) - throw new ArgumentException("Current node is null. This might be due to previewing an unpublished node. As the NodeFactory works with published data, macros using the node factory won't work in preview mode.", "Current node is " + System.Web.HttpContext.Current.Items["pageID"].ToString()); - - return int.Parse(n.Attributes.GetNamedItem("id").Value); + if (UmbracoContext.Current == null) throw new InvalidOperationException("Cannot get current node id without an UmbracoContext."); + if (UmbracoContext.Current.PublishedContentRequest == null) throw new InvalidOperationException("Cannot get current node id without a PublishedContentRequest."); + if (UmbracoContext.Current.PublishedContentRequest.HasPublishedContent == false) throw new InvalidOperationException("Cannot get current node id because the current PublishedContentRequest has no content."); + return UmbracoContext.Current.PublishedContentRequest.PublishedContent.Id; } } } \ No newline at end of file diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs index 0362095d4f..e2a7c6d56f 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs @@ -6,9 +6,11 @@ using umbraco.interfaces; using umbraco.cms.businesslogic.media; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.property; -using umbraco.presentation.nodeFactory; using System.Data; using Umbraco.Core; +using umbraco.MacroEngines.Library; +using Umbraco.Web; +using Umbraco.Web.umbraco.presentation; namespace umbraco.MacroEngines { @@ -30,11 +32,11 @@ namespace umbraco.MacroEngines } public DynamicBackingItem(int Id) { - NodeFactory.Node baseNode = new NodeFactory.Node(Id); + var n = UmbracoContext.Current.ContentCache.GetById(Id).ConvertToNode(); - this.content = baseNode; + this.content = n; this.Type = DynamicBackingItemType.Content; - if (baseNode.Id == 0 && Id != 0) + if (n.Id == 0 && Id != 0) { this.media = ExamineBackedMedia.GetUmbracoMedia(Id); this.Type = DynamicBackingItemType.Media; @@ -48,7 +50,6 @@ namespace umbraco.MacroEngines } public DynamicBackingItem(int Id, DynamicBackingItemType Type) { - NodeFactory.Node baseNode = new NodeFactory.Node(Id); if (Type == DynamicBackingItemType.Media) { this.media = ExamineBackedMedia.GetUmbracoMedia(Id); @@ -56,7 +57,7 @@ namespace umbraco.MacroEngines } else { - this.content = baseNode; + this.content = UmbracoContext.Current.ContentCache.GetById(Id).ConvertToNode(); this.Type = Type; } } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs b/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs index cf5180d5e1..59b32fa0ea 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models; using Umbraco.Web; using umbraco.NodeFactory; using umbraco.interfaces; +using Umbraco.Web.umbraco.presentation; using Property = umbraco.NodeFactory.Property; namespace umbraco.MacroEngines.Library @@ -18,107 +19,9 @@ namespace umbraco.MacroEngines.Library /// These are dedicated to converting DynamicPublishedContent to INode. internal static class PublishedContentExtensions { - internal static IProperty ConvertToNodeProperty(this IPublishedProperty prop) - { - return new PropertyResult(prop.PropertyTypeAlias, prop.Value.ToString()); - } - internal static INode ConvertToNode(this IPublishedContent doc) { - var node = new ConvertedNode(doc); - return node; - } - - /// - /// Internal custom INode class used for conversions from DynamicPublishedContent. - /// - private class ConvertedNode : INode - { - private readonly IPublishedContent _doc; - - public ConvertedNode(IPublishedContent doc) - { - _doc = doc; - template = doc.TemplateId; - Id = doc.Id; - Path = doc.Path; - CreatorName = doc.CreatorName; - SortOrder = doc.SortOrder; - UpdateDate = doc.UpdateDate; - Name = doc.Name; - NodeTypeAlias = doc.DocumentTypeAlias; - CreateDate = doc.CreateDate; - CreatorID = doc.CreatorId; - Level = doc.Level; - UrlName = doc.UrlName; - Version = doc.Version; - WriterID = doc.WriterId; - WriterName = doc.WriterName; - } - - public INode Parent - { - get { return _doc.Parent.ConvertToNode(); } - } - public int Id { get; private set; } - public int template { get; private set; } - public int SortOrder { get; private set; } - public string Name { get; private set; } - public string UrlName { get; private set; } - public string NodeTypeAlias { get; private set; } - public string WriterName { get; private set; } - public string CreatorName { get; private set; } - public int WriterID { get; private set; } - public int CreatorID { get; private set; } - public string Path { get; private set; } - public DateTime CreateDate { get; private set; } - public DateTime UpdateDate { get; private set; } - public Guid Version { get; private set; } - - public string NiceUrl - { - get { return library.NiceUrl(Id); } - } - - public string Url - { - get { return library.NiceUrl(Id); } - } - - public int Level { get; private set; } - public List PropertiesAsList - { - get { return _doc.Properties.Select(ConvertToNodeProperty).ToList(); } - } - public List ChildrenAsList - { - get { return _doc.Children.Select(x => x.ConvertToNode()).ToList(); } - } - public IProperty GetProperty(string Alias) - { - return PropertiesAsList.Cast().FirstOrDefault(p => p.Alias == Alias); - } - - public IProperty GetProperty(string Alias, out bool propertyExists) - { - foreach (var p in from Property p in PropertiesAsList where p.Alias == Alias select p) - { - propertyExists = true; - return p; - } - propertyExists = false; - return null; - } - - public DataTable ChildrenAsTable() - { - return _doc.ChildrenAsTable(); - } - - public DataTable ChildrenAsTable(string nodeTypeAliasFilter) - { - return _doc.ChildrenAsTable(nodeTypeAliasFilter); - } + return CompatibilityHelper.ConvertToNode(doc); } } } \ No newline at end of file