using System; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; namespace Umbraco.Web.BackOffice.Trees { /// /// A base controller reference for non-attributed trees (un-registered). /// /// /// Developers should generally inherit from TreeController. /// [AngularJsonOnlyConfiguration] public abstract class TreeControllerBase : UmbracoAuthorizedApiController, ITree { // TODO: Need to set this, but from where? // Presumably not injecting as this will be a base controller for package/solution developers. private readonly UmbracoApiControllerTypeCollection _apiControllers; protected TreeControllerBase(UmbracoApiControllerTypeCollection apiControllers) { _apiControllers = apiControllers; } /// /// The method called to render the contents of the tree structure /// /// /// /// All of the query string parameters passed from jsTree /// /// /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); /// /// Returns the menu structure for the node /// /// /// /// protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); /// /// The name to display on the root node /// public abstract string RootNodeDisplayName { get; } /// public abstract string TreeGroup { get; } /// public abstract string TreeAlias { get; } /// public abstract string TreeTitle { get; } /// public abstract TreeUse TreeUse { get; } /// public abstract string SectionAlias { get; } /// public abstract int SortOrder { get; } /// public abstract bool IsSingleNodeTree { get; } /// /// Returns the root node for the tree /// /// /// public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; var node = CreateRootNode(queryStrings); //add the tree alias to the root node.AdditionalData["treeAlias"] = TreeAlias; AddQueryStringsToAdditionalData(node, queryStrings); //check if the tree is searchable and add that to the meta data as well if (this is ISearchableTree) node.AdditionalData.Add("searchable", "true"); //now update all data based on some of the query strings, like if we are running in dialog mode if (IsDialog(queryStrings)) node.RoutePath = "#"; OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); return node; } /// /// The action called to render the contents of the tree structure /// /// /// /// All of the query string parameters passed from jsTree /// /// JSON markup for jsTree /// /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; var nodes = GetTreeNodes(id, queryStrings); foreach (var node in nodes) AddQueryStringsToAdditionalData(node, queryStrings); //now update all data based on some of the query strings, like if we are running in dialog mode if (IsDialog(queryStrings)) foreach (var node in nodes) node.RoutePath = "#"; //raise the event OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); return nodes; } /// /// The action called to render the menu for a tree node /// /// /// /// public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; var menu = GetMenuForNode(id, queryStrings); //raise the event OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); return menu; } /// /// Helper method to create a root model for a tree /// /// protected virtual TreeNode CreateRootNode(FormCollection queryStrings) { var rootNodeAsString = Constants.System.RootString; queryStrings.TryGetValue(TreeQueryStringParameters.Application, out var currApp); var node = new TreeNode( rootNodeAsString, null, //this is a root node, there is no parent Url.GetTreeUrl(_apiControllers, GetType(), rootNodeAsString, queryStrings), Url.GetMenuUrl(_apiControllers, GetType(), rootNodeAsString, queryStrings)) { HasChildren = true, RoutePath = currApp, Name = RootNodeDisplayName }; return node; } #region Create TreeNode methods /// /// Helper method to create tree nodes /// /// /// /// /// /// public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title) { var jsonUrl = Url.GetTreeUrl(_apiControllers, GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(_apiControllers, GetType(), id, queryStrings); var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; return node; } /// /// Helper method to create tree nodes /// /// /// /// /// /// /// public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon) { var jsonUrl = Url.GetTreeUrl(_apiControllers, GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(_apiControllers, GetType(), id, queryStrings); var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, Icon = icon, NodeType = TreeAlias }; return node; } /// /// Helper method to create tree nodes /// /// /// /// /// /// /// /// public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon, string routePath) { var jsonUrl = Url.GetTreeUrl(_apiControllers, GetType(), id, queryStrings); var menuUrl = Url.GetMenuUrl(_apiControllers, GetType(), id, queryStrings); var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; return node; } /// /// Helper method to create tree nodes and automatically generate the json url + UDI /// /// /// /// /// /// /// public TreeNode CreateTreeNode(IEntitySlim entity, Guid entityObjectType, string parentId, FormCollection queryStrings, bool hasChildren) { var contentTypeIcon = entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, contentTypeIcon); treeNode.Path = entity.Path; treeNode.Udi = Udi.Create(ObjectTypes.GetUdiType(entityObjectType), entity.Key); treeNode.HasChildren = hasChildren; treeNode.Trashed = entity.Trashed; return treeNode; } /// /// Helper method to create tree nodes and automatically generate the json url + UDI /// /// /// /// /// /// /// /// public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormCollection queryStrings, string icon, bool hasChildren) { var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); treeNode.Udi = Udi.Create(ObjectTypes.GetUdiType(entityObjectType), entity.Key); treeNode.Path = entity.Path; treeNode.HasChildren = hasChildren; return treeNode; } /// /// Helper method to create tree nodes and automatically generate the json url /// /// /// /// /// /// /// /// public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon, bool hasChildren) { var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; return treeNode; } /// /// Helper method to create tree nodes and automatically generate the json url /// /// /// /// /// /// /// /// /// public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon, bool hasChildren, string routePath) { var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; treeNode.RoutePath = routePath; return treeNode; } /// /// Helper method to create tree nodes and automatically generate the json url + UDI /// /// /// /// /// /// /// /// /// /// public TreeNode CreateTreeNode(string id, string parentId, FormCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) { var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); treeNode.HasChildren = hasChildren; treeNode.RoutePath = routePath; treeNode.Udi = udi; return treeNode; } #endregion /// /// The AdditionalData of a node is always populated with the query string data, this method performs this /// operation and ensures that special values are not inserted or that duplicate keys are not added. /// /// /// protected void AddQueryStringsToAdditionalData(TreeNode node, FormCollection queryStrings) { foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) node.AdditionalData.Add(q.Key, q.Value); } /// /// If the request is for a dialog mode tree /// /// /// protected bool IsDialog(FormCollection queryStrings) { queryStrings.TryGetValue(TreeQueryStringParameters.Use, out var use); return use == "dialog"; } /// /// An event that allows developers to modify the tree node collection that is being rendered /// /// /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. /// public static event TypedEventHandler TreeNodesRendering; private static void OnTreeNodesRendering(TreeControllerBase instance, TreeNodesRenderingEventArgs e) { var handler = TreeNodesRendering; handler?.Invoke(instance, e); } /// /// An event that allows developer to modify the root tree node that is being rendered /// public static event TypedEventHandler RootNodeRendering; // internal for temp class below - kill eventually! internal static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) { var handler = RootNodeRendering; handler?.Invoke(instance, e); } /// /// An event that allows developers to modify the menu that is being rendered /// /// /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. /// public static event TypedEventHandler MenuRendering; private static void OnMenuRendering(TreeControllerBase instance, MenuRenderingEventArgs e) { var handler = MenuRendering; handler?.Invoke(instance, e); } } }