2013-08-12 15:06:12 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
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;
|
|
|
|
|
|
using Umbraco.Core.Models;
|
|
|
|
|
|
using Umbraco.Core.Models.EntityBase;
|
2014-02-17 11:50:13 +11:00
|
|
|
|
using Umbraco.Core.Persistence;
|
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-08-12 15:06:12 +02:00
|
|
|
|
using umbraco;
|
|
|
|
|
|
using umbraco.BusinessLogic.Actions;
|
2013-11-18 22:29:19 +01:00
|
|
|
|
using System.Globalization;
|
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
|
|
|
|
|
|
|
|
|
|
|
|
protected abstract TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings);
|
|
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Returns the
|
|
|
|
|
|
/// </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>
|
|
|
|
|
|
protected abstract int UserStartNode { get; }
|
|
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
protected abstract TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings);
|
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; }
|
|
|
|
|
|
|
|
|
|
|
|
protected IEnumerable<IUmbracoEntity> GetChildEntities(string id)
|
|
|
|
|
|
{
|
|
|
|
|
|
int iid;
|
|
|
|
|
|
if (int.TryParse(id, out iid) == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidCastException("The id for the media tree must be an integer");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//if a request is made for the root node data but the user's start node is not the default, then
|
|
|
|
|
|
// we need to return their start node data
|
2013-10-01 16:38:10 +10:00
|
|
|
|
if (iid == Constants.System.Root && UserStartNode != Constants.System.Root)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2013-10-01 16:38:10 +10:00
|
|
|
|
//just return their single start node, it will show up under the 'Content' label
|
|
|
|
|
|
var startNode = Services.EntityService.Get(UserStartNode, UmbracoObjectType);
|
2014-02-17 11:50:13 +11:00
|
|
|
|
if (startNode == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new EntityNotFoundException(UserStartNode, "User's start content node could not be found");
|
|
|
|
|
|
}
|
2014-01-17 13:00:11 +11:00
|
|
|
|
return new[] { startNode };
|
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>
|
|
|
|
|
|
protected abstract bool HasPathAccess(string id, FormDataCollection queryStrings);
|
|
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// This will automatically check if the recycle bin needs to be rendered (i.e. its the first level)
|
|
|
|
|
|
/// and will automatically append it to the result of GetChildNodes.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="id"></param>
|
|
|
|
|
|
/// <param name="queryStrings"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
|
|
|
|
|
{
|
2013-11-19 12:28:50 +11:00
|
|
|
|
//check if we're rendering the root
|
2014-08-08 10:54:05 +02:00
|
|
|
|
if (id == Constants.System.Root.ToInvariantString())
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2014-08-08 10:54:05 +02:00
|
|
|
|
//when rendering the root, 3 things can happen:
|
|
|
|
|
|
//1. we return -1 children without modifications
|
|
|
|
|
|
//2. the user has a non -1 content root set and we return that
|
|
|
|
|
|
//3. the tree has a non -1 content root set and we return that - if the user has access to it.
|
|
|
|
|
|
|
|
|
|
|
|
var hasUserRoot = UserStartNode != Constants.System.Root;
|
|
|
|
|
|
var hasTreeRoot = queryStrings.HasKey(TreeQueryStringParameters.StartNodeId);
|
2014-01-17 13:00:11 +11:00
|
|
|
|
|
2014-08-08 10:54:05 +02:00
|
|
|
|
//initial id
|
|
|
|
|
|
var idToLoad = id;
|
2013-11-19 11:06:09 +01:00
|
|
|
|
|
2014-08-08 10:54:05 +02:00
|
|
|
|
//user permission override root
|
|
|
|
|
|
if (hasUserRoot)
|
2014-08-08 12:08:34 -06:00
|
|
|
|
idToLoad = UserStartNode.ToString(CultureInfo.InvariantCulture);
|
2013-11-18 22:29:19 +01:00
|
|
|
|
|
2014-08-08 10:54:05 +02:00
|
|
|
|
//tree overrides root
|
|
|
|
|
|
if (hasTreeRoot)
|
2013-11-19 12:28:50 +11:00
|
|
|
|
{
|
2014-08-08 10:54:05 +02:00
|
|
|
|
//but only if the user is allowed to access this node
|
2014-08-08 12:08:34 -06:00
|
|
|
|
var altId = queryStrings.GetValue<string>(TreeQueryStringParameters.StartNodeId);
|
2013-11-18 22:29:19 +01:00
|
|
|
|
|
2014-08-08 10:54:05 +02:00
|
|
|
|
//so if we dont have a user content root or the user has access
|
2014-08-08 12:08:34 -06:00
|
|
|
|
if (hasUserRoot == false || HasPathAccess(altId, queryStrings))
|
2013-12-04 15:54:50 +11:00
|
|
|
|
{
|
2014-08-08 12:08:34 -06:00
|
|
|
|
idToLoad = altId;
|
2013-11-19 12:28:50 +11:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2014-08-08 10:54:05 +02:00
|
|
|
|
|
|
|
|
|
|
//load whatever root nodes we concluded was the user/tree root
|
2014-08-08 12:08:34 -06:00
|
|
|
|
var nodes = GetTreeNodesInternal(idToLoad, queryStrings);
|
2013-09-04 17:36:05 +10:00
|
|
|
|
|
2014-08-08 12:08:34 -06:00
|
|
|
|
//only render the recycle bin if we are not in dialog and the start id is still the root
|
2014-08-08 10:54:05 +02:00
|
|
|
|
if (IsDialog(queryStrings) == false && idToLoad == 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-08-08 10:54:05 +02:00
|
|
|
|
idToLoad,
|
2013-09-04 17:36:05 +10:00
|
|
|
|
queryStrings,
|
|
|
|
|
|
ui.GetText("general", "recycleBin"),
|
|
|
|
|
|
"icon-trash",
|
|
|
|
|
|
RecycleBinSmells,
|
2014-06-08 16:15:28 +01:00
|
|
|
|
queryStrings.GetValue<string>("application") + TreeAlias.EnsureStartsWith('/') + "/recyclebin"));
|
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)
|
|
|
|
|
|
{
|
2013-11-15 16:19:46 +11:00
|
|
|
|
//before we get the children we need to see if this is a container node
|
2013-11-15 16:56:51 +11:00
|
|
|
|
var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectType);
|
2014-01-17 13:00:11 +11:00
|
|
|
|
|
2014-01-28 15:49:04 +01:00
|
|
|
|
//test if the parent is a listview / container
|
|
|
|
|
|
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();
|
2013-10-09 17:35:16 +11:00
|
|
|
|
menu.Items.Add<ActionEmptyTranscan>(ui.Text("actions", "emptyTrashcan"));
|
2013-10-03 15:05:48 +10:00
|
|
|
|
menu.Items.Add<ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true);
|
2013-08-12 15:06:12 +02:00
|
|
|
|
return menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
|
|
var actions = global::umbraco.BusinessLogic.Actions.Action.FromString(UmbracoUser.GetPermissions(dd.Path));
|
|
|
|
|
|
|
|
|
|
|
|
// A user is allowed to delete their own stuff
|
|
|
|
|
|
if (dd.CreatorId == UmbracoUser.Id && actions.Contains(ActionDelete.Instance) == false)
|
|
|
|
|
|
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>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable<MenuItem> allowedUserOptions)
|
|
|
|
|
|
{
|
|
|
|
|
|
return allowedUserOptions.Select(x => x.Action).OfType<ActionBrowse>().Any();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2013-07-30 14:23:39 +10:00
|
|
|
|
}
|