using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Threading;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Logging;
using Umbraco.Core.Strings;
using umbraco.interfaces;
using System.Collections;
using System.Reflection;
using umbraco.cms.businesslogic.web;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.property;
using umbraco.BusinessLogic;
using umbraco.DataLayer;
using umbraco.cms.businesslogic;
using System.Xml;
using System.Xml.Linq;
using umbraco.cms.businesslogic.media;
using umbraco.MacroEngines.Library;
using umbraco.BusinessLogic.Utils;
using Examine;
using Examine.SearchCriteria;
using Examine.LuceneEngine.SearchCriteria;
namespace umbraco.MacroEngines
{
public class DynamicNode : DynamicObject, INode
{
///
/// This callback is used only so we can set it dynamically for use in unit tests
///
internal static Func GetDataTypeCallback = (docTypeAlias, propertyAlias) =>
ContentType.GetDataType(docTypeAlias, propertyAlias);
#region consts
// these are private readonlys as const can't be Guids
private readonly Guid DATATYPE_YESNO_GUID = new Guid(Constants.PropertyEditors.TrueFalse);
private readonly Guid DATATYPE_TINYMCE_GUID = new Guid(Constants.PropertyEditors.TinyMCEv3);
private readonly Guid DATATYPE_DATETIMEPICKER_GUID = new Guid(Constants.PropertyEditors.DateTime);
private readonly Guid DATATYPE_DATEPICKER_GUID = new Guid(Constants.PropertyEditors.Date);
//private readonly Guid DATATYPE_INTEGER_GUID = new Guid("1413afcb-d19a-4173-8e9a-68288d2a73b8");
#endregion
private DynamicNodeList _cachedChildren;
private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary();
private readonly ConcurrentDictionary _cachedProperties = new ConcurrentDictionary();
internal readonly DynamicBackingItem n;
public DynamicNodeList ownerList;
public DynamicNode(DynamicBackingItem n)
{
if (n != null)
this.n = n;
else
throw new ArgumentNullException("n", "A node must be provided to make a dynamic instance");
}
public DynamicNode(int NodeId)
{
this.n = new DynamicBackingItem(NodeId);
}
public DynamicNode(int NodeId, DynamicBackingItemType ItemType)
{
this.n = new DynamicBackingItem(NodeId, ItemType);
}
public DynamicNode(string NodeId)
{
int DynamicBackingItemId = 0;
if (int.TryParse(NodeId, out DynamicBackingItemId))
{
this.n = new DynamicBackingItem(DynamicBackingItemId);
return;
}
throw new ArgumentException("Cannot instantiate a DynamicNode without an id");
}
public DynamicNode(INode Node)
{
this.n = new DynamicBackingItem(Node);
}
public DynamicNode(object NodeId)
{
int DynamicBackingItemId = 0;
if (int.TryParse(string.Format("{0}", NodeId), out DynamicBackingItemId))
{
this.n = new DynamicBackingItem(DynamicBackingItemId);
return;
}
throw new ArgumentException("Cannot instantiate a DynamicNode without an id");
}
public DynamicNode()
{
//Empty constructor for a special case with Generic Methods
}
public DynamicNode Up()
{
return DynamicNodeWalker.Up(this);
}
public DynamicNode Up(int number)
{
return DynamicNodeWalker.Up(this, number);
}
public DynamicNode Up(string nodeTypeAlias)
{
return DynamicNodeWalker.Up(this, nodeTypeAlias);
}
public DynamicNode Down()
{
return DynamicNodeWalker.Down(this);
}
public DynamicNode Down(int number)
{
return DynamicNodeWalker.Down(this, number);
}
public DynamicNode Down(string nodeTypeAlias)
{
return DynamicNodeWalker.Down(this, nodeTypeAlias);
}
public DynamicNode Next()
{
return DynamicNodeWalker.Next(this);
}
public DynamicNode Next(int number)
{
return DynamicNodeWalker.Next(this, number);
}
public DynamicNode Next(string nodeTypeAlias)
{
return DynamicNodeWalker.Next(this, nodeTypeAlias);
}
public DynamicNode Previous()
{
return DynamicNodeWalker.Previous(this);
}
public DynamicNode Previous(int number)
{
return DynamicNodeWalker.Previous(this, number);
}
public DynamicNode Previous(string nodeTypeAlias)
{
return DynamicNodeWalker.Previous(this, nodeTypeAlias);
}
public DynamicNode Sibling(int number)
{
return DynamicNodeWalker.Sibling(this, number);
}
public DynamicNode Sibling(string nodeTypeAlias)
{
return DynamicNodeWalker.Sibling(this, nodeTypeAlias);
}
public DynamicNodeList GetChildrenAsList
{
get
{
if (_cachedChildren == null)
{
List children = n.ChildrenAsList;
//testing
if (children.Count == 0 && n.Id == 0)
{
_cachedChildren = new DynamicNodeList(new List {this.n});
}
else
{
_cachedChildren = new DynamicNodeList(children);
}
}
return _cachedChildren;
}
}
public DynamicNodeList 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 selfList = new List() { this };
return new DynamicNodeList(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 nodeFactoryNodeList = new List();
//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 DynamicNodeList 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 and return it as a DynamicNodeList
return new DynamicNodeList(nodeFactoryNodeList.ConvertAll(nfNode => new DynamicNode((INode)nfNode)));
}
else
{
// XPath returned no nodes, return an empty DynamicNodeList
return new DynamicNodeList();
}
}
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 DynamicNodeList Search(string term, bool useWildCards = true, string searchProvider = null)
{
var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider;
if(!string.IsNullOrEmpty(searchProvider))
searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider];
var t = term.Escape().Value;
if (useWildCards)
t = term.MultipleCharacterWildcard().Value;
string luceneQuery = "+__Path:(" + this.Path.Replace("-", "\\-") + "*) +" + t;
var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery);
return Search(crit, searcher);
}
public DynamicNodeList SearchDescendants(string term, bool useWildCards = true, string searchProvider = null)
{
return Search(term, useWildCards, searchProvider);
}
public DynamicNodeList SearchChildren(string term, bool useWildCards = true, string searchProvider = null)
{
var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider;
if (!string.IsNullOrEmpty(searchProvider))
searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider];
var t = term.Escape().Value;
if (useWildCards)
t = term.MultipleCharacterWildcard().Value;
string luceneQuery = "+parentID:" + this.Id.ToString() + " +" + t;
var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery);
return Search(crit, searcher);
}
public DynamicNodeList Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null)
{
var s = Examine.ExamineManager.Instance.DefaultSearchProvider;
if (searchProvider != null)
s = searchProvider;
var results = s.Search(criteria);
return ExamineSearchUtill.ConvertSearchResultToDynamicNode(results);
}
public bool HasProperty(string name)
{
if (n != null)
{
return GetProperty(name) != null;
}
return false;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
try
{
//Property?
result = typeof(DynamicNode).InvokeMember(binder.Name,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.GetProperty,
null,
this,
args);
return true;
}
catch (MissingMethodException)
{
try
{
//Static or Instance Method?
result = typeof(DynamicNode).InvokeMember(binder.Name,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.InvokeMethod,
null,
this,
args);
return true;
}
catch (MissingMethodException)
{
try
{
result = ExecuteExtensionMethod(args, binder.Name, false);
return true;
}
catch (TargetInvocationException)
{
result = new DynamicNull();
return true;
}
catch
{
result = null;
return false;
}
}
}
catch
{
result = null;
return false;
}
}
private object ExecuteExtensionMethod(object[] args, string name, bool argsContainsThis)
{
object result = null;
MethodInfo methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(IEnumerable), args, name, false);
if (methodToExecute == null)
{
methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(DynamicNodeList), args, name, false);
}
if (methodToExecute != null)
{
var genericArgs = (new[] { this }).Concat(args);
result = methodToExecute.Invoke(null, genericArgs.ToArray());
}
else
{
throw new MissingMethodException();
}
if (result != null)
{
if (result is IEnumerable)
{
result = new DynamicNodeList((IEnumerable)result);
}
if (result is IEnumerable)
{
result = new DynamicNodeList((IEnumerable)result);
}
if (result is DynamicBackingItem)
{
result = new DynamicNode((DynamicBackingItem)result);
}
}
return result;
}
private static Dictionary, Type> _razorDataTypeModelTypes = null;
private static readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
internal static Dictionary, Type> RazorDataTypeModelTypes
{
get
{
using (var l = new UpgradeableReadLock(_locker))
{
if (_razorDataTypeModelTypes == null)
{
l.UpgradeToWriteLock();
var foundTypes = new Dictionary, Type>();
try
{
PluginManager.Current.ResolveRazorDataTypeModels()
.ToList()
.ConvertAll(type =>
{
var razorDataTypeModelAttributes = type.GetCustomAttributes(true);
return razorDataTypeModelAttributes.ToList().ConvertAll(razorDataTypeModelAttribute =>
{
var g = razorDataTypeModelAttribute.DataTypeEditorId;
var priority = razorDataTypeModelAttribute.Priority;
return new KeyValuePair, Type>(new System.Tuple(g, priority), type);
});
})
.SelectMany(item => item)
.ToList()
.ForEach(item =>
{
System.Tuple key = item.Key;
if (!foundTypes.ContainsKey(key))
{
foundTypes.Add(key, item.Value);
}
});
//there is no error, so set the collection
_razorDataTypeModelTypes = foundTypes;
}
catch (Exception ex)
{
LogHelper.Warn("Exception occurred while populating cache, will keep RazorDataTypeModelTypes to null so that this error remains visible and you don't end up with an empty cache with silent failure."
+ string.Format("The exception was {0} and the message was {1}. {2}", ex.GetType().FullName, ex.Message, ex.StackTrace));
}
}
return _razorDataTypeModelTypes;
}
}
}
private static Guid GetDataType(string docTypeAlias, string propertyAlias)
{
return GetDataTypeCallback(docTypeAlias, propertyAlias);
}
///
/// Returns the value from the property result and ensure it is filtered through the razor data type converters
///
///
/// The value result for the property
/// true if getting the property data was successful
private bool TryGetPropertyData(PropertyResult propResult, out object result)
{
if (propResult == null) throw new ArgumentNullException("propResult");
//special casing for true/false properties
//int/decimal are handled by ConvertPropertyValueByDataType
//fallback is stringT
if (n.NodeTypeAlias == null && propResult.Alias == null)
{
throw new ArgumentNullException("No node alias or property alias available. Unable to look up the datatype of the property you are trying to fetch.");
}
//contextAlias is the node which the property data was returned from
//Guid dataType = ContentType.GetDataType(data.ContextAlias, data.Alias);
var dataType = GetDataType(propResult.ContextAlias, propResult.Alias);
//now we need to map to the old object until we can clean all this nonsense up
var configMapping = UmbracoConfig.For.UmbracoSettings().Scripting.DataTypeModelStaticMappings
.Select(x => new RazorDataTypeModelStaticMappingItem()
{
DataTypeGuid = x.DataTypeGuid,
NodeTypeAlias = x.NodeTypeAlias,
PropertyTypeAlias = x.PropertyTypeAlias,
Raw = string.Empty,
TypeName = x.MappingName
}).ToList();
var staticMapping = configMapping
.FirstOrDefault(mapping => mapping.Applies(dataType, propResult.ContextAlias, propResult.Alias));
if (staticMapping != null)
{
var dataTypeType = Type.GetType(staticMapping.TypeName);
if (dataTypeType != null)
{
object valueOutput = null;
if (TryCreateInstanceRazorDataTypeModel(dataType, dataTypeType, propResult.Value, out valueOutput))
{
result = valueOutput;
return true;
}
LogHelper.Warn(string.Format("Failed to create the instance of the model binder"));
}
else
{
LogHelper.Warn(string.Format("staticMapping type name {0} came back as null from Type.GetType; check the casing, assembly presence, assembly framework version, namespace", staticMapping.TypeName));
}
}
if (RazorDataTypeModelTypes != null && RazorDataTypeModelTypes.Any(model => model.Key.Item1 == dataType) && dataType != Guid.Empty)
{
var razorDataTypeModelDefinition = RazorDataTypeModelTypes.Where(model => model.Key.Item1 == dataType).OrderByDescending(model => model.Key.Item2).FirstOrDefault();
if (!(razorDataTypeModelDefinition.Equals(default(KeyValuePair, Type>))))
{
Type dataTypeType = razorDataTypeModelDefinition.Value;
object valueResult = null;
if (TryCreateInstanceRazorDataTypeModel(dataType, dataTypeType, propResult.Value, out valueResult))
{
result = valueResult;
return true;
}
LogHelper.Warn(string.Format("Failed to create the instance of the model binder"));
}
else
{
LogHelper.Warn(string.Format("Could not get the dataTypeType for the RazorDataTypeModel"));
}
}
result = propResult.Value;
//convert the string value to a known type
var returnVal = ConvertPropertyValueByDataType(ref result, propResult.Alias, dataType);
return returnVal;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;
//check the cache first!
if (_cachedMemberOutput.TryGetValue(binder.Name, out result))
{
return true;
}
result = null; //this will never be returned
if (name.InvariantEquals("ChildrenAsList") || name.InvariantEquals("Children"))
{
result = GetChildrenAsList;
//cache the result so we don't have to re-process the whole thing
_cachedMemberOutput.TryAdd(binder.Name, result);
return true;
}
if (binder.Name.InvariantEquals("parentId"))
{
var parent = n.Parent;
if (parent == null)
{
throw new InvalidOperationException(string.Format("The node {0} does not have a parent", Id));
}
result = parent.Id;
_cachedMemberOutput.TryAdd(binder.Name, result);
return true;
}
bool propertyExists = false;
if (n != null)
{
bool recursive = false;
if (name.StartsWith("_"))
{
name = name.Substring(1, name.Length - 1);
recursive = true;
}
PropertyResult prop;
if (!_cachedProperties.TryGetValue(binder.Name, out prop))
{
prop = n.GetProperty(name, recursive, out propertyExists);
// check for nicer support of Pascal Casing EVEN if alias is camelCasing:
if (prop == null && name.Substring(0, 1).ToUpper() == name.Substring(0, 1) && !propertyExists)
{
prop = n.GetProperty(name.Substring(0, 1).ToLower() + name.Substring((1)), recursive, out propertyExists);
}
}
if (prop != null)
{
if (TryGetPropertyData(prop, out result))
{
//cache the result so we don't have to re-process the whole thing
_cachedMemberOutput.TryAdd(binder.Name, result);
return true;
}
}
//check if the alias is that of a child type
var typeChildren = n.ChildrenAsList;
if (typeChildren != null)
{
var filteredTypeChildren = typeChildren
.Where(x => x.NodeTypeAlias.InvariantEquals(name) || x.NodeTypeAlias.MakePluralName().InvariantEquals(binder.Name))
.ToArray();
if (filteredTypeChildren.Any())
{
result = new DynamicNodeList(filteredTypeChildren);
//cache the result so we don't have to re-process the whole thing
_cachedMemberOutput.TryAdd(binder.Name, result);
return true;
}
}
//lookup the property using reflection
result = GetReflectedProperty(binder.Name);
if (result != null)
{
_cachedMemberOutput.TryAdd(binder.Name, result);
return true;
}
}
//if property access, type lookup and member invoke all failed
//at this point, we're going to return null
//instead, we return a DynamicNull - see comments in that file
//this will let things like Model.ChildItem work and return nothing instead of crashing
if (!propertyExists && result == null)
{
//.Where explictly checks for this type
//and will make it false
//which means backwards equality (&& property != true) will pass
//forwwards equality (&& property or && property == true) will fail
result = new DynamicNull();
return true;
}
return true;
}
private object GetReflectedProperty(string alias)
{
Func> getMember =
memberAlias =>
{
try
{
return Attempt