diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
index 8fabc1677e..668e7c310a 100644
--- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
@@ -54,7 +54,7 @@ namespace Umbraco.Web.Trees
}
#endregion
-
+
///
/// Ensure the noAccess metadata is applied for the root node if in dialog mode and the user doesn't have path access to it
///
@@ -70,12 +70,12 @@ namespace Umbraco.Web.Trees
}
return node;
- }
+ }
protected abstract TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings);
-
+
///
- /// Returns a for the and
+ /// Returns a for the and
/// attaches some meta data to the node if the user doesn't have start node access to it when in dialog mode
///
///
@@ -83,13 +83,13 @@ namespace Umbraco.Web.Trees
///
///
internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings)
- {
- bool hasPathAccess;
- var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess);
- if (entityIsAncestorOfStartNodes == false)
- return null;
+ {
+ bool hasPathAccess;
+ var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess);
+ if (entityIsAncestorOfStartNodes == false)
+ return null;
- var treeNode = GetSingleTreeNode(e, parentId, queryStrings);
+ var treeNode = GetSingleTreeNode(e, parentId, queryStrings);
if (hasPathAccess == false)
{
treeNode.AdditionalData["noAccess"] = true;
@@ -98,7 +98,7 @@ namespace Umbraco.Web.Trees
}
///
- /// Returns the
+ /// Returns the
///
protected abstract int RecycleBinId { get; }
@@ -111,79 +111,69 @@ namespace Umbraco.Web.Trees
/// Returns the user's start node for this tree
///
protected abstract int[] UserStartNodes { get; }
-
+
protected virtual TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings)
{
var nodes = new TreeNodeCollection();
- var altStartId = string.Empty;
- if (queryStrings.HasKey(TreeQueryStringParameters.StartNodeId))
- altStartId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId);
- var rootIdString = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
+ var rootIdString = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
+ var hasAccessToRoot = UserStartNodes.Contains(Constants.System.Root);
- //check if a request has been made to render from a specific start node
- if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != rootIdString)
+ var startNodeId = queryStrings.HasKey(TreeQueryStringParameters.StartNodeId)
+ ? queryStrings.GetValue(TreeQueryStringParameters.StartNodeId)
+ : string.Empty;
+
+ if (string.IsNullOrEmpty(startNodeId) == false && startNodeId != "undefined" && startNodeId != rootIdString)
{
- id = altStartId;
+ // request has been made to render from a specific, non-root, start node
+ id = startNodeId;
- //we need to verify that the user has access to view this node, otherwise we'll render an empty tree collection
- // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access
+ // ensure that the user has access to that node, otherwise return the empty tree nodes collection
+ // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access
if (HasPathAccess(id, queryStrings) == false)
{
- LogHelper.Warn("The user " + Security.CurrentUser.Username + " does not have access to the tree node " + id);
- return new TreeNodeCollection();
+ LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id);
+ return nodes;
}
- // So there's an alt id specified, it's not the root node and the user has access to it, great! But there's one thing we
- // need to consider:
- // If the tree is being rendered in a dialog view we want to render only the children of the specified id, but
- // when the tree is being rendered normally in a section and the current user's start node is not -1, then
- // we want to include their start node in the tree as well.
- // Therefore, in the latter case, we want to change the id to -1 since we want to render the current user's root node
- // and the GetChildEntities method will take care of rendering the correct root node.
- // If it is in dialog mode, then we don't need to change anything and the children will just render as per normal.
- if (IsDialog(queryStrings) == false && UserStartNodes.Contains(Constants.System.Root) == false)
+ // if the tree is rendered...
+ // - in a dialog: render only the children of the specific start node, nothing to do
+ // - in a section: if the current user's start nodes do not contain the root node, we need
+ // to include these start nodes in the tree too, to provide some context - i.e. change
+ // start node back to root node, and then GetChildEntities method will take care of the rest.
+ if (IsDialog(queryStrings) == false && hasAccessToRoot == false)
+ id = rootIdString;
+ }
+
+ // get child entities - if id is root, but user's start nodes do not contain the
+ // root node, this returns the start nodes instead of root's children
+ var entities = GetChildEntities(id).ToList();
+ nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null));
+
+ // if the user does not have access to the root node, what we have is the start nodes,
+ // but to provide some context we also need to add their topmost nodes when they are not
+ // topmost nodes themselves (level > 1).
+ if (id == rootIdString && hasAccessToRoot == false)
+ {
+ var topNodeIds = entities.Where(x => x.Level > 1).Select(GetTopNodeId).Where(x => x != 0).Distinct().ToArray();
+ if (topNodeIds.Length > 0)
{
- id = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
+ var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray());
+ nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null));
}
}
- var entities = GetChildEntities(id).ToList();
-
- //If we are looking up the root and there is more than one node ...
- //then we want to lookup those nodes' 'site' nodes and render those so that the
- //user has some context of where they are in the tree, this is generally for pickers in a dialog.
- //for any node they don't have access too, we need to add some metadata
- if (id == rootIdString && entities.Count > 1)
- {
- var siteNodeIds = new List();
- //put into array since we might modify the list
- foreach (var e in entities.ToArray())
- {
- var pathParts = e.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
- if (pathParts.Length < 2)
- continue; // this should never happen but better to check
-
- int siteNodeId;
- if (int.TryParse(pathParts[1], out siteNodeId) == false)
- continue;
-
- //we'll look up this
- siteNodeIds.Add(siteNodeId);
- }
- var siteNodes = Services.EntityService.GetAll(UmbracoObjectType, siteNodeIds.ToArray())
- .DistinctBy(e => e.Id)
- .ToArray();
-
- //add site nodes
- nodes.AddRange(siteNodes.Select(e => GetSingleTreeNodeWithAccessCheck(e, id, queryStrings)).Where(node => node != null));
-
- return nodes;
- }
-
- nodes.AddRange(entities.Select(e => GetSingleTreeNodeWithAccessCheck(e, id, queryStrings)).Where(node => node != null));
return nodes;
- }
+ }
+
+ private static readonly char[] Comma = { ',' };
+
+ private int GetTopNodeId(IUmbracoEntity entity)
+ {
+ int id;
+ var parts = entity.Path.Split(Comma, StringSplitOptions.RemoveEmptyEntries);
+ return parts.Length >= 2 && int.TryParse(parts[1], out id) ? id : 0;
+ }
protected abstract MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings);
@@ -191,30 +181,27 @@ namespace Umbraco.Web.Trees
protected IEnumerable GetChildEntities(string id)
{
- // use helper method to ensure we support both integer and guid lookups
- int iid;
-
- // look up from GUID if it's not an integer
- if (int.TryParse(id, out iid) == false)
+ // try to parse id as an integer else use GetEntityFromId
+ // which will grok Guids, Udis, etc and let use obtain the id
+ int entityId;
+ if (int.TryParse(id, out entityId) == false)
{
- var idEntity = GetEntityFromId(id);
- if (idEntity == null)
- {
+ var entity = GetEntityFromId(id);
+ if (entity == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
- }
- iid = idEntity.Id;
+ entityId = entity.Id;
}
// if a request is made for the root node but user has no access to
// root node, return start nodes instead
- if (iid == Constants.System.Root && UserStartNodes.Contains(Constants.System.Root) == false)
+ if (entityId == Constants.System.Root && UserStartNodes.Contains(Constants.System.Root) == false)
{
return UserStartNodes.Length > 0
? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes)
: Enumerable.Empty();
}
- return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray();
+ return Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray();
}
///
@@ -236,7 +223,7 @@ namespace Umbraco.Web.Trees
{
if (entity == null) return false;
return Security.CurrentUser.HasPathAccess(entity, Services.EntityService, RecycleBinId);
- }
+ }
///
/// Ensures the recycle bin is appended when required (i.e. user has access to the root and it's not in dialog mode)
@@ -257,7 +244,7 @@ namespace Umbraco.Web.Trees
if (queryStrings.HasKey(TreeQueryStringParameters.StartNodeId))
altStartId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId);
- //check if a request has been made to render from a specific start node
+ //check if a request has been made to render from a specific start node
if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.Root.ToString(CultureInfo.InvariantCulture))
{
id = altStartId;
@@ -324,7 +311,7 @@ namespace Umbraco.Web.Trees
menu.Items.Add(ui.Text("actions", "emptyTrashcan"));
menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true);
return menu;
- }
+ }
return PerformGetMenuForNode(id, queryStrings);
}
@@ -355,8 +342,8 @@ namespace Umbraco.Web.Trees
internal IEnumerable