using System; using System.Data; using System.Collections; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.Collections.Generic; using CMS.CMSHelper; using CMS.EventLog; using CMS.GlobalHelper; using CMS.IO; using CMS.PortalEngine; using CMS.SettingsProvider; using CMS.SiteProvider; using CMS.UIControls; using CMS.DocumentEngine; using TreeNode = System.Web.UI.WebControls.TreeNode; public partial class CMSAdminControls_UI_Trees_MacroTree : CMSUserControl { #region "Variables" private ContextResolver mContextResolver; private string mOnNodeClickHandler = null; private string mMacroExpression; private object mRoot; private bool mDisplayValues = true; private bool mVirtualMode = true; private int mMaxDepth = 10; #endregion #region "Properties" /// /// If true, the control is displayed in the virtual mode /// public bool VirtualMode { get { return mVirtualMode; } set { mVirtualMode = value; } } /// /// Tree view control. /// public TreeView TreeControl { get { return treeElem; } } /// /// Gets or sets the name of the resolver to use. This property is used if ContextResolver property is not explicitly set. /// public string ResolverName { get { return ValidationHelper.GetString(GetValue("ResolverName"), ""); } set { SetValue("ResolverName", value); } } /// /// Gets or sets ContextResolver object which is used to evaluate macro expression. If nothing is specified, CMSContext.CurrentResolver is used. /// public ContextResolver ContextResolver { get { if (mContextResolver == null) { if (!string.IsNullOrEmpty(ResolverName)) { mContextResolver = ResolverFactory.GetResolver(ResolverName); } else { // Initialize the default context resolver mContextResolver = CMSContext.CurrentResolver.CreateContextChild(); } mContextResolver.Context.VirtualMode = VirtualMode; } return mContextResolver; } set { mContextResolver = value; } } /// /// Gets or sets macro expression to evaluate. The result of the evaluation is used as a root of the tree structure. /// public string MacroExpression { get { return mMacroExpression; } set { mMacroExpression = value; } } /// /// Gets or sets JavaScript function which will be called when a node in the tree is clicked. Macro of the node is passed as a parameter to this function. /// public string OnNodeClickHandler { get { return mOnNodeClickHandler; } set { mOnNodeClickHandler = value; } } /// /// Gets or sets maximal tree depth. /// public int MaxDepth { get { return mMaxDepth; } set { mMaxDepth = value; } } /// /// Indicates whether to display values of the properties. /// public bool DisplayValues { get { return mDisplayValues && !VirtualMode; } set { mDisplayValues = value; } } /// /// Gets a root of the tree structure (result of the evauation of given macro). /// public object Root { get { if (mRoot == null) { bool match = false; ContextResolver.ResolveDataMacro(MacroExpression, ref mRoot, out match, true); } return mRoot; } } /// /// Gets an macro expression specifying currently selected node. /// public string SelectedMacroExpression { get { if (treeElem.SelectedNode != null) { return treeElem.SelectedNode.Value.Replace('/', '.').Replace(".[", "["); } else { return MacroExpression; } } } /// /// Gets an object specifying currently selected node. /// public object SelectedObject { get { object currentNodeObj = null; bool match = false; ContextResolver.ResolveDataMacro(SelectedMacroExpression, ref currentNodeObj, out match, true); return currentNodeObj; } } #endregion protected void Page_Load(object sender, EventArgs e) { // Setup the design bool isRTL = CultureHelper.IsUICultureRTL(); if (IsLiveSite) { isRTL = CultureHelper.IsPreferredCultureRTL(); } if (isRTL) { treeElem.LineImagesFolder = GetImageUrl("RTL/Design/Controls/Tree", IsLiveSite, true); } else { treeElem.LineImagesFolder = GetImageUrl("Design/Controls/Tree", IsLiveSite, true); } treeElem.ImageSet = TreeViewImageSet.Custom; treeElem.ExpandImageToolTip = GetString("ContentTree.Expand"); treeElem.CollapseImageToolTip = GetString("ContentTree.Collapse"); treeElemPriority.LineImagesFolder = treeElem.LineImagesFolder; treeElemPriority.ImageSet = treeElem.ImageSet; treeElemPriority.ExpandImageToolTip = treeElem.ExpandImageToolTip; treeElemPriority.CollapseImageToolTip = treeElem.CollapseImageToolTip; ContextResolver.UserName = CMSContext.CurrentUser.UserName; ContextResolver.CheckIntegrity = false; if (!Page.IsCallback) { // Register tree progress icon ScriptHelper.RegisterTreeProgress(Page); } } protected void Page_PreRender(object sender, EventArgs e) { if (!StopProcessing) { ReloadData(); if (ContextResolver.ShowOnlyPrioritized) { treeElem.Visible = false; } } } /// /// Reloads the tree. /// public void ReloadData() { // Setup the root if (!String.IsNullOrEmpty(MacroExpression)) { TreeNode root = new TreeNode("" + GetString("objecttasks.root") + ""); root.NavigateUrl = "javascript: " + OnNodeClickHandler + "('');"; root.Value = HttpUtility.UrlEncode(MacroExpression); if (IsSimpleValue(Root)) { // Macro result is only simple value, no children root.ToolTip = GetValueString(Root, true); } else { root.ToolTip = GetValueString(Root, false); root.PopulateOnDemand = true; } treeElem.Nodes.Add(root); } // Initialize priority tree if (ContextResolver.NamedSourceDataPriority.Count > 0) { List priorities = ContextResolver.NamedSourceDataPriority; priorities.Sort(); TreeNode rootPriority = new TreeNode("" + GetString("objecttasks.contextspecific") + ""); rootPriority.NavigateUrl = "javascript: " + OnNodeClickHandler + "('');"; rootPriority.Value = ""; foreach (string item in priorities) { object result = null; bool match = false; ContextResolver.ResolveDataMacro(item, ref result, out match, true); AppendChild(rootPriority, item, result, false, true); } treeElemPriority.Nodes.Add(rootPriority); rootPriority.Expanded = true; } else { pnlPriorityTree.Visible = false; } // Hide the tree if there are no nodes treeElem.Nodes[0].Expanded = true; if (treeElem.Nodes[0].ChildNodes.Count == 0) { treeElem.Visible = false; } else { // Expand all tree by default only if there are no context values if (treeElem.Nodes[0] != null) { treeElem.Nodes[0].Expanded = (ContextResolver.NamedSourceDataPriority.Count == 0); } } } protected void treeElem_TreeNodePopulate(object sender, TreeNodeEventArgs e) { TreeNodePopulate(e, false); } protected void treeElemPriority_TreeNodePopulate(object sender, TreeNodeEventArgs e) { TreeNodePopulate(e, true); } private void TreeNodePopulate(TreeNodeEventArgs e, bool isPriorityTree) { // Get the macro expression and evaluate it string macro = HttpUtility.UrlDecode(e.Node.ValuePath.Replace('/', '.')).Replace(".[", "[").TrimStart('.'); object currentNodeObj = null; bool match = false; bool securityCheckPassed = false; ContextResolver.ResolveDataMacro(macro, ref currentNodeObj, out match, true, out securityCheckPassed); if (securityCheckPassed) { BindObject(currentNodeObj, e.Node, isPriorityTree); } else { TreeNode child = new TreeNode(GetString("macrodesigner.accessdenied")); child.Value = ""; e.Node.ChildNodes.Add(child); } } private void BindObject(object obj, TreeNode root, bool isPriorityTree) { int index = 0; try { if (obj is DataRow) { // DataRow source, bind column names DataRow dr = (DataRow)obj; // Create tree structure foreach (DataColumn col in dr.Table.Columns) { // Stop on max nodes if (index++ >= MacroResolver.MaxMacroNodes) { AppendMore(root); break; } // Add the column object childObj = dr[col.ColumnName]; AppendChild(root, col.ColumnName, childObj, false, isPriorityTree); } } else if (obj is DataRowView) { // DataRowView source, bind column names DataRowView dr = (DataRowView)obj; // Create tree structure foreach (DataColumn col in dr.DataView.Table.Columns) { // Stop on max nodes if (index++ >= MacroResolver.MaxMacroNodes) { AppendMore(root); break; } // Add the column object childObj = dr[col.ColumnName]; AppendChild(root, col.ColumnName, childObj, false, isPriorityTree); } } else if (obj is IHierarchicalObject) { // Data container source IHierarchicalObject hc = (IHierarchicalObject)obj; // Create tree structure foreach (string col in hc.Properties) { // Stop on max nodes if (index++ >= MacroResolver.MaxMacroNodes) { AppendMore(root); break; } // Add the property object childObj = null; try { ContextResolver.GetObjectValue(hc, col, ref childObj); // Append the child value AppendChild(root, col, childObj, false, isPriorityTree); } catch (Exception ex) { EventLogProvider.LogException("MacroTree", "EXPAND", ex); // Append the exception as child value AppendChild(root, col, ex, false, isPriorityTree); } } } else if (obj is IDataContainer) { // Data container source IDataContainer dc = (IDataContainer)obj; // Create tree structure foreach (string col in dc.ColumnNames) { // Stop on max nodes if (index++ >= MacroResolver.MaxMacroNodes) { AppendMore(root); break; } // Add the column object childObj = null; dc.TryGetValue(col, out childObj); AppendChild(root, col, childObj, false, isPriorityTree); } } // Enumerable objects if ((obj is IEnumerable) && !(obj is string)) { IEnumerable collection = (IEnumerable)obj; IEnumerator enumerator = null; bool indexByName = false; INamedEnumerable namedCol = null; if (obj is INamedEnumerable) { // Collection with name enumerator namedCol = (INamedEnumerable)collection; if (namedCol.ItemsHaveNames) { enumerator = namedCol.GetNamedEnumerator(); indexByName = true; } } if (!indexByName) { // Standard collection enumerator = collection.GetEnumerator(); } int i = 0; List addedItems = new List(); addedItems.Add("trees"); addedItems.Add("documents"); while (enumerator.MoveNext()) { // Stop on max nodes if (index++ >= MacroResolver.MaxMacroNodes) { AppendMore(root); break; } // Add the item object item = SqlHelperClass.EncapsulateObject(enumerator.Current); if (indexByName) { // Convert the name with dot to indexer string name = namedCol.GetObjectName(item); if (!ValidationHelper.IsIdentifier(name)) { name = "[\"" + name + "\"]"; } string nameToLower = name.ToLowerCSafe(); if (!addedItems.Contains(nameToLower)) { addedItems.Add(nameToLower); AppendChild(root, name, item, false, isPriorityTree); } } else { // Indexed item AppendChild(root, i.ToString(), item, true, isPriorityTree); } i++; } } } catch (Exception ex) { EventLogProvider.LogException("MacroTree", "EXPAND", ex); throw ex; } } #region "Helper methods" /// /// Appends the "more" node (for case when there is more items than max nodes /// /// Root node private void AppendMore(TreeNode root) { string iconUrl = GetImagePath("Design/Controls/MacroEditor/info.png"); string icon = "\"\""; string text = "" + icon + "" + GetString("MacroTree.More") + ""; TreeNode child = new TreeNode(text); child.ToolTip = GetString("MacroTree.MoreInfo"); child.Value = ""; child.PopulateOnDemand = false; root.ChildNodes.Add(child); } /// /// Appends given child object to the root node. /// /// Root node /// Name of the column /// Child object to append /// Indicates if it's enumeration item /// If true, nodes are appended to a priority tree private void AppendChild(TreeNode root, string colName, object childObj, bool enumerate, bool isPriorityTree) { // Convert object property to value if (childObj is ObjectProperty) { object value = ((ObjectProperty)childObj).Value; AppendChild(root, colName, value, enumerate, isPriorityTree); return; } // Prepare the property name string col = colName; string defaultIconUrl = null; if (enumerate) { if (childObj is InfoObjectCollection) { col = ((InfoObjectCollection)childObj).Name; } else if (childObj != null) { col = "[" + col + "]" + (DisplayValues ? " " + HTMLHelper.HTMLEncode(childObj.ToString()) : ""); } } string name = col; if (ContextResolver.Context.DisablePageContextMacros) { if ((name.ToLowerCSafe() == "currentdocument") || (name.ToLowerCSafe() == "currentpageinfo")) { return; } } if (ContextResolver.Context.DisableContextObjectMacros) { if (name.EndsWithCSafe("context", true)) { return; } } // Prepare the icon string iconUrl = GetImagePath("CMSModules/list.png"); if (childObj is SettingsCategoryContainer) { // Get list icon of Settings category iconUrl = GetObjectIconPath(SiteObjectType.SETTINGSCATEGORY, "list.png"); } else if (childObj is CollectionPropertyWrapper) { iconUrl = GetImagePath("Design/Controls/MacroEditor/list.png"); } else if (childObj is InfoObjectCollection) { var colObj = (InfoObjectCollection)childObj; // Collection iconUrl = GetObjectIconPath(colObj.ObjectType, "list.png"); defaultIconUrl = GetDefaultIconUrl(colObj.TypeInfo); } else if (childObj is CMS.DocumentEngine.TreeNode) { // Document CMS.DocumentEngine.TreeNode node = (CMS.DocumentEngine.TreeNode)childObj; iconUrl = GetDocumentTypeIconPath(node.NodeClassName, null, true); string docName = node.GetDocumentName(); if (enumerate) { name = "[" + colName + "] " + HTMLHelper.HTMLEncode(docName); } else { if (DisplayValues) { name += " (" + HTMLHelper.HTMLEncode(docName) + ")"; ; } } } else if (childObj is BaseInfo) { // Info object BaseInfo infoObj = (BaseInfo)childObj; iconUrl = GetObjectIconPath(infoObj.ObjectType, "list.png"); defaultIconUrl = GetDefaultIconUrl(infoObj.TypeInfo); string objName = infoObj.Generalized.ObjectDisplayName; if (!String.IsNullOrEmpty(objName)) { if (enumerate) { name = "[" + colName + "] " + (DisplayValues ? " " + HTMLHelper.HTMLEncode(objName) : ""); } else { if (DisplayValues) { name += " (" + HTMLHelper.HTMLEncode(objName) + ")"; } } } } else if (childObj is PageInfo) { // Page info PageInfo pi = (PageInfo)childObj; iconUrl = GetDocumentTypeIconPath(pi.ClassName, "", false); } else if (childObj is IContext) { // Context iconUrl = GetImagePath("Design/Controls/MacroEditor/context.png"); } else if (childObj is Exception) { // Error iconUrl = GetImagePath("Design/Controls/MacroEditor/error.png"); } else { switch (col.ToLowerCSafe()) { case "children": // Children iconUrl = GetImagePath("Design/Controls/MacroEditor/children.png"); break; case "parent": // Parent iconUrl = GetImagePath("Design/Controls/MacroEditor/parent.png"); break; default: if (childObj == null) { // Null iconUrl = GetImagePath("Design/Controls/MacroEditor/null.png"); } else if (!IsSimpleValue(childObj)) { // Complex object iconUrl = GetImagePath("Design/Controls/MacroEditor/repository.png"); } else if (childObj is string) { // Simple value iconUrl = GetImagePath("Design/Controls/MacroEditor/string.png"); } else if ((childObj is int) || (childObj is double) || (childObj is long)) { // Simple value iconUrl = GetImagePath("Design/Controls/MacroEditor/number.png"); } else if (childObj is bool) { if ((bool)childObj) { // True iconUrl = GetImagePath("Design/Controls/MacroEditor/true.png"); } else { // False iconUrl = GetImagePath("Design/Controls/MacroEditor/false.png"); } } else if (childObj is DateTime) { // Simple value iconUrl = GetImagePath("Design/Controls/MacroEditor/datetime.png"); } else { // Simple value iconUrl = GetImagePath("Design/Controls/MacroEditor/value.png"); } break; } } string icon = null; if (!String.IsNullOrEmpty(iconUrl)) { // Check the existence of the file if (!File.Exists(Server.MapPath(iconUrl))) { if (defaultIconUrl == null) { iconUrl = GetImagePath("CMSModules/list.png"); } else { iconUrl = defaultIconUrl; } } icon = "\"\""; } bool isSimple = IsSimpleValue(childObj); // Append JS onclick action if defined, pass macro expression to the handler string jsHandler = ""; if (!string.IsNullOrEmpty(OnNodeClickHandler)) { string macroArg = root.ValuePath.Replace('/', '.') + (enumerate ? "[" + colName + "]" : "." + col); macroArg = HttpUtility.UrlDecode(macroArg); macroArg = macroArg.Replace(".[", "[").TrimStart('.'); if (isPriorityTree) { jsHandler = OnNodeClickHandler + "(" + ScriptHelper.GetString(macroArg, true).Replace("\\\"", """) + ");"; } else { jsHandler = OnNodeClickHandler + "(" + ScriptHelper.GetString(macroArg.Substring(MacroExpression.Length + 1), true).Replace("\\\"", """) + ");"; } } // Create the node name = TextHelper.LimitLength(ResHelper.LocalizeString(name), 150, null, true); string text = "" + icon + "" + name + ""; TreeNode child = new TreeNode(text); child.ToolTip = GetValueString(childObj, isSimple); child.Value = HttpUtility.UrlEncode(enumerate ? "[" + colName + "]" : col); child.PopulateOnDemand = !isSimple; child.Expanded = false; root.ChildNodes.Add(child); } /// /// Gets the default icon URL for the given type info /// /// Type information protected string GetDefaultIconUrl(TypeInfo ti) { string defaultIconUrl = null; // Default icon per object category if (ti.IsSiteBinding) { defaultIconUrl = GetImagePath("Design/Controls/MacroEditor/sitebinding.png"); } else if (ti.IsBinding) { defaultIconUrl = GetImagePath("Design/Controls/MacroEditor/binding.png"); } return defaultIconUrl; } /// /// Returns formated value of given value. /// /// Value /// Indicates if the value is simple object without children private string GetValueString(object obj, bool isSimple) { if (isSimple) { if (obj != null) { string stringValue = TextHelper.LimitLength(ValidationHelper.GetString(obj, ""), 200, null, true); return "(" + obj.GetType() + "): " + stringValue; } } else { return "(" + obj.GetType() + ")" + (obj is IList ? " Count: " + ((IList)obj).Count : ""); } return "null"; } /// /// Returns true if the object is simple value (value which has no children). /// /// Object to check private bool IsSimpleValue(object obj) { if (obj == null) { return true; } if ((obj is IDataContainer) || (obj is IHierarchicalObject) || (obj is DataRowView) || (obj is DataRow)) { return false; } if ((obj is IEnumerable) && !(obj is string) && !(obj is byte[])) { //// If it's enumeration, return false only if there are some items //IEnumerable collection = (IEnumerable)obj; //IEnumerator enumerator = collection.GetEnumerator(); //if (enumerator.MoveNext()) //{ // return false; //} return false; } return true; } #endregion }