2012-07-20 01:04:35 +06:00
|
|
|
using System;
|
2012-10-04 01:31:08 +05:00
|
|
|
using System.Collections.Generic;
|
2013-03-20 16:01:49 -01:00
|
|
|
using System.Globalization;
|
2013-05-03 20:09:15 -02:00
|
|
|
using System.Runtime.CompilerServices;
|
2012-07-20 01:04:35 +06:00
|
|
|
using System.Text;
|
|
|
|
|
using System.Xml;
|
2013-04-03 11:19:10 -02:00
|
|
|
using System.Xml.XPath;
|
2013-03-20 16:01:49 -01:00
|
|
|
using Umbraco.Core.Logging;
|
2012-08-10 13:08:47 +06:00
|
|
|
using Umbraco.Core.Models;
|
2013-02-14 16:23:56 -01:00
|
|
|
using Umbraco.Core.Xml;
|
2013-03-20 16:01:49 -01:00
|
|
|
using Umbraco.Web.Routing;
|
2012-07-20 01:04:35 +06:00
|
|
|
using umbraco;
|
2012-09-13 12:19:56 +07:00
|
|
|
using System.Linq;
|
2013-03-19 17:51:55 -01:00
|
|
|
using umbraco.BusinessLogic;
|
|
|
|
|
using umbraco.presentation.preview;
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-22 15:02:26 -01:00
|
|
|
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
2012-07-20 01:04:35 +06:00
|
|
|
{
|
2013-02-05 06:31:13 -01:00
|
|
|
internal class PublishedContentCache : IPublishedContentCache
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
2013-03-20 16:01:49 -01:00
|
|
|
#region Routes cache
|
|
|
|
|
|
|
|
|
|
private readonly RoutesCache _routesCache = new RoutesCache(!UnitTesting);
|
|
|
|
|
|
|
|
|
|
// for INTERNAL, UNIT TESTS use ONLY
|
|
|
|
|
internal RoutesCache RoutesCache { get { return _routesCache; } }
|
|
|
|
|
|
|
|
|
|
// for INTERNAL, UNIT TESTS use ONLY
|
|
|
|
|
internal static bool UnitTesting = false;
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual IPublishedContent GetByRoute(UmbracoContext umbracoContext, bool preview, string route, bool? hideTopLevelNode = null)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
|
|
|
|
if (route == null) throw new ArgumentNullException("route");
|
|
|
|
|
|
|
|
|
|
// try to get from cache if not previewing
|
2013-03-31 18:47:25 -02:00
|
|
|
var contentId = preview ? 0 : _routesCache.GetNodeId(route);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// if found id in cache then get corresponding content
|
|
|
|
|
// and clear cache if not found - for whatever reason
|
|
|
|
|
IPublishedContent content = null;
|
|
|
|
|
if (contentId > 0)
|
|
|
|
|
{
|
2013-03-31 18:47:25 -02:00
|
|
|
content = GetById(umbracoContext, preview, contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
if (content == null)
|
|
|
|
|
_routesCache.ClearNode(contentId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// still have nothing? actually determine the id
|
|
|
|
|
hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath; // default = settings
|
2013-03-31 18:47:25 -02:00
|
|
|
content = content ?? DetermineIdByRoute(umbracoContext, preview, route, hideTopLevelNode.Value);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// cache if we have a content and not previewing
|
2013-03-31 18:47:25 -02:00
|
|
|
if (content != null && !preview)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
|
|
|
|
var domainRootNodeId = route.StartsWith("/") ? -1 : int.Parse(route.Substring(0, route.IndexOf('/')));
|
|
|
|
|
var iscanon = !UnitTesting && !DomainHelper.ExistsDomainInPath(DomainHelper.GetAllDomains(false), content.Path, domainRootNodeId);
|
|
|
|
|
// and only if this is the canonical url (the one GetUrl would return)
|
|
|
|
|
if (iscanon)
|
|
|
|
|
_routesCache.Store(contentId, route);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
|
|
|
|
// try to get from cache if not previewing
|
2013-03-31 18:47:25 -02:00
|
|
|
var route = preview ? null : _routesCache.GetRoute(contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// if found in cache then return
|
|
|
|
|
if (route != null)
|
|
|
|
|
return route;
|
|
|
|
|
|
|
|
|
|
// else actually determine the route
|
2013-03-31 18:47:25 -02:00
|
|
|
route = DetermineRouteById(umbracoContext, preview, contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// cache if we have a route and not previewing
|
2013-03-31 18:47:25 -02:00
|
|
|
if (route != null && !preview)
|
2013-03-20 16:01:49 -01:00
|
|
|
_routesCache.Store(contentId, route);
|
|
|
|
|
|
|
|
|
|
return route;
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
IPublishedContent DetermineIdByRoute(UmbracoContext umbracoContext, bool preview, string route, bool hideTopLevelNode)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
|
|
|
|
if (route == null) throw new ArgumentNullException("route");
|
|
|
|
|
|
|
|
|
|
//the route always needs to be lower case because we only store the urlName attribute in lower case
|
|
|
|
|
route = route.ToLowerInvariant();
|
|
|
|
|
|
|
|
|
|
var pos = route.IndexOf('/');
|
|
|
|
|
var path = pos == 0 ? route : route.Substring(pos);
|
|
|
|
|
var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos));
|
|
|
|
|
IEnumerable<XPathVariable> vars;
|
|
|
|
|
|
|
|
|
|
var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode, out vars);
|
|
|
|
|
|
|
|
|
|
//check if we can find the node in our xml cache
|
2013-03-31 18:47:25 -02:00
|
|
|
var content = GetSingleByXPath(umbracoContext, preview, xpath, vars == null ? null : vars.ToArray());
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-20 16:01:49 -01:00
|
|
|
// if hideTopLevelNodePath is true then for url /foo we looked for /*/foo
|
|
|
|
|
// but maybe that was the url of a non-default top-level node, so we also
|
|
|
|
|
// have to look for /foo (see note in ApplyHideTopLevelNodeFromPath).
|
|
|
|
|
if (content == null && hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
|
|
|
|
{
|
|
|
|
|
xpath = CreateXpathQuery(startNodeId, path, false, out vars);
|
2013-03-31 18:47:25 -02:00
|
|
|
content = GetSingleByXPath(umbracoContext, preview, xpath, vars == null ? null : vars.ToArray());
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
string DetermineRouteById(UmbracoContext umbracoContext, bool preview, int contentId)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
2013-03-31 18:47:25 -02:00
|
|
|
var node = GetById(umbracoContext, preview, contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
if (node == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
// walk up from that node until we hit a node with a domain,
|
|
|
|
|
// or we reach the content root, collecting urls in the way
|
|
|
|
|
var pathParts = new List<string>();
|
|
|
|
|
var n = node;
|
|
|
|
|
var hasDomains = DomainHelper.NodeHasDomains(n.Id);
|
|
|
|
|
while (!hasDomains && n != null) // n is null at root
|
|
|
|
|
{
|
|
|
|
|
// get the url
|
|
|
|
|
var urlName = n.UrlName;
|
|
|
|
|
pathParts.Add(urlName);
|
|
|
|
|
|
|
|
|
|
// move to parent node
|
|
|
|
|
n = n.Parent;
|
|
|
|
|
hasDomains = n != null && DomainHelper.NodeHasDomains(n.Id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
|
|
|
|
|
if (!hasDomains && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
|
|
|
|
|
ApplyHideTopLevelNodeFromPath(umbracoContext, node, pathParts);
|
|
|
|
|
|
|
|
|
|
// assemble the route
|
|
|
|
|
pathParts.Reverse();
|
|
|
|
|
var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
|
|
|
|
|
var route = (n == null ? "" : n.Id.ToString(CultureInfo.InvariantCulture)) + path;
|
|
|
|
|
|
|
|
|
|
return route;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, IPublishedContent node, IList<string> pathParts)
|
|
|
|
|
{
|
|
|
|
|
// in theory if hideTopLevelNodeFromPath is true, then there should be only once
|
|
|
|
|
// top-level node, or else domains should be assigned. but for backward compatibility
|
|
|
|
|
// we add this check - we look for the document matching "/" and if it's not us, then
|
|
|
|
|
// we do not hide the top level path
|
|
|
|
|
// it has to be taken care of in GetByRoute too so if
|
|
|
|
|
// "/foo" fails (looking for "/*/foo") we try also "/foo".
|
|
|
|
|
// this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but
|
|
|
|
|
// that's the way it works pre-4.10 and we try to be backward compat for the time being
|
|
|
|
|
if (node.Parent == null)
|
|
|
|
|
{
|
|
|
|
|
var rootNode = umbracoContext.ContentCache.GetByRoute("/", true);
|
2013-03-31 18:40:55 -02:00
|
|
|
if (rootNode == null)
|
|
|
|
|
throw new Exception("Failed to get node at /.");
|
2013-03-20 16:01:49 -01:00
|
|
|
if (rootNode.Id == node.Id) // remove only if we're the default node
|
|
|
|
|
pathParts.RemoveAt(pathParts.Count - 1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pathParts.RemoveAt(pathParts.Count - 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region XPath Strings
|
|
|
|
|
|
|
|
|
|
class XPathStringsDefinition
|
2012-10-05 08:00:06 -02:00
|
|
|
{
|
2012-10-05 08:23:43 -02:00
|
|
|
public int Version { get; private set; }
|
|
|
|
|
|
2013-02-05 06:31:13 -01:00
|
|
|
public static string Root { get { return "/root"; } }
|
2012-10-05 08:00:06 -02:00
|
|
|
public string RootDocuments { get; private set; }
|
|
|
|
|
public string DescendantDocumentById { get; private set; }
|
|
|
|
|
public string ChildDocumentByUrlName { get; private set; }
|
2013-02-19 13:58:16 -01:00
|
|
|
public string ChildDocumentByUrlNameVar { get; private set; }
|
|
|
|
|
public string RootDocumentWithLowestSortOrder { get; private set; }
|
2012-10-05 08:00:06 -02:00
|
|
|
|
|
|
|
|
public XPathStringsDefinition(int version)
|
|
|
|
|
{
|
2012-10-05 08:23:43 -02:00
|
|
|
Version = version;
|
|
|
|
|
|
2012-10-05 08:00:06 -02:00
|
|
|
switch (version)
|
|
|
|
|
{
|
|
|
|
|
// legacy XML schema
|
|
|
|
|
case 0:
|
|
|
|
|
RootDocuments = "/root/node";
|
|
|
|
|
DescendantDocumentById = "//node [@id={0}]";
|
|
|
|
|
ChildDocumentByUrlName = "/node [@urlName='{0}']";
|
2013-02-19 13:58:16 -01:00
|
|
|
ChildDocumentByUrlNameVar = "/node [@urlName=${0}]";
|
2012-10-05 08:00:06 -02:00
|
|
|
RootDocumentWithLowestSortOrder = "/root/node [not(@sortOrder > ../node/@sortOrder)][1]";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// default XML schema as of 4.10
|
|
|
|
|
case 1:
|
|
|
|
|
RootDocuments = "/root/* [@isDoc]";
|
|
|
|
|
DescendantDocumentById = "//* [@isDoc and @id={0}]";
|
|
|
|
|
ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']";
|
2013-02-19 13:58:16 -01:00
|
|
|
ChildDocumentByUrlNameVar = "/* [@isDoc and @urlName=${0}]";
|
2012-10-05 08:00:06 -02:00
|
|
|
RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception(string.Format("Unsupported Xml schema version '{0}').", version));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-05 06:31:13 -01:00
|
|
|
static XPathStringsDefinition _xPathStringsValue;
|
2012-10-05 08:23:43 -02:00
|
|
|
static XPathStringsDefinition XPathStrings
|
2012-10-05 08:00:06 -02:00
|
|
|
{
|
2012-10-05 08:23:43 -02:00
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
// in theory XPathStrings should be a static variable that
|
|
|
|
|
// we should initialize in a static ctor - but then test cases
|
|
|
|
|
// that switch schemas fail - so cache and refresh when needed,
|
|
|
|
|
// ie never when running the actual site
|
|
|
|
|
|
2013-02-05 06:31:13 -01:00
|
|
|
var version = UmbracoSettings.UseLegacyXmlSchema ? 0 : 1;
|
|
|
|
|
if (_xPathStringsValue == null || _xPathStringsValue.Version != version)
|
|
|
|
|
_xPathStringsValue = new XPathStringsDefinition(version);
|
|
|
|
|
return _xPathStringsValue;
|
2012-10-05 08:23:43 -02:00
|
|
|
}
|
2012-10-05 08:00:06 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#region Converters
|
|
|
|
|
|
|
|
|
|
private static IPublishedContent ConvertToDocument(XmlNode xmlNode)
|
2012-08-10 13:08:47 +06:00
|
|
|
{
|
2013-02-05 06:31:13 -01:00
|
|
|
return xmlNode == null ? null : new Models.XmlPublishedContent(xmlNode);
|
|
|
|
|
}
|
2012-08-10 13:08:47 +06:00
|
|
|
|
2013-02-05 06:31:13 -01:00
|
|
|
private static IEnumerable<IPublishedContent> ConvertToDocuments(XmlNodeList xmlNodes)
|
|
|
|
|
{
|
|
|
|
|
return xmlNodes.Cast<XmlNode>().Select(xmlNode => new Models.XmlPublishedContent(xmlNode));
|
|
|
|
|
}
|
2012-09-08 11:59:01 +07:00
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Getters
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId)
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
2013-03-31 18:47:25 -02:00
|
|
|
return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)));
|
2012-09-08 11:59:01 +07:00
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext, bool preview)
|
|
|
|
|
{
|
|
|
|
|
return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments));
|
2012-10-04 01:31:08 +05:00
|
|
|
}
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars)
|
2013-02-05 06:31:13 -01:00
|
|
|
{
|
|
|
|
|
if (xpath == null) throw new ArgumentNullException("xpath");
|
|
|
|
|
if (string.IsNullOrWhiteSpace(xpath)) return null;
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
var xml = GetXml(umbracoContext, preview);
|
2013-02-05 06:31:13 -01:00
|
|
|
var node = vars == null
|
|
|
|
|
? xml.SelectSingleNode(xpath)
|
|
|
|
|
: xml.SelectSingleNode(xpath, vars);
|
|
|
|
|
return ConvertToDocument(node);
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-10 12:49:45 -02:00
|
|
|
public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars)
|
|
|
|
|
{
|
|
|
|
|
if (xpath == null) throw new ArgumentNullException("xpath");
|
|
|
|
|
|
|
|
|
|
var xml = GetXml(umbracoContext, preview);
|
|
|
|
|
var node = vars == null
|
|
|
|
|
? xml.SelectSingleNode(xpath)
|
|
|
|
|
: xml.SelectSingleNode(xpath, vars);
|
|
|
|
|
return ConvertToDocument(node);
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual IEnumerable<IPublishedContent> GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars)
|
2013-02-05 06:31:13 -01:00
|
|
|
{
|
|
|
|
|
if (xpath == null) throw new ArgumentNullException("xpath");
|
|
|
|
|
if (string.IsNullOrWhiteSpace(xpath)) return Enumerable.Empty<IPublishedContent>();
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
var xml = GetXml(umbracoContext, preview);
|
2013-04-10 12:49:45 -02:00
|
|
|
var nodes = vars == null
|
|
|
|
|
? xml.SelectNodes(xpath)
|
|
|
|
|
: xml.SelectNodes(xpath, vars);
|
|
|
|
|
return ConvertToDocuments(nodes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual IEnumerable<IPublishedContent> GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars)
|
|
|
|
|
{
|
|
|
|
|
if (xpath == null) throw new ArgumentNullException("xpath");
|
|
|
|
|
|
|
|
|
|
var xml = GetXml(umbracoContext, preview);
|
2013-02-05 06:31:13 -01:00
|
|
|
var nodes = vars == null
|
|
|
|
|
? xml.SelectNodes(xpath)
|
|
|
|
|
: xml.SelectNodes(xpath, vars);
|
|
|
|
|
return ConvertToDocuments(nodes);
|
|
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual bool HasContent(UmbracoContext umbracoContext, bool preview)
|
2012-09-28 07:04:33 -02:00
|
|
|
{
|
2013-03-31 18:47:25 -02:00
|
|
|
var xml = GetXml(umbracoContext, preview);
|
2012-10-11 02:30:48 +05:00
|
|
|
if (xml == null)
|
|
|
|
|
return false;
|
|
|
|
|
var node = xml.SelectSingleNode(XPathStrings.RootDocuments);
|
2012-10-05 08:00:06 -02:00
|
|
|
return node != null;
|
2012-09-28 07:04:33 -02:00
|
|
|
}
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
public virtual XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview)
|
2013-04-03 11:19:10 -02:00
|
|
|
{
|
2013-03-31 18:47:25 -02:00
|
|
|
var xml = GetXml(umbracoContext, preview);
|
2013-04-03 11:19:10 -02:00
|
|
|
return xml.CreateNavigator();
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#endregion
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#region Legacy Xml
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-05-03 20:09:15 -02:00
|
|
|
static readonly ConditionalWeakTable<UmbracoContext, PreviewContent> PreviewContentCache
|
|
|
|
|
= new ConditionalWeakTable<UmbracoContext, PreviewContent>();
|
|
|
|
|
|
|
|
|
|
private Func<UmbracoContext, bool, XmlDocument> _xmlDelegate;
|
2013-03-19 17:51:55 -01:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets/sets the delegate used to retreive the Xml content, generally the setter is only used for unit tests
|
|
|
|
|
/// and by default if it is not set will use the standard delegate which ONLY works when in the context an Http Request
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// If not defined, we will use the standard delegate which ONLY works when in the context an Http Request
|
|
|
|
|
/// mostly because the 'content' object heavily relies on HttpContext, SQL connections and a bunch of other stuff
|
|
|
|
|
/// that when run inside of a unit test fails.
|
|
|
|
|
/// </remarks>
|
2013-05-03 20:09:15 -02:00
|
|
|
internal Func<UmbracoContext, bool, XmlDocument> GetXmlDelegate
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2013-05-03 20:09:15 -02:00
|
|
|
return _xmlDelegate ?? (_xmlDelegate = (context, preview) =>
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
|
|
|
|
if (preview)
|
|
|
|
|
{
|
2013-05-03 20:09:15 -02:00
|
|
|
var previewContent = PreviewContentCache.GetOrCreateValue(context); // will use the ctor with no parameters
|
|
|
|
|
previewContent.EnsureInitialized(context.UmbracoUser, StateHelper.Cookies.Preview.GetValue(), true, () =>
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
2013-05-03 20:09:15 -02:00
|
|
|
if (previewContent.ValidPreviewSet)
|
|
|
|
|
previewContent.LoadPreviewset();
|
|
|
|
|
});
|
|
|
|
|
if (previewContent.ValidPreviewSet)
|
|
|
|
|
return previewContent.XmlContent;
|
2013-03-19 17:51:55 -01:00
|
|
|
}
|
|
|
|
|
return content.Instance.XmlContent;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_xmlDelegate = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-31 18:47:25 -02:00
|
|
|
internal XmlDocument GetXml(UmbracoContext umbracoContext, bool preview)
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
2013-05-03 20:09:15 -02:00
|
|
|
return GetXmlDelegate(umbracoContext, preview);
|
2013-03-19 17:51:55 -01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region XPathQuery
|
|
|
|
|
|
|
|
|
|
static readonly char[] SlashChar = new[] { '/' };
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-02-14 16:23:56 -01:00
|
|
|
protected string CreateXpathQuery(int startNodeId, string path, bool hideTopLevelNodeFromPath, out IEnumerable<XPathVariable> vars)
|
2012-07-20 01:04:35 +06:00
|
|
|
{
|
|
|
|
|
string xpath;
|
2013-02-14 16:23:56 -01:00
|
|
|
vars = null;
|
2012-07-20 01:04:35 +06:00
|
|
|
|
|
|
|
|
if (path == string.Empty || path == "/")
|
|
|
|
|
{
|
|
|
|
|
// if url is empty
|
|
|
|
|
if (startNodeId > 0)
|
|
|
|
|
{
|
2012-10-05 08:00:06 -02:00
|
|
|
// if in a domain then use the root node of the domain
|
2013-02-05 06:31:13 -01:00
|
|
|
xpath = string.Format(XPathStringsDefinition.Root + XPathStrings.DescendantDocumentById, startNodeId);
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// if not in a domain - what is the default page?
|
2012-09-30 12:25:47 -02:00
|
|
|
// let's say it is the first one in the tree, if any -- order by sortOrder
|
2012-10-01 06:55:44 -02:00
|
|
|
|
|
|
|
|
// but!
|
|
|
|
|
// umbraco does not consistently guarantee that sortOrder starts with 0
|
|
|
|
|
// so the one that we want is the one with the smallest sortOrder
|
|
|
|
|
// read http://stackoverflow.com/questions/1128745/how-can-i-use-xpath-to-find-the-minimum-value-of-an-attribute-in-a-set-of-elemen
|
|
|
|
|
|
|
|
|
|
// so that one does not work, because min(@sortOrder) maybe 1
|
|
|
|
|
// xpath = "/root/*[@isDoc and @sortOrder='0']";
|
|
|
|
|
|
|
|
|
|
// and we can't use min() because that's XPath 2.0
|
|
|
|
|
// that one works
|
2012-10-05 08:00:06 -02:00
|
|
|
xpath = XPathStrings.RootDocumentWithLowestSortOrder;
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// if url is not empty, then use it to try lookup a matching page
|
|
|
|
|
var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
var xpathBuilder = new StringBuilder();
|
|
|
|
|
int partsIndex = 0;
|
2013-02-14 16:23:56 -01:00
|
|
|
List<XPathVariable> varsList = null;
|
2012-07-20 01:04:35 +06:00
|
|
|
|
|
|
|
|
if (startNodeId == 0)
|
|
|
|
|
{
|
2012-10-05 08:00:06 -02:00
|
|
|
if (hideTopLevelNodeFromPath)
|
|
|
|
|
xpathBuilder.Append(XPathStrings.RootDocuments); // first node is not in the url
|
2012-10-04 21:37:11 +05:00
|
|
|
else
|
2013-02-05 06:31:13 -01:00
|
|
|
xpathBuilder.Append(XPathStringsDefinition.Root);
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-02-05 06:31:13 -01:00
|
|
|
xpathBuilder.AppendFormat(XPathStringsDefinition.Root + XPathStrings.DescendantDocumentById, startNodeId);
|
2012-10-01 12:20:51 -02:00
|
|
|
// always "hide top level" when there's a domain
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
2012-10-05 08:00:06 -02:00
|
|
|
|
2012-07-20 01:04:35 +06:00
|
|
|
while (partsIndex < urlParts.Length)
|
2012-10-04 21:37:11 +05:00
|
|
|
{
|
2013-02-14 16:23:56 -01:00
|
|
|
var part = urlParts[partsIndex++];
|
|
|
|
|
if (part.Contains('\'') || part.Contains('"'))
|
|
|
|
|
{
|
|
|
|
|
// use vars, escaping gets ugly pretty quickly
|
|
|
|
|
varsList = varsList ?? new List<XPathVariable>();
|
|
|
|
|
var varName = string.Format("var{0}", partsIndex);
|
|
|
|
|
varsList.Add(new XPathVariable(varName, part));
|
2013-02-19 13:58:16 -01:00
|
|
|
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlNameVar, varName);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlName, part);
|
|
|
|
|
|
2013-02-14 16:23:56 -01:00
|
|
|
}
|
2012-10-04 21:37:11 +05:00
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
|
|
|
|
xpath = xpathBuilder.ToString();
|
2013-02-19 13:58:16 -01:00
|
|
|
if (varsList != null)
|
|
|
|
|
vars = varsList.ToArray();
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return xpath;
|
|
|
|
|
}
|
2013-03-19 17:51:55 -01:00
|
|
|
|
|
|
|
|
#endregion
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
}
|