diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index a20b5d8f76..71dc587c1c 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Xml.XPath; using Umbraco.Core.Models; using Umbraco.Core.Xml; @@ -72,6 +73,15 @@ namespace Umbraco.Web.PublishedCache return _cache.GetByXPath(UmbracoContext, xpath, vars); } + /// + /// Gets an XPath navigator that can be used to navigate contents. + /// + /// The XPath navigator. + public virtual XPathNavigator GetXPathNavigator() + { + return _cache.GetXPathNavigator(UmbracoContext); + } + /// /// Gets a value indicating whether the underlying non-contextual cache contains published content. /// diff --git a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs index c0a25e47fd..b28e1f786f 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; - +using System.Xml.XPath; using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Models; using Umbraco.Core.Xml; @@ -16,12 +16,12 @@ namespace Umbraco.Web.PublishedCache "We need to create something like the IPublishListener interface to have proper published content storage.")] public interface IPublishedCache { - /// - /// Gets a content identified by its unique identifier. - /// - /// The context. - /// The content unique identifier. - /// The content, or null. + /// + /// Gets a content identified by its unique identifier. + /// + /// The context. + /// The content unique identifier. + /// The content, or null. IPublishedContent GetById(UmbracoContext umbracoContext, int contentId); /// @@ -49,6 +49,13 @@ namespace Umbraco.Web.PublishedCache /// The contents. IEnumerable GetByXPath(UmbracoContext umbracoContext, string xpath, XPathVariable[] vars); + /// + /// Gets an XPath navigator that can be used to navigate contents. + /// + /// The context. + /// The XPath navigator. + XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext); + /// /// Gets a value indicating whether the cache contains published content. /// diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index b48d680918..f4b17ecf7a 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text; using System.Xml; +using System.Xml.XPath; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Xml; @@ -37,14 +38,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. /// If is null then the settings value is used. /// - public IPublishedContent GetByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null) + public virtual IPublishedContent GetByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null) { if (route == null) throw new ArgumentNullException("route"); // try to get from cache if not previewing - var contentId = umbracoContext.InPreviewMode - ? 0 - : _routesCache.GetNodeId(route); + var contentId = umbracoContext.InPreviewMode ? 0 : _routesCache.GetNodeId(route); // if found id in cache then get corresponding content // and clear cache if not found - for whatever reason @@ -79,12 +78,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// The context. /// The content unique identifier. /// The route. - public string GetRouteById(UmbracoContext umbracoContext, int contentId) + public virtual string GetRouteById(UmbracoContext umbracoContext, int contentId) { // try to get from cache if not previewing - var route = umbracoContext.InPreviewMode - ? null - : _routesCache.GetRoute(contentId); + var route = umbracoContext.InPreviewMode ? null : _routesCache.GetRoute(contentId); // if found in cache then return if (route != null) @@ -176,6 +173,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (node.Parent == null) { var rootNode = umbracoContext.ContentCache.GetByRoute("/", true); + if (rootNode == null) + throw new Exception("Failed to get node at /."); if (rootNode.Id == node.Id) // remove only if we're the default node pathParts.RemoveAt(pathParts.Count - 1); } @@ -270,12 +269,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return ConvertToDocument(GetXml(umbracoContext).GetElementById(nodeId.ToString())); } - public IEnumerable GetAtRoot(UmbracoContext umbracoContext) + public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext) { return (from XmlNode x in GetXml(umbracoContext).SelectNodes(XPathStrings.RootDocuments) select ConvertToDocument(x)).ToList(); } - public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, string xpath, params XPathVariable[] vars) + public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, string xpath, params XPathVariable[] vars) { if (xpath == null) throw new ArgumentNullException("xpath"); if (string.IsNullOrWhiteSpace(xpath)) return null; @@ -287,7 +286,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return ConvertToDocument(node); } - public IEnumerable GetByXPath(UmbracoContext umbracoContext, string xpath, params XPathVariable[] vars) + public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, string xpath, params XPathVariable[] vars) { if (xpath == null) throw new ArgumentNullException("xpath"); if (string.IsNullOrWhiteSpace(xpath)) return Enumerable.Empty(); @@ -299,7 +298,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return ConvertToDocuments(nodes); } - public bool HasContent() + public virtual bool HasContent() { var xml = GetXml(); if (xml == null) @@ -308,6 +307,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return node != null; } + public virtual XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext) + { + var xml = GetXml(umbracoContext); + return xml.CreateNavigator(); + } + #endregion #region Legacy Xml diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index b160dcfa2f..08e0f65a2f 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private readonly BaseSearchProvider _searchProvider; private readonly BaseIndexProvider _indexProvider; - public virtual IPublishedContent GetById(UmbracoContext umbracoContext, int nodeId) + public virtual IPublishedContent GetById(UmbracoContext umbracoContext, int nodeId) { return GetUmbracoMedia(nodeId); } @@ -69,14 +69,19 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, string xpath, XPathVariable[] vars) { - throw new NotImplementedException("PublishedMediaCache does not support XPath queries."); + throw new NotImplementedException("PublishedMediaCache does not support XPath."); } public IEnumerable GetByXPath(UmbracoContext umbracoContext, string xpath, XPathVariable[] vars) { - throw new NotImplementedException("PublishedMediaCache does not support XPath queries."); + throw new NotImplementedException("PublishedMediaCache does not support XPath."); } - + + public virtual XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + } + public bool HasContent() { throw new NotImplementedException(); } private ExamineManager GetExamineManagerSafe() diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 3fc7ebee42..0fed70a7a3 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -293,6 +293,9 @@ namespace Umbraco.Web /// public int? PageId { + // TODO - this is dirty old legacy tricks, we should clean it up at some point + // also, what is a "custom page" and when should this be either null, or different + // from PublishedContentRequest.PublishedContent.Id ?? get { try diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 2a511acc84..b16b0d967f 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Net; using System.Net.Security; @@ -14,6 +15,7 @@ using System.Web.Caching; using System.Web.UI; using System.Web.UI.WebControls; using System.Xml; +using System.Xml.XPath; using System.Xml.Xsl; using Umbraco.Core; using Umbraco.Core.Cache; @@ -43,7 +45,6 @@ namespace umbraco { #region private properties - /// Cache for . private static Dictionary _predefinedExtensions; private const string LoadUserControlKey = "loadUserControl"; @@ -767,17 +768,20 @@ namespace umbraco HttpRuntime.Cache.Remove("macroXslt_" + XsltFile); } + #region LoadMacroXslt + + // gets the control for the macro, using GetXsltTransform methods for execution + // will pick XmlDocument or Navigator mode depending on the capabilities of the published caches internal Control LoadMacroXslt(macro macro, MacroModel model, Hashtable pageElements, bool throwError) { if (XsltFile.Trim() != string.Empty) { - // Get main XML - var umbracoXml = umbraco.content.Instance.XmlContent; + XmlDocument macroXml = null; - // Create XML document for Macro - var macroXml = new XmlDocument(); + // get master xml document + XmlDocument umbracoXml = umbraco.content.Instance.XmlContent; + macroXml = new XmlDocument(); macroXml.LoadXml(""); - foreach (var prop in macro.Model.Properties) { AddMacroXmlNode(umbracoXml, macroXml, prop.Key, prop.Type, prop.Value); @@ -785,10 +789,11 @@ namespace umbraco if (HttpContext.Current.Request.QueryString["umbDebug"] != null && GlobalSettings.DebugMode) { + var outerXml = macroXml.OuterXml; return new LiteralControl("
Debug from " + macro.Name + - "

" + HttpContext.Current.Server.HtmlEncode(macroXml.OuterXml) + + "

" + HttpContext.Current.Server.HtmlEncode(outerXml) + "

"); } @@ -798,7 +803,8 @@ namespace umbraco try { - var result = CreateControlsFromText(GetXsltTransformResult(macroXml, xsltFile)); + var transformed = GetXsltTransformResult(macroXml, xsltFile); + var result = CreateControlsFromText(transformed); TraceInfo("umbracoMacro", "After performing transformation"); @@ -849,11 +855,14 @@ namespace umbraco return new LiteralControl(string.Empty); } + // gets the control for the macro, using GetXsltTransform methods for execution public Control loadMacroXSLT(macro macro, MacroModel model, Hashtable pageElements) { return LoadMacroXslt(macro, model, pageElements, false); } + #endregion + /// /// Parses the text for umbraco Item controls that need to be rendered. /// @@ -908,13 +917,16 @@ namespace umbraco return container; } - public static string GetXsltTransformResult(XmlDocument macroXML, XslCompiledTransform xslt) + #region GetXsltTransform + + // gets the result of the xslt transform with no parameters - XmlDocument mode + public static string GetXsltTransformResult(XmlDocument macroXml, XslCompiledTransform xslt) { - return GetXsltTransformResult(macroXML, xslt, null); + return GetXsltTransformResult(macroXml, xslt, null); } - public static string GetXsltTransformResult(XmlDocument macroXML, XslCompiledTransform xslt, - Dictionary parameters) + // gets the result of the xslt transform - XmlDocument mode + public static string GetXsltTransformResult(XmlDocument macroXml, XslCompiledTransform xslt, Dictionary parameters) { TextWriter tw = new StringWriter(); @@ -938,10 +950,49 @@ namespace umbraco // Do transformation TraceInfo("umbracoMacro", "Before performing transformation"); - xslt.Transform(macroXML.CreateNavigator(), xslArgs, tw); + xslt.Transform(macroXml.CreateNavigator(), xslArgs, tw); return TemplateUtilities.ResolveUrlsFromTextString(tw.ToString()); } + // gets the result of the xslt transform with no parameters - Navigator mode + public static string GetXsltTransformResult(XPathNavigator macroNavigator, XPathNavigator contentNavigator, + XslCompiledTransform xslt) + { + return GetXsltTransformResult(macroNavigator, contentNavigator, xslt, null); + } + + // gets the result of the xslt transform - Navigator mode + public static string GetXsltTransformResult(XPathNavigator macroNavigator, XPathNavigator contentNavigator, + XslCompiledTransform xslt, Dictionary parameters) + { + TextWriter tw = new StringWriter(); + + TraceInfo("umbracoMacro", "Before adding extensions"); + XsltArgumentList xslArgs = AddXsltExtensions(); + var lib = new library(); + xslArgs.AddExtensionObject("urn:umbraco.library", lib); + TraceInfo("umbracoMacro", "After adding extensions"); + + // Add parameters + if (parameters == null || !parameters.ContainsKey("currentPage")) + { + var current = contentNavigator.Clone().Select("//* [@id=" + HttpContext.Current.Items["pageID"] + "]"); + xslArgs.AddParam("currentPage", string.Empty, current); + } + if (parameters != null) + { + foreach (var parameter in parameters) + xslArgs.AddParam(parameter.Key, string.Empty, parameter.Value); + } + + // Do transformation + TraceInfo("umbracoMacro", "Before performing transformation"); + xslt.Transform(macroNavigator, xslArgs, tw); + return TemplateUtilities.ResolveUrlsFromTextString(tw.ToString()); + } + + #endregion + public static XsltArgumentList AddXsltExtensions() { return AddMacroXsltExtensions(); @@ -973,9 +1024,6 @@ namespace umbraco GetXsltExtensionsImpl); } - // zb-00041 #29966 : cache the extensions - - private static Dictionary GetXsltExtensionsImpl() { // fill a dictionary with the predefined extensions @@ -1093,21 +1141,17 @@ namespace umbraco return xslArgs; } - private void AddMacroXmlNode(XmlDocument umbracoXml, XmlDocument macroXml, String macroPropertyAlias, - String macroPropertyType, String macroPropertyValue) + #region LoadMacroXslt (2) + + // add elements to the root node, corresponding to parameters + private void AddMacroXmlNode(XmlDocument umbracoXml, XmlDocument macroXml, + string macroPropertyAlias, string macroPropertyType, string macroPropertyValue) { XmlNode macroXmlNode = macroXml.CreateNode(XmlNodeType.Element, macroPropertyAlias, string.Empty); var x = new XmlDocument(); - var currentId = -1; - // If no value is passed, then use the current pageID as value - if (macroPropertyValue == string.Empty) - { - var umbPage = (page)HttpContext.Current.Items["umbPageObject"]; - if (umbPage == null) - return; - currentId = umbPage.PageID; - } + // if no value is passed, then use the current "pageID" as value + var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue; TraceInfo("umbracoMacro", "Xslt node adding search start (" + macroPropertyAlias + ",'" + @@ -1116,29 +1160,20 @@ namespace umbraco { case "contentTree": var nodeId = macroXml.CreateAttribute("nodeID"); - if (macroPropertyValue != string.Empty) - nodeId.Value = macroPropertyValue; - else - nodeId.Value = currentId.ToString(); + nodeId.Value = contentId; macroXmlNode.Attributes.SetNamedItem(nodeId); // Get subs try { - macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.GetElementById(nodeId.Value), true)); + macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.GetElementById(contentId), true)); } catch - { - break; - } + { } break; - case "contentCurrent": - x.LoadXml(""); - XmlNode currentNode; - if (macroPropertyValue != string.Empty) - currentNode = macroXml.ImportNode(umbracoXml.GetElementById(macroPropertyValue), true); - else - currentNode = macroXml.ImportNode(umbracoXml.GetElementById(currentId.ToString()), true); + + case "contentPicker": + var currentNode = macroXml.ImportNode(umbracoXml.GetElementById(contentId), true); // remove all sub content nodes foreach (XmlNode n in currentNode.SelectNodes("node|*[@isDoc]")) @@ -1147,37 +1182,36 @@ namespace umbraco macroXmlNode.AppendChild(currentNode); break; - case "contentSubs": - x.LoadXml(""); - if (macroPropertyValue != string.Empty) - x.FirstChild.AppendChild(x.ImportNode(umbracoXml.GetElementById(macroPropertyValue), true)); - else - x.FirstChild.AppendChild(x.ImportNode(umbracoXml.GetElementById(currentId.ToString()), true)); - macroXmlNode.InnerXml = TransformMacroXml(x, "macroGetSubs.xsl"); + + case "contentSubs": // disable that one, it does not work anyway... + //x.LoadXml(""); + //x.FirstChild.AppendChild(x.ImportNode(umbracoXml.GetElementById(contentId), true)); + //macroXmlNode.InnerXml = TransformMacroXml(x, "macroGetSubs.xsl"); break; + case "contentAll": - x.ImportNode(umbracoXml.DocumentElement.LastChild, true); + macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.DocumentElement, true)); break; + case "contentRandom": - XmlNode source = umbracoXml.GetElementById(macroPropertyValue); + XmlNode source = umbracoXml.GetElementById(contentId); if (source != null) { - XmlNodeList sourceList = source.SelectNodes("node|*[@isDoc]"); + var sourceList = source.SelectNodes("node|*[@isDoc]"); if (sourceList.Count > 0) { int rndNumber; - Random r = library.GetRandom(); + var r = library.GetRandom(); lock (r) { rndNumber = r.Next(sourceList.Count); } - XmlNode node = macroXml.ImportNode(sourceList[rndNumber], true); + var node = macroXml.ImportNode(sourceList[rndNumber], true); // remove all sub content nodes foreach (XmlNode n in node.SelectNodes("node|*[@isDoc]")) node.RemoveChild(n); macroXmlNode.AppendChild(node); - break; } else TraceWarn("umbracoMacro", @@ -1189,10 +1223,12 @@ namespace umbraco "Error adding random node - parent (" + macroPropertyValue + ") doesn't exists!"); break; + case "mediaCurrent": var c = new Content(int.Parse(macroPropertyValue)); macroXmlNode.AppendChild(macroXml.ImportNode(c.ToXml(umbraco.content.Instance.XmlContent, false), true)); break; + default: macroXmlNode.InnerText = HttpContext.Current.Server.HtmlDecode(macroPropertyValue); break; @@ -1200,21 +1236,7 @@ namespace umbraco macroXml.FirstChild.AppendChild(macroXmlNode); } - private static string TransformMacroXml(XmlDocument xmlSource, string xsltFile) - { - var sb = new StringBuilder(); - var sw = new StringWriter(sb); - - XslCompiledTransform result = getXslt(xsltFile); - //XmlDocument xslDoc = new XmlDocument(); - - result.Transform(xmlSource.CreateNavigator(), null, sw); - - if (sw.ToString() != string.Empty) - return sw.ToString(); - else - return string.Empty; - } + #endregion /// /// Renders a Partial View Macro @@ -1278,8 +1300,6 @@ namespace umbraco return retVal; } - - [Obsolete("Replaced with loadMacroScript", true)] public DLRMacroResult loadMacroDLR(MacroModel macro) { diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index 592a8999ab..58fc1a4d94 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -327,8 +327,6 @@ namespace umbraco { template templateDesign = new template(templateId); - HttpContext.Current.Items["umbPageObject"] = this; - _pageContentControl = templateDesign.ParseWithControls(this); _pageContent.Append(templateDesign.TemplateContent); }