U4-10829 - fix tree controller
This commit is contained in:
@@ -54,7 +54,7 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the noAccess metadata is applied for the root node if in dialog mode and the user doesn't have path access to it
|
||||
/// </summary>
|
||||
@@ -70,12 +70,12 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="TreeNode"/> for the <see cref="IUmbracoEntity"/> and
|
||||
/// Returns a <see cref="TreeNode"/> for the <see cref="IUmbracoEntity"/> and
|
||||
/// attaches some meta data to the node if the user doesn't have start node access to it when in dialog mode
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
@@ -83,13 +83,13 @@ namespace Umbraco.Web.Trees
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the
|
||||
/// Returns the
|
||||
/// </summary>
|
||||
protected abstract int RecycleBinId { get; }
|
||||
|
||||
@@ -111,79 +111,69 @@ namespace Umbraco.Web.Trees
|
||||
/// Returns the user's start node for this tree
|
||||
/// </summary>
|
||||
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<string>(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<string>(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<ContentTreeControllerBase>("The user " + Security.CurrentUser.Username + " does not have access to the tree node " + id);
|
||||
return new TreeNodeCollection();
|
||||
LogHelper.Warn<ContentTreeControllerBase>("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<int>();
|
||||
//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<IUmbracoEntity> 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<IUmbracoEntity>();
|
||||
}
|
||||
|
||||
return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray();
|
||||
return Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,7 +223,7 @@ namespace Umbraco.Web.Trees
|
||||
{
|
||||
if (entity == null) return false;
|
||||
return Security.CurrentUser.HasPathAccess(entity, Services.EntityService, RecycleBinId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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<string>(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<ActionEmptyTranscan>(ui.Text("actions", "emptyTrashcan"));
|
||||
menu.Items.Add<ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true);
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
|
||||
return PerformGetMenuForNode(id, queryStrings);
|
||||
}
|
||||
@@ -355,8 +342,8 @@ namespace Umbraco.Web.Trees
|
||||
internal IEnumerable<MenuItem> GetAllowedUserMenuItemsForNode(IUmbracoEntity dd)
|
||||
{
|
||||
var actions = ActionsResolver.Current.FromActionSymbols(Security.CurrentUser.GetPermissions(dd.Path, Services.UserService))
|
||||
.ToList();
|
||||
|
||||
.ToList();
|
||||
|
||||
// A user is allowed to delete their own stuff
|
||||
if (dd.CreatorId == Security.GetUserId() && actions.Contains(ActionDelete.Instance) == false)
|
||||
actions.Add(ActionDelete.Instance);
|
||||
@@ -370,12 +357,12 @@ namespace Umbraco.Web.Trees
|
||||
/// <param name="doc">The Document to check permissions against</param>
|
||||
/// <param name="allowedUserOptions">A list of MenuItems that the user has permissions to execute on the current document</param>
|
||||
/// <remarks>By default the user must have Browse permissions to see the node in the Content tree</remarks>
|
||||
/// <returns></returns>
|
||||
/// <returns></returns>
|
||||
internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable<MenuItem> allowedUserOptions)
|
||||
{
|
||||
return allowedUserOptions.Select(x => x.Action).OfType<ActionBrowse>().Any();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// this will parse the string into either a GUID or INT
|
||||
/// </summary>
|
||||
@@ -399,11 +386,11 @@ namespace Umbraco.Web.Trees
|
||||
{
|
||||
var guidUdi = idUdi as GuidUdi;
|
||||
if (guidUdi != null)
|
||||
return new Tuple<Guid?, int?>(guidUdi.Guid, null);
|
||||
}
|
||||
return new Tuple<Guid?, int?>(guidUdi.Guid, null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an entity via an id that can be either an integer, Guid or UDI
|
||||
@@ -442,11 +429,11 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
|
||||
return entity;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, IUmbracoEntity> _entityCache = new ConcurrentDictionary<string, IUmbracoEntity>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user