2013-08-12 15:06:12 +02:00
|
|
|
|
using System;
|
2017-09-19 15:51:47 +02:00
|
|
|
|
using System.Collections.Concurrent;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
2013-12-12 14:10:03 +11:00
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Net.Http;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
using System.Net.Http.Formatting;
|
2013-12-12 14:10:03 +11:00
|
|
|
|
using System.Web.Http;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
using Umbraco.Core;
|
2016-03-11 15:43:34 +01:00
|
|
|
|
using Umbraco.Core.Services;
|
2014-09-23 16:50:05 +10:00
|
|
|
|
using Umbraco.Core.Logging;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
using Umbraco.Core.Models;
|
2013-10-08 12:38:27 +11:00
|
|
|
|
using Umbraco.Web.Models.Trees;
|
2013-12-12 14:10:03 +11:00
|
|
|
|
using Umbraco.Web.WebApi.Filters;
|
2013-11-18 22:29:19 +01:00
|
|
|
|
using System.Globalization;
|
2018-01-15 11:32:30 +01:00
|
|
|
|
using Umbraco.Core.Models.Entities;
|
2016-03-17 15:28:46 +01:00
|
|
|
|
using Umbraco.Web._Legacy.Actions;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.Trees
|
|
|
|
|
|
{
|
2013-09-26 15:55:38 +10:00
|
|
|
|
public abstract class ContentTreeControllerBase : TreeController
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2013-12-12 14:10:03 +11:00
|
|
|
|
#region Actions
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets an individual tree node
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
[HttpQueryStringFilter("queryStrings")]
|
|
|
|
|
|
public TreeNode GetTreeNode(string id, FormDataCollection queryStrings)
|
|
|
|
|
|
{
|
|
|
|
|
|
int asInt;
|
|
|
|
|
|
if (int.TryParse(id, out asInt) == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var entity = Services.EntityService.Get(asInt, UmbracoObjectType);
|
|
|
|
|
|
if (entity == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var node = GetSingleTreeNode(entity, entity.ParentId.ToInvariantString(), queryStrings);
|
|
|
|
|
|
|
|
|
|
|
|
//add the tree alias to the node since it is standalone (has no root for which this normally belongs)
|
|
|
|
|
|
node.AdditionalData["treeAlias"] = TreeAlias;
|
|
|
|
|
|
return node;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2017-09-19 15:51:47 +02:00
|
|
|
|
/// <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>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
|
|
|
|
|
{
|
|
|
|
|
|
var node = base.CreateRootNode(queryStrings);
|
|
|
|
|
|
|
|
|
|
|
|
if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
node.AdditionalData["noAccess"] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return node;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-15 11:32:30 +01:00
|
|
|
|
protected abstract TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings);
|
2013-12-12 14:10:03 +11:00
|
|
|
|
|
2017-09-19 15:51:47 +02:00
|
|
|
|
/// <summary>
|
2017-09-23 10:08:18 +02:00
|
|
|
|
/// Returns a <see cref="TreeNode"/> for the <see cref="IUmbracoEntity"/> and
|
2017-09-19 15:51:47 +02:00
|
|
|
|
/// 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>
|
|
|
|
|
|
/// <param name="parentId"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2018-01-15 11:32:30 +01:00
|
|
|
|
internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings)
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
2018-01-15 11:32:30 +01:00
|
|
|
|
var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out var hasPathAccess);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (entityIsAncestorOfStartNodes == false)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
2017-09-23 10:08:18 +02:00
|
|
|
|
var treeNode = GetSingleTreeNode(e, parentId, queryStrings);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (hasPathAccess == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
treeNode.AdditionalData["noAccess"] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return treeNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// <summary>
|
2016-09-01 19:06:08 +02:00
|
|
|
|
/// Returns the
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected abstract int RecycleBinId { get; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Returns true if the recycle bin has items in it
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected abstract bool RecycleBinSmells { get; }
|
|
|
|
|
|
|
2013-10-01 16:38:10 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Returns the user's start node for this tree
|
|
|
|
|
|
/// </summary>
|
2017-09-12 16:22:16 +02:00
|
|
|
|
protected abstract int[] UserStartNodes { get; }
|
2017-09-23 10:08:18 +02:00
|
|
|
|
|
2014-09-23 16:50:05 +10:00
|
|
|
|
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);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
var rootIdString = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
|
2014-09-23 16:50:05 +10:00
|
|
|
|
|
|
|
|
|
|
//check if a request has been made to render from a specific start node
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != rootIdString)
|
2014-09-23 16:50:05 +10:00
|
|
|
|
{
|
|
|
|
|
|
id = altStartId;
|
|
|
|
|
|
|
|
|
|
|
|
//we need to verify that the user has access to view this node, otherwise we'll render an empty tree collection
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// 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
|
2014-09-23 16:50:05 +10:00
|
|
|
|
if (HasPathAccess(id, queryStrings) == false)
|
|
|
|
|
|
{
|
2016-09-11 19:57:33 +02:00
|
|
|
|
Logger.Warn<ContentTreeControllerBase>("The user " + Security.CurrentUser.Username + " does not have access to the tree node " + id);
|
2014-09-23 16:50:05 +10:00
|
|
|
|
return new TreeNodeCollection();
|
|
|
|
|
|
}
|
2016-09-01 19:06:08 +02:00
|
|
|
|
|
2014-09-23 16:50:05 +10:00
|
|
|
|
// 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
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// need to consider:
|
2014-09-23 16:50:05 +10:00
|
|
|
|
// If the tree is being rendered in a dialog view we want to render only the children of the specified id, but
|
2016-09-01 19:06:08 +02:00
|
|
|
|
// when the tree is being rendered normally in a section and the current user's start node is not -1, then
|
2014-09-23 16:50:05 +10:00
|
|
|
|
// 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.
|
2017-09-12 16:22:16 +02:00
|
|
|
|
if (IsDialog(queryStrings) == false && UserStartNodes.Contains(Constants.System.Root) == false)
|
2014-09-23 16:50:05 +10:00
|
|
|
|
{
|
|
|
|
|
|
id = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-09-01 19:06:08 +02:00
|
|
|
|
|
2017-09-19 15:51:47 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
2017-09-23 10:08:18 +02:00
|
|
|
|
//we'll look up this
|
2017-09-19 15:51:47 +02:00
|
|
|
|
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));
|
2017-09-23 10:08:18 +02:00
|
|
|
|
|
2017-09-19 15:51:47 +02:00
|
|
|
|
return nodes;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nodes.AddRange(entities.Select(e => GetSingleTreeNodeWithAccessCheck(e, id, queryStrings)).Where(node => node != null));
|
2014-09-23 16:50:05 +10:00
|
|
|
|
return nodes;
|
2017-09-23 10:08:18 +02:00
|
|
|
|
}
|
2014-01-17 13:00:11 +11:00
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
protected abstract MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings);
|
|
|
|
|
|
|
|
|
|
|
|
protected abstract UmbracoObjectTypes UmbracoObjectType { get; }
|
|
|
|
|
|
|
2018-01-15 11:32:30 +01:00
|
|
|
|
protected IEnumerable<IEntitySlim> GetChildEntities(string id)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2017-05-12 14:49:44 +02:00
|
|
|
|
// use helper method to ensure we support both integer and guid lookups
|
|
|
|
|
|
|
|
|
|
|
|
if (int.TryParse(id, out int iid) == false)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2017-05-12 14:49:44 +02:00
|
|
|
|
var idEntity = GetEntityFromId(id);
|
|
|
|
|
|
if (idEntity == null)
|
|
|
|
|
|
throw new HttpResponseException(HttpStatusCode.NotFound);
|
|
|
|
|
|
|
|
|
|
|
|
iid = idEntity.Id;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-09-12 16:22:16 +02:00
|
|
|
|
// 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)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2017-09-12 16:22:16 +02:00
|
|
|
|
return UserStartNodes.Length > 0
|
|
|
|
|
|
? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes)
|
2018-01-15 11:32:30 +01:00
|
|
|
|
: Enumerable.Empty<IEntitySlim>();
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Services.EntityService.GetChildren(iid, UmbracoObjectType).ToArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-11-19 12:28:50 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2017-09-19 15:51:47 +02:00
|
|
|
|
//we should remove this in v8, it's now here for backwards compat only
|
2013-11-19 12:28:50 +11:00
|
|
|
|
protected abstract bool HasPathAccess(string id, FormDataCollection queryStrings);
|
|
|
|
|
|
|
2017-09-19 15:51:47 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="entity"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected bool HasPathAccess(IUmbracoEntity entity, FormDataCollection queryStrings)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entity == null) return false;
|
|
|
|
|
|
return Security.CurrentUser.HasPathAccess(entity, Services.EntityService, RecycleBinId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// <summary>
|
2014-09-23 16:50:05 +10:00
|
|
|
|
/// Ensures the recycle bin is appended when required (i.e. user has access to the root and it's not in dialog mode)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2014-09-23 16:50:05 +10:00
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// This method is overwritten strictly to render the recycle bin, it should serve no other purpose
|
|
|
|
|
|
/// </remarks>
|
2013-08-12 15:06:12 +02:00
|
|
|
|
protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
|
|
|
|
|
{
|
2013-11-19 12:28:50 +11:00
|
|
|
|
//check if we're rendering the root
|
2017-09-12 16:22:16 +02:00
|
|
|
|
if (id == Constants.System.Root.ToInvariantString() && UserStartNodes.Contains(Constants.System.Root))
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2014-09-23 16:50:05 +10:00
|
|
|
|
var altStartId = string.Empty;
|
2014-08-08 10:54:05 +02:00
|
|
|
|
|
2014-09-23 16:50:05 +10:00
|
|
|
|
if (queryStrings.HasKey(TreeQueryStringParameters.StartNodeId))
|
|
|
|
|
|
altStartId = queryStrings.GetValue<string>(TreeQueryStringParameters.StartNodeId);
|
2014-01-17 13:00:11 +11:00
|
|
|
|
|
2016-09-01 19:06:08 +02:00
|
|
|
|
//check if a request has been made to render from a specific start node
|
2014-09-23 16:50:05 +10:00
|
|
|
|
if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.Root.ToString(CultureInfo.InvariantCulture))
|
2013-11-19 12:28:50 +11:00
|
|
|
|
{
|
2014-09-23 16:50:05 +10:00
|
|
|
|
id = altStartId;
|
2013-11-19 12:28:50 +11:00
|
|
|
|
}
|
2016-09-01 19:06:08 +02:00
|
|
|
|
|
2014-09-23 16:50:05 +10:00
|
|
|
|
var nodes = GetTreeNodesInternal(id, queryStrings);
|
2016-09-01 19:06:08 +02:00
|
|
|
|
|
2014-09-23 16:50:05 +10:00
|
|
|
|
//only render the recycle bin if we are not in dialog and the start id id still the root
|
|
|
|
|
|
if (IsDialog(queryStrings) == false && id == Constants.System.Root.ToInvariantString())
|
2013-09-04 17:36:05 +10:00
|
|
|
|
{
|
|
|
|
|
|
nodes.Add(CreateTreeNode(
|
2013-10-24 16:54:17 +11:00
|
|
|
|
RecycleBinId.ToInvariantString(),
|
2014-09-23 16:50:05 +10:00
|
|
|
|
id,
|
2013-09-04 17:36:05 +10:00
|
|
|
|
queryStrings,
|
2016-03-11 15:56:00 +01:00
|
|
|
|
Services.TextService.Localize("general/recycleBin"),
|
2013-09-04 17:36:05 +10:00
|
|
|
|
"icon-trash",
|
|
|
|
|
|
RecycleBinSmells,
|
2014-06-08 16:15:28 +01:00
|
|
|
|
queryStrings.GetValue<string>("application") + TreeAlias.EnsureStartsWith('/') + "/recyclebin"));
|
2014-09-23 16:50:05 +10:00
|
|
|
|
|
2013-09-04 17:36:05 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
return nodes;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-12-04 15:54:50 +11:00
|
|
|
|
return GetTreeNodesInternal(id, queryStrings);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Before we make a call to get the tree nodes we have to check if they can actually be rendered
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// Currently this just checks if it is a container type, if it is we cannot render children. In the future this might check for other things.
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
private TreeNodeCollection GetTreeNodesInternal(string id, FormDataCollection queryStrings)
|
|
|
|
|
|
{
|
2017-09-19 15:51:47 +02:00
|
|
|
|
var current = GetEntityFromId(id);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
|
2017-07-20 11:21:28 +02:00
|
|
|
|
//before we get the children we need to see if this is a container node
|
2014-01-17 13:00:11 +11:00
|
|
|
|
|
2014-01-28 15:49:04 +01:00
|
|
|
|
//test if the parent is a listview / container
|
2018-01-15 11:32:30 +01:00
|
|
|
|
if (current != null && current.IsContainer)
|
2013-11-15 16:19:46 +11:00
|
|
|
|
{
|
|
|
|
|
|
//no children!
|
|
|
|
|
|
return new TreeNodeCollection();
|
|
|
|
|
|
}
|
2014-01-27 22:03:52 +01:00
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
return PerformGetTreeNodes(id, queryStrings);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checks if the menu requested is for the recycle bin and renders that, otherwise renders the result of PerformGetMenuForNode
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected sealed override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (RecycleBinId.ToInvariantString() == id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var menu = new MenuItemCollection();
|
2016-03-11 15:43:34 +01:00
|
|
|
|
menu.Items.Add<ActionEmptyTranscan>(Services.TextService.Localize("actions/emptyTrashcan"));
|
2016-03-15 16:58:13 +01:00
|
|
|
|
menu.Items.Add<ActionRefresh>(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true);
|
2013-08-12 15:06:12 +02:00
|
|
|
|
return menu;
|
|
|
|
|
|
}
|
2017-09-19 15:51:47 +02:00
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
return PerformGetMenuForNode(id, queryStrings);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Based on the allowed actions, this will filter the ones that the current user is allowed
|
|
|
|
|
|
/// </summary>
|
2013-09-26 15:55:38 +10:00
|
|
|
|
/// <param name="menuWithAllItems"></param>
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// <param name="userAllowedMenuItems"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2013-09-26 15:55:38 +10:00
|
|
|
|
protected void FilterUserAllowedMenuItems(MenuItemCollection menuWithAllItems, IEnumerable<MenuItem> userAllowedMenuItems)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
|
|
|
|
|
var userAllowedActions = userAllowedMenuItems.Where(x => x.Action != null).Select(x => x.Action).ToArray();
|
2013-09-26 15:55:38 +10:00
|
|
|
|
|
2013-10-03 15:05:48 +10:00
|
|
|
|
var notAllowed = menuWithAllItems.Items.Where(
|
2013-09-26 15:55:38 +10:00
|
|
|
|
a => (a.Action != null
|
|
|
|
|
|
&& a.Action.CanBePermissionAssigned
|
|
|
|
|
|
&& (a.Action.CanBePermissionAssigned == false || userAllowedActions.Contains(a.Action) == false)))
|
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
//remove the ones that aren't allowed.
|
|
|
|
|
|
foreach (var m in notAllowed)
|
|
|
|
|
|
{
|
2013-10-03 15:05:48 +10:00
|
|
|
|
menuWithAllItems.Items.Remove(m);
|
2013-09-26 15:55:38 +10:00
|
|
|
|
}
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2013-09-26 15:55:38 +10:00
|
|
|
|
internal IEnumerable<MenuItem> GetAllowedUserMenuItemsForNode(IUmbracoEntity dd)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2016-03-16 17:52:08 +01:00
|
|
|
|
var permission = Services.UserService.GetPermissions(Security.CurrentUser, dd.Path);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
var actions = global::Umbraco.Web._Legacy.Actions.Action.FromEntityPermission(permission)
|
|
|
|
|
|
.ToList();
|
2017-09-23 10:08:18 +02:00
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
// A user is allowed to delete their own stuff
|
2018-03-02 15:48:21 +01:00
|
|
|
|
var tryGetCurrentUserId = Security.GetUserId();
|
|
|
|
|
|
if (tryGetCurrentUserId && dd.CreatorId == tryGetCurrentUserId.Result && actions.Contains(ActionDelete.Instance) == false)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
actions.Add(ActionDelete.Instance);
|
|
|
|
|
|
|
2013-09-26 15:55:38 +10:00
|
|
|
|
return actions.Select(x => new MenuItem(x));
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Determins if the user has access to view the node/document
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <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>
|
2016-09-01 19:06:08 +02:00
|
|
|
|
/// <returns></returns>
|
2013-08-12 15:06:12 +02:00
|
|
|
|
internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable<MenuItem> allowedUserOptions)
|
|
|
|
|
|
{
|
|
|
|
|
|
return allowedUserOptions.Select(x => x.Action).OfType<ActionBrowse>().Any();
|
|
|
|
|
|
}
|
2017-05-12 14:49:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-09-19 15:51:47 +02:00
|
|
|
|
/// this will parse the string into either a GUID or INT
|
2017-05-12 14:49:44 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2017-09-19 15:51:47 +02:00
|
|
|
|
internal Tuple<Guid?, int?> GetIdentifierFromString(string id)
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
2017-09-19 15:51:47 +02:00
|
|
|
|
Guid idGuid;
|
|
|
|
|
|
int idInt;
|
|
|
|
|
|
Udi idUdi;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (Guid.TryParse(id, out idGuid))
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
2017-09-19 15:51:47 +02:00
|
|
|
|
return new Tuple<Guid?, int?>(idGuid, null);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
}
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (int.TryParse(id, out idInt))
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
2017-09-19 15:51:47 +02:00
|
|
|
|
return new Tuple<Guid?, int?>(null, idInt);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
}
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (Udi.TryParse(id, out idUdi))
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
|
|
|
|
|
var guidUdi = idUdi as GuidUdi;
|
2017-09-19 15:51:47 +02:00
|
|
|
|
if (guidUdi != null)
|
2017-09-23 10:08:18 +02:00
|
|
|
|
return new Tuple<Guid?, int?>(guidUdi.Guid, null);
|
2017-05-12 14:49:44 +02:00
|
|
|
|
}
|
2017-09-19 15:51:47 +02:00
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get an entity via an id that can be either an integer, Guid or UDI
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// This object has it's own contextual cache for these lookups
|
|
|
|
|
|
/// </remarks>
|
2018-01-15 11:32:30 +01:00
|
|
|
|
internal IEntitySlim GetEntityFromId(string id)
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
|
|
|
|
|
return _entityCache.GetOrAdd(id, s =>
|
2017-05-12 14:49:44 +02:00
|
|
|
|
{
|
2018-01-15 11:32:30 +01:00
|
|
|
|
IEntitySlim entity;
|
2017-05-12 14:49:44 +02:00
|
|
|
|
|
2018-01-15 11:32:30 +01:00
|
|
|
|
if (Guid.TryParse(s, out var idGuid))
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
2018-01-15 11:32:30 +01:00
|
|
|
|
entity = Services.EntityService.Get(idGuid, UmbracoObjectType);
|
2017-09-19 15:51:47 +02:00
|
|
|
|
}
|
2018-01-15 11:32:30 +01:00
|
|
|
|
else if (int.TryParse(s, out var idInt))
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
|
|
|
|
|
entity = Services.EntityService.Get(idInt, UmbracoObjectType);
|
|
|
|
|
|
}
|
2018-01-15 11:32:30 +01:00
|
|
|
|
else if (Udi.TryParse(s, out var idUdi))
|
2017-09-19 15:51:47 +02:00
|
|
|
|
{
|
|
|
|
|
|
var guidUdi = idUdi as GuidUdi;
|
2018-01-15 11:32:30 +01:00
|
|
|
|
entity = guidUdi != null ? Services.EntityService.Get(guidUdi.Guid, UmbracoObjectType) : null;
|
2017-09-19 15:51:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2018-01-15 11:32:30 +01:00
|
|
|
|
entity = null;
|
2017-09-19 15:51:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return entity;
|
|
|
|
|
|
});
|
2017-05-12 14:49:44 +02:00
|
|
|
|
}
|
2017-09-19 15:51:47 +02:00
|
|
|
|
|
2018-01-15 11:32:30 +01:00
|
|
|
|
private readonly ConcurrentDictionary<string, IEntitySlim> _entityCache = new ConcurrentDictionary<string, IEntitySlim>();
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
2017-07-20 11:21:28 +02:00
|
|
|
|
}
|