makes SystemDirectories and SystemFiles public.

moves ResolveUrlsFromTextString to TemplateUtilities class and obsoletes other ones.
Fixes an issue if content cache is stored in codegen folder and not in med trust.
Updates the XmlDocument (IDocumentProperty) to always return a value with parsed {localLink} and resolved Urls and cache them so the parsing only happens once.
This commit is contained in:
Shannon Deminick
2012-09-29 11:11:48 +07:00
parent 11fd6553e9
commit 1aebce7ad6
13 changed files with 158 additions and 169 deletions

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Core.Dynamics
/// </summary>
public class DynamicDocument : DynamicObject
{
private readonly IDocument _backingItem;
private readonly IDocument _document;
private DynamicDocumentList _cachedChildren;
private readonly ConcurrentDictionary<string, object> _cachedMemberOutput = new ConcurrentDictionary<string, object>();
@@ -28,7 +28,7 @@ namespace Umbraco.Core.Dynamics
public DynamicDocument(IDocument node)
{
if (node == null) throw new ArgumentNullException("node");
_backingItem = node;
_document = node;
}
/// <summary>
@@ -107,82 +107,9 @@ namespace Umbraco.Core.Dynamics
return DynamicDocumentWalker.Sibling(this, nodeTypeAlias);
}
//public DynamicDocumentList XPath(string xPath)
//{
// //if this DN was initialized with an underlying NodeFactory.Node
// if (n != null && n.Type == DynamicBackingItemType.Content)
// {
// //get the underlying xml content
// XmlDocument doc = umbraco.content.Instance.XmlContent;
// if (doc != null)
// {
// //get n as a XmlNode (to be used as the context point for the xpath)
// //rather than just applying the xPath to the root node, this lets us use .. etc from the DynamicNode point
// //in test mode, n.Id is 0, let this always succeed
// if (n.Id == 0)
// {
// List<DynamicNode> selfList = new List<DynamicNode>() { this };
// return new DynamicDocumentList(selfList);
// }
// XmlNode node = doc.SelectSingleNode(string.Format("//*[@id='{0}']", n.Id));
// if (node != null)
// {
// //got the current node (within the XmlContent instance)
// XmlNodeList nodes = node.SelectNodes(xPath);
// if (nodes.Count > 0)
// {
// //we got some resulting nodes
// List<NodeFactory.Node> nodeFactoryNodeList = new List<NodeFactory.Node>();
// //attempt to convert each node in the set to a NodeFactory.Node
// foreach (XmlNode nodeXmlNode in nodes)
// {
// try
// {
// nodeFactoryNodeList.Add(new NodeFactory.Node(nodeXmlNode));
// }
// catch (Exception) { } //swallow the exceptions - the returned nodes might not be full nodes, e.g. property
// }
// //Wanted to do this, but because we return DynamicDocumentList here, the only
// //common parent class is DynamicObject
// //maybe some future refactoring will solve this?
// //if (nodeFactoryNodeList.Count == 0)
// //{
// // //if the xpath resulted in a node set, but none of them could be converted to NodeFactory.Node
// // XElement xElement = XElement.Parse(node.OuterXml);
// // //return
// // return new DynamicXml(xElement);
// //}
// //convert the NodeFactory nodelist to IEnumerable<DynamicNode> and return it as a DynamicDocumentList
// return new DynamicDocumentList(nodeFactoryNodeList.ConvertAll(nfNode => new DynamicNode((INode)nfNode)));
// }
// else
// {
// // XPath returned no nodes, return an empty DynamicDocumentList
// return new DynamicDocumentList();
// }
// }
// else
// {
// throw new NullReferenceException("Couldn't locate the DynamicNode within the XmlContent");
// }
// }
// else
// {
// throw new NullReferenceException("umbraco.content.Instance.XmlContent is null");
// }
// }
// else
// {
// throw new NullReferenceException("DynamicNode wasn't initialized with an underlying NodeFactory.Node");
// }
//}
public bool HasProperty(string name)
{
if (_backingItem != null)
if (_document != null)
{
try
{
@@ -240,7 +167,7 @@ namespace Umbraco.Core.Dynamics
{
try
{
result = ExecuteExtensionMethod(args, binder.Name, false);
result = ExecuteExtensionMethod(args, binder.Name);
return true;
}
catch (TargetInvocationException)
@@ -269,7 +196,7 @@ namespace Umbraco.Core.Dynamics
}
private object ExecuteExtensionMethod(object[] args, string name, bool argsContainsThis)
private object ExecuteExtensionMethod(object[] args, string name)
{
object result = null;
@@ -350,7 +277,7 @@ namespace Umbraco.Core.Dynamics
protected virtual Attempt<object> TryGetChildrenByAlias(GetMemberBinder binder)
{
var filteredTypeChildren = _backingItem.Children
var filteredTypeChildren = _document.Children
.Where(x => x.DocumentTypeAlias.InvariantEquals(binder.Name) || x.DocumentTypeAlias.MakePluralName().InvariantEquals(binder.Name))
.ToArray();
if (filteredTypeChildren.Any())
@@ -403,7 +330,7 @@ namespace Umbraco.Core.Dynamics
var result = userProperty.Value;
if (_backingItem.DocumentTypeAlias == null && userProperty.Alias == null)
if (_document.DocumentTypeAlias == null && userProperty.Alias == null)
{
throw new InvalidOperationException("No node alias or property alias available. Unable to look up the datatype of the property you are trying to fetch.");
}
@@ -496,7 +423,7 @@ namespace Umbraco.Core.Dynamics
/// <returns></returns>
private PropertyResult GetReflectedProperty(string alias)
{
return GetPropertyInternal(alias, _backingItem, false);
return GetPropertyInternal(alias, _document, false);
}
/// <summary>
@@ -509,15 +436,15 @@ namespace Umbraco.Core.Dynamics
{
if (!recursive)
{
return GetPropertyInternal(alias, _backingItem);
return GetPropertyInternal(alias, _document);
}
var context = this;
var prop = GetPropertyInternal(alias, _backingItem);
var prop = GetPropertyInternal(alias, _document);
while (prop == null || !prop.HasValue())
{
context = context.Parent;
if (context == null) break;
prop = context.GetPropertyInternal(alias, context._backingItem);
prop = context.GetPropertyInternal(alias, context._document);
}
return prop;
}
@@ -830,7 +757,7 @@ namespace Umbraco.Core.Dynamics
}
internal DynamicDocumentList Descendants(Func<IDocument, bool> func)
{
var flattenedNodes = this._backingItem.Children.Map(func, (IDocument n) => n.Children);
var flattenedNodes = this._document.Children.Map(func, (IDocument n) => n.Children);
return new DynamicDocumentList(flattenedNodes.ToList().ConvertAll(dynamicBackingItem => new DynamicDocument(dynamicBackingItem)));
}
public DynamicDocumentList DescendantsOrSelf(int level)
@@ -847,14 +774,14 @@ namespace Umbraco.Core.Dynamics
}
internal DynamicDocumentList DescendantsOrSelf(Func<IDocument, bool> func)
{
if (this._backingItem != null)
if (this._document != null)
{
var thisNode = new List<IDocument>();
if (func(this._backingItem))
if (func(this._document))
{
thisNode.Add(this._backingItem);
thisNode.Add(this._document);
}
var flattenedNodes = this._backingItem.Children.Map(func, (IDocument n) => n.Children);
var flattenedNodes = this._document.Children.Map(func, (IDocument n) => n.Children);
return new DynamicDocumentList(thisNode.Concat(flattenedNodes).ToList().ConvertAll(dynamicBackingItem => new DynamicDocument(dynamicBackingItem)));
}
return new DynamicDocumentList(Enumerable.Empty<IDocument>());
@@ -906,11 +833,11 @@ namespace Umbraco.Core.Dynamics
{
get
{
if (_backingItem.Parent != null)
if (_document.Parent != null)
{
return new DynamicDocument(_backingItem.Parent);
return new DynamicDocument(_document.Parent);
}
if (_backingItem != null && _backingItem.Id == 0)
if (_document != null && _document.Id == 0)
{
return this;
}
@@ -920,17 +847,17 @@ namespace Umbraco.Core.Dynamics
public int TemplateId
{
get { return _backingItem.TemplateId; }
get { return _document.TemplateId; }
}
public int SortOrder
{
get { return _backingItem.SortOrder; }
get { return _document.SortOrder; }
}
public string Name
{
get { return _backingItem.Name; }
get { return _document.Name; }
}
public bool Visible
{
@@ -948,66 +875,66 @@ namespace Umbraco.Core.Dynamics
public string UrlName
{
get { return _backingItem.UrlName; }
get { return _document.UrlName; }
}
public string DocumentTypeAlias
{
get { return _backingItem.DocumentTypeAlias; }
get { return _document.DocumentTypeAlias; }
}
public string WriterName
{
get { return _backingItem.WriterName; }
get { return _document.WriterName; }
}
public string CreatorName
{
get { return _backingItem.CreatorName; }
get { return _document.CreatorName; }
}
public int WriterId
{
get { return _backingItem.WriterId; }
get { return _document.WriterId; }
}
public int CreatorId
{
get { return _backingItem.CreatorId; }
get { return _document.CreatorId; }
}
public string Path
{
get { return _backingItem.Path; }
get { return _document.Path; }
}
public DateTime CreateDate
{
get { return _backingItem.CreateDate; }
get { return _document.CreateDate; }
}
public int Id
{
get { return _backingItem.Id; }
get { return _document.Id; }
}
public DateTime UpdateDate
{
get { return _backingItem.UpdateDate; }
get { return _document.UpdateDate; }
}
public Guid Version
{
get { return _backingItem.Version; }
get { return _document.Version; }
}
public int Level
{
get { return _backingItem.Level; }
get { return _document.Level; }
}
public IEnumerable<IDocumentProperty> Properties
{
get { return _backingItem.Properties; }
get { return _document.Properties; }
}
public IEnumerable<DynamicDocument> Children
@@ -1016,15 +943,15 @@ namespace Umbraco.Core.Dynamics
{
if (_cachedChildren == null)
{
var children = _backingItem.Children;
var children = _document.Children;
//testing, think this must be a special case for the root node ?
if (!children.Any() && _backingItem.Id == 0)
if (!children.Any() && _document.Id == 0)
{
_cachedChildren = new DynamicDocumentList(new List<DynamicDocument> { new DynamicDocument(this._backingItem) });
_cachedChildren = new DynamicDocumentList(new List<DynamicDocument> { new DynamicDocument(this._document) });
}
else
{
_cachedChildren = new DynamicDocumentList(_backingItem.Children.Select(x => new DynamicDocument(x)));
_cachedChildren = new DynamicDocumentList(_document.Children.Select(x => new DynamicDocument(x)));
}
}
return _cachedChildren;

View File

@@ -7,7 +7,7 @@ namespace Umbraco.Core.Dynamics
{
internal class PropertyResult : IDocumentProperty, IHtmlString
{
public PropertyResult(IDocumentProperty source, PropertyResultType type)
internal PropertyResult(IDocumentProperty source, PropertyResultType type)
{
if (source == null) throw new ArgumentNullException("source");
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Dynamics
Version = source.Version;
PropertyType = type;
}
public PropertyResult(string alias, object value, Guid version, PropertyResultType type)
internal PropertyResult(string alias, object value, Guid version, PropertyResultType type)
{
if (alias == null) throw new ArgumentNullException("alias");
if (value == null) throw new ArgumentNullException("value");
@@ -32,10 +32,16 @@ namespace Umbraco.Core.Dynamics
public string Alias { get; private set; }
public object Value { get; private set; }
public string ValueAsString
/// <summary>
/// Returns the value as a string output, this is used in the final rendering process of a property
/// </summary>
internal string ValueAsString
{
get { return Value == null ? "" : Convert.ToString(Value); }
get
{
return Value == null ? "" : Convert.ToString(Value);
}
}
public Guid Version { get; private set; }

View File

@@ -9,12 +9,14 @@ using System.Configuration;
using System.Web;
using System.Text.RegularExpressions;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
namespace Umbraco.Core.IO
{
internal static class IOHelper
{
private static string _rootDir = "";
// static compiled regex for faster performance
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
@@ -49,46 +51,33 @@ namespace Umbraco.Core.IO
return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root);
}
[Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")]
public static string ResolveUrlsFromTextString(string text)
{
if (UmbracoSettings.ResolveUrlsFromTextString)
{
var sw = new Stopwatch();
sw.Start();
Debug.WriteLine("Start: " + sw.ElapsedMilliseconds);
// find all relative urls (ie. urls that contain ~)
var tags =
ResolveUrlPattern.Matches(text);
Debug.WriteLine("After regex: " + sw.ElapsedMilliseconds);
foreach (Match tag in tags)
{
Debug.WriteLine("-- inside regex: " + sw.ElapsedMilliseconds);
string url = "";
if (tag.Groups[1].Success)
url = tag.Groups[1].Value;
// The richtext editor inserts a slash in front of the url. That's why we need this little fix
// if (url.StartsWith("/"))
// text = text.Replace(url, ResolveUrl(url.Substring(1)));
// else
if (!String.IsNullOrEmpty(url))
{
Debug.WriteLine("---- before resolve: " + sw.ElapsedMilliseconds);
string resolvedUrl = (url.Substring(0, 1) == "/") ? ResolveUrl(url.Substring(1)) : ResolveUrl(url);
Debug.WriteLine("---- after resolve: " + sw.ElapsedMilliseconds);
Debug.WriteLine("---- before replace: " + sw.ElapsedMilliseconds);
text = text.Replace(url, resolvedUrl);
Debug.WriteLine("---- after replace: " + sw.ElapsedMilliseconds);
}
}
Debug.WriteLine("total: " + sw.ElapsedMilliseconds);
sw.Stop();
Debug.WriteLine("Resolve Urls", sw.ElapsedMilliseconds.ToString());
{
using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
{
// find all relative urls (ie. urls that contain ~)
var tags = ResolveUrlPattern.Matches(text);
LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count);
foreach (Match tag in tags)
{
string url = "";
if (tag.Groups[1].Success)
url = tag.Groups[1].Value;
// The richtext editor inserts a slash in front of the url. That's why we need this little fix
// if (url.StartsWith("/"))
// text = text.Replace(url, ResolveUrl(url.Substring(1)));
// else
if (!String.IsNullOrEmpty(url))
{
string resolvedUrl = (url.Substring(0, 1) == "/") ? ResolveUrl(url.Substring(1)) : ResolveUrl(url);
text = text.Replace(url, resolvedUrl);
}
}
}
}
return text;
}

View File

@@ -10,7 +10,7 @@ using System.IO;
namespace Umbraco.Core.IO
{
//all paths has a starting but no trailing /
internal class SystemDirectories
public class SystemDirectories
{
public static string Bin
{

View File

@@ -8,7 +8,7 @@ using System.Web;
namespace Umbraco.Core.IO
{
internal class SystemFiles
public class SystemFiles
{
public static string AccessXml
@@ -96,7 +96,7 @@ namespace Umbraco.Core.IO
{
get
{
if (ContentCacheXmlIsEphemeral)
if (ContentCacheXmlIsEphemeral && SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted)
{
return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config");
}
@@ -104,7 +104,7 @@ namespace Umbraco.Core.IO
}
}
public static bool ContentCacheXmlIsEphemeral
internal static bool ContentCacheXmlIsEphemeral
{
get
{

View File

@@ -5,6 +5,7 @@ using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Web.Templates;
namespace Umbraco.Web.Models
{
@@ -25,9 +26,27 @@ namespace Umbraco.Web.Models
get { return _alias; }
}
private string _parsedValue;
/// <summary>
/// Returns the value of a property from the XML cache
/// </summary>
/// <remarks>
/// This ensures that the result has any {localLink} syntax parsed and that urls are resolved correctly.
/// This also ensures that the parsing is only done once as the result is cached in a private field of this object.
/// </remarks>
public object Value
{
get { return IOHelper.ResolveUrlsFromTextString(_value); }
get
{
if (_parsedValue == null)
{
_parsedValue = TemplateUtilities.ResolveUrlsFromTextString(
TemplateUtilities.ParseInternalLinks(
_value));
}
return _parsedValue;
}
}
public Guid Version

View File

@@ -71,10 +71,5 @@ namespace Umbraco.Web.Mvc
get { return _helper ?? (_helper = new UmbracoHelper(UmbracoContext)); }
}
public override void Write(object value)
{
base.Write(value);
}
}
}

View File

@@ -1,5 +1,10 @@
using System.Text.RegularExpressions;
using System;
using System.Text.RegularExpressions;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using umbraco;
using UmbracoSettings = Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Web.Templates
{
@@ -32,6 +37,47 @@ namespace Umbraco.Web.Templates
return text;
}
// static compiled regex for faster performance
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
/// <summary>
/// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path.
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
/// <remarks>
/// When used with a Virtual-Directory set-up, this would resolve all URLs correctly.
/// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs.
/// </remarks>
public static string ResolveUrlsFromTextString(string text)
{
if (UmbracoSettings.ResolveUrlsFromTextString)
{
using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
{
// find all relative urls (ie. urls that contain ~)
var tags = ResolveUrlPattern.Matches(text);
LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count);
foreach (Match tag in tags)
{
string url = "";
if (tag.Groups[1].Success)
url = tag.Groups[1].Value;
// The richtext editor inserts a slash in front of the url. That's why we need this little fix
// if (url.StartsWith("/"))
// text = text.Replace(url, ResolveUrl(url.Substring(1)));
// else
if (!String.IsNullOrEmpty(url))
{
string resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : ResolveUrl(url);
text = text.Replace(url, resolvedUrl);
}
}
}
}
return text;
}
}
}

View File

@@ -17,6 +17,7 @@ using System.Web.UI.WebControls;
using System.Xml;
using System.Xml.Xsl;
using Umbraco.Core;
using Umbraco.Web.Templates;
using umbraco.BusinessLogic;
using umbraco.BusinessLogic.Utils;
using umbraco.cms.businesslogic.macro;
@@ -757,7 +758,7 @@ namespace umbraco
// Do transformation
UmbracoContext.Current.Trace.Write("umbracoMacro", "Before performing transformation");
xslt.Transform(macroXML.CreateNavigator(), xslArgs, tw);
return IOHelper.ResolveUrlsFromTextString(tw.ToString());
return TemplateUtilities.ResolveUrlsFromTextString(tw.ToString());
}
public static XsltArgumentList AddXsltExtensions()

View File

@@ -4,6 +4,7 @@ using System.Data;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
using Umbraco.Web.Templates;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.propertytype;
@@ -543,9 +544,11 @@ namespace umbraco.presentation.nodeFactory
get { return _alias; }
}
private string _parsedValue;
public string Value
{
get { return IO.IOHelper.ResolveUrlsFromTextString(_value); }
get { return _parsedValue ?? (_parsedValue = TemplateUtilities.ResolveUrlsFromTextString(_value)); }
}
public Guid Version

View File

@@ -1,6 +1,7 @@
using System;
using System.Xml;
using System.Xml.Serialization;
using Umbraco.Web.Templates;
using umbraco.interfaces;
namespace umbraco.NodeFactory
@@ -18,9 +19,10 @@ namespace umbraco.NodeFactory
get { return _alias; }
}
private string _parsedValue;
public string Value
{
get { return IO.IOHelper.ResolveUrlsFromTextString(_value); }
get { return _parsedValue ?? (_parsedValue = TemplateUtilities.ResolveUrlsFromTextString(_value)); }
}
public Guid Version

View File

@@ -8,6 +8,7 @@ using System.Web;
using System.Web.UI;
using System.Xml;
using Umbraco.Core.Macros;
using Umbraco.Web.Templates;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.property;
using umbraco.cms.businesslogic.web;
@@ -65,7 +66,7 @@ namespace umbraco.presentation.templateControls
// handle text before/after
xsltTransformedOutput = AddBeforeAfterText(xsltTransformedOutput, helper.FindAttribute(item.LegacyAttributes, "insertTextBefore"), helper.FindAttribute(item.LegacyAttributes, "insertTextAfter"));
string finalResult = xsltTransformedOutput.Trim().Length > 0 ? xsltTransformedOutput : GetEmptyText(item);
writer.Write(IOHelper.ResolveUrlsFromTextString(finalResult));
writer.Write(TemplateUtilities.ResolveUrlsFromTextString(finalResult));
}
catch (Exception renderException)
{

View File

@@ -36,7 +36,7 @@ namespace umbraco.IO
return Umbraco.Core.IO.IOHelper.ResolveUrl(virtualPath);
}
[Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")]
public static string ResolveUrlsFromTextString(string text)
{
return Umbraco.Core.IO.IOHelper.ResolveUrlsFromTextString(text);