2012-07-20 01:04:35 +06:00
|
|
|
using System;
|
2012-10-04 01:31:08 +05:00
|
|
|
using System.Collections.Generic;
|
2012-07-20 01:04:35 +06:00
|
|
|
using System.Text;
|
|
|
|
|
using System.Xml;
|
2012-09-08 13:22:45 +07:00
|
|
|
using System.Xml.Linq;
|
2012-08-10 13:08:47 +06:00
|
|
|
using Umbraco.Core.Models;
|
|
|
|
|
using Umbraco.Web.Routing;
|
2012-07-20 01:04:35 +06:00
|
|
|
using umbraco;
|
2012-08-10 13:08:47 +06:00
|
|
|
using umbraco.NodeFactory;
|
2012-07-20 01:04:35 +06:00
|
|
|
using umbraco.interfaces;
|
2012-09-13 12:19:56 +07:00
|
|
|
using System.Linq;
|
2012-07-20 01:04:35 +06:00
|
|
|
|
|
|
|
|
namespace Umbraco.Web
|
|
|
|
|
{
|
2012-08-10 13:38:02 +06:00
|
|
|
/// <summary>
|
2012-09-08 13:22:45 +07:00
|
|
|
/// An IPublishedContentStore which uses the Xml cache system to return data
|
2012-07-20 01:04:35 +06:00
|
|
|
/// </summary>
|
2012-09-11 05:58:16 +07:00
|
|
|
internal class DefaultPublishedContentStore : IPublishedContentStore
|
2012-10-05 08:00:06 -02:00
|
|
|
{
|
|
|
|
|
#region XPath Strings
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-10-05 08:00:06 -02:00
|
|
|
class XPathStringsDefinition
|
|
|
|
|
{
|
2012-10-05 08:23:43 -02:00
|
|
|
public int Version { get; private set; }
|
|
|
|
|
|
2012-10-05 08:00:06 -02:00
|
|
|
public string Root { get { return "/root"; } }
|
|
|
|
|
public string RootDocuments { get; private set; }
|
|
|
|
|
public string DescendantDocumentById { get; private set; }
|
|
|
|
|
public string DescendantDocumentByAlias { get; private set; }
|
|
|
|
|
public string ChildDocumentByUrlName { get; private set; }
|
|
|
|
|
public string RootDocumentWithLowestSortOrder { get; private set; }
|
|
|
|
|
|
|
|
|
|
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}]";
|
|
|
|
|
DescendantDocumentByAlias = "//node[("
|
|
|
|
|
+ "contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',{0},')"
|
|
|
|
|
+ " or contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',/{0},'"
|
|
|
|
|
+ ")]";
|
|
|
|
|
ChildDocumentByUrlName = "/node [@urlName='{0}']";
|
|
|
|
|
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}]";
|
|
|
|
|
DescendantDocumentByAlias = "//* [@isDoc and ("
|
|
|
|
|
+ "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')"
|
|
|
|
|
+ " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')"
|
|
|
|
|
+ ")]";
|
|
|
|
|
ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']";
|
|
|
|
|
RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception(string.Format("Unsupported Xml schema version '{0}').", version));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-05 08:23:43 -02:00
|
|
|
static XPathStringsDefinition XPathStringsValue = null;
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
int version = UmbracoSettings.UseLegacyXmlSchema ? 0 : 1;
|
|
|
|
|
if (XPathStringsValue == null || XPathStringsValue.Version != version)
|
|
|
|
|
XPathStringsValue = new XPathStringsDefinition(version);
|
|
|
|
|
return XPathStringsValue;
|
|
|
|
|
}
|
2012-10-05 08:00:06 -02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
private IPublishedContent ConvertToDocument(XmlNode xmlNode)
|
2012-08-10 13:08:47 +06:00
|
|
|
{
|
|
|
|
|
if (xmlNode == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2012-10-02 01:35:39 +05:00
|
|
|
return new Models.XmlPublishedContent(xmlNode);
|
2012-08-10 13:08:47 +06:00
|
|
|
}
|
2012-08-10 13:38:02 +06:00
|
|
|
|
2012-10-02 01:35:39 +05:00
|
|
|
public virtual IPublishedContent GetDocumentById(UmbracoContext umbracoContext, int nodeId)
|
2012-09-08 11:59:01 +07:00
|
|
|
{
|
|
|
|
|
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
|
|
|
|
|
|
|
|
|
return ConvertToDocument(GetXml(umbracoContext).GetElementById(nodeId.ToString()));
|
|
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-10-04 01:31:08 +05:00
|
|
|
public IEnumerable<IPublishedContent> GetRootDocuments(UmbracoContext umbracoContext)
|
|
|
|
|
{
|
2012-10-05 08:00:06 -02:00
|
|
|
return (from XmlNode x in GetXml(umbracoContext).SelectNodes(XPathStrings.RootDocuments) select ConvertToDocument(x)).ToList();
|
2012-10-04 01:31:08 +05:00
|
|
|
}
|
|
|
|
|
|
2012-10-02 01:35:39 +05:00
|
|
|
public IPublishedContent GetDocumentByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null)
|
2012-07-20 01:04:35 +06:00
|
|
|
{
|
2012-09-08 11:59:01 +07:00
|
|
|
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
|
|
|
|
if (route == null) throw new ArgumentNullException("route");
|
|
|
|
|
|
2012-08-09 07:41:13 +06:00
|
|
|
//set the default to be what is in the settings
|
2012-09-24 11:36:25 -02:00
|
|
|
hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath;
|
2012-08-09 07:41:13 +06:00
|
|
|
|
|
|
|
|
//the route always needs to be lower case because we only store the urlName attribute in lower case
|
|
|
|
|
route = route.ToLowerInvariant();
|
|
|
|
|
|
2012-09-24 11:36:25 -02:00
|
|
|
int pos = route.IndexOf('/');
|
|
|
|
|
string path = pos == 0 ? route : route.Substring(pos);
|
|
|
|
|
int startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos));
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-08-09 07:41:13 +06:00
|
|
|
var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode.Value);
|
2012-08-10 13:08:47 +06:00
|
|
|
|
2012-09-13 12:19:56 +07:00
|
|
|
//check if we can find the node in our xml cache
|
|
|
|
|
var found = GetXml(umbracoContext).SelectSingleNode(xpath);
|
|
|
|
|
|
2012-09-24 11:36:25 -02: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 NiceUrlProvider).
|
|
|
|
|
if (found == null && hideTopLevelNode.Value && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
2012-09-13 12:19:56 +07:00
|
|
|
{
|
|
|
|
|
xpath = CreateXpathQuery(startNodeId, path, false);
|
|
|
|
|
found = GetXml(umbracoContext).SelectSingleNode(xpath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ConvertToDocument(found);
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
|
2012-10-02 01:35:39 +05:00
|
|
|
public IPublishedContent GetDocumentByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias)
|
2012-07-20 01:04:35 +06:00
|
|
|
{
|
2012-09-08 11:59:01 +07:00
|
|
|
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
|
|
|
|
if (alias == null) throw new ArgumentNullException("alias");
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-09-08 11:59:01 +07:00
|
|
|
// the alias may be "foo/bar" or "/foo/bar"
|
2012-07-20 01:04:35 +06:00
|
|
|
// there may be spaces as in "/foo/bar, /foo/nil"
|
|
|
|
|
// these should probably be taken care of earlier on
|
|
|
|
|
|
|
|
|
|
alias = alias.TrimStart('/');
|
|
|
|
|
var xpathBuilder = new StringBuilder();
|
2012-10-05 08:00:06 -02:00
|
|
|
xpathBuilder.Append(XPathStrings.Root);
|
2012-10-04 21:37:11 +05:00
|
|
|
|
2012-10-05 08:00:06 -02:00
|
|
|
if (rootNodeId > 0)
|
|
|
|
|
xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentById, rootNodeId);
|
|
|
|
|
xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentByAlias, alias);
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-10-05 08:00:06 -02:00
|
|
|
var xpath = xpathBuilder.ToString();
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-08-10 13:38:02 +06:00
|
|
|
return ConvertToDocument(GetXml(umbracoContext).SelectSingleNode(xpath));
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
|
2012-09-28 07:04:33 -02:00
|
|
|
public bool HasContent(UmbracoContext umbracoContext)
|
|
|
|
|
{
|
2012-10-11 02:30:48 +05:00
|
|
|
var xml = GetXml(umbracoContext);
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2012-09-08 11:59:01 +07:00
|
|
|
XmlDocument GetXml(UmbracoContext umbracoContext)
|
|
|
|
|
{
|
|
|
|
|
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-08-10 13:38:02 +06:00
|
|
|
return umbracoContext.GetXml();
|
2012-09-08 11:59:01 +07:00
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2012-09-08 11:59:01 +07:00
|
|
|
static readonly char[] SlashChar = new char[] { '/' };
|
2012-07-20 01:04:35 +06:00
|
|
|
|
|
|
|
|
protected string CreateXpathQuery(int startNodeId, string path, bool hideTopLevelNodeFromPath)
|
|
|
|
|
{
|
|
|
|
|
string xpath;
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
xpath = string.Format(XPathStrings.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;
|
|
|
|
|
|
|
|
|
|
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
|
2012-10-05 08:00:06 -02:00
|
|
|
xpathBuilder.Append(XPathStrings.Root);
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2012-10-05 08:00:06 -02:00
|
|
|
xpathBuilder.AppendFormat(XPathStrings.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
|
|
|
{
|
2012-10-05 08:00:06 -02:00
|
|
|
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlName, urlParts[partsIndex++]);
|
2012-10-04 21:37:11 +05:00
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
|
|
|
|
xpath = xpathBuilder.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return xpath;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|