Files
Umbraco-CMS/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs

470 lines
20 KiB
C#
Raw Normal View History

using System;
2017-09-19 15:51:47 +02:00
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Web.Http;
using Umbraco.Core;
2016-03-11 15:43:34 +01:00
using Umbraco.Core.Services;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.WebApi.Filters;
using System.Globalization;
using Umbraco.Core.Models.Entities;
Merge remote-tracking branch 'origin/dev-v7' into dev-v8 # Conflicts: # src/SolutionInfo.cs # src/Umbraco.Core/Configuration/UmbracoVersion.cs # src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs # src/Umbraco.Core/Persistence/PetaPoco.cs # src/Umbraco.Core/Scoping/NoScope.cs # src/Umbraco.Core/Scoping/ScopeProvider.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/IContentService.cs # src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs # src/Umbraco.Tests/UI/LegacyDialogTests.cs # src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js # src/Umbraco.Web.UI/umbraco/config/create/UI.xml # src/Umbraco.Web.UI/umbraco/config/lang/zh.xml # src/Umbraco.Web/BatchedDatabaseServerMessenger.cs # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/MediaTypeController.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs # src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs # src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs
2018-04-19 23:41:35 +10:00
using Umbraco.Web._Legacy.Actions;
namespace Umbraco.Web.Trees
{
public abstract class ContentTreeControllerBase : TreeController
{
#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;
2018-03-27 10:04:07 +02:00
Guid asGuid = Guid.Empty;
if (int.TryParse(id, out asInt) == false)
{
2018-03-27 10:04:07 +02:00
if (Guid.TryParse(id, out asGuid) == false)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
}
2018-03-27 10:04:07 +02:00
var entity = asGuid == Guid.Empty
? Services.EntityService.Get(asInt, UmbracoObjectType)
: Services.EntityService.Get(asGuid, 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;
}
protected abstract TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings);
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>
internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings)
2017-09-19 15:51:47 +02: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;
}
/// <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; }
/// <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
protected virtual TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings)
{
var nodes = new TreeNodeCollection();
2017-09-19 15:51:47 +02:00
var rootIdString = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
2018-03-27 10:04:07 +02:00
var hasAccessToRoot = UserStartNodes.Contains(Constants.System.Root);
var startNodeId = queryStrings.HasKey(TreeQueryStringParameters.StartNodeId)
? queryStrings.GetValue<string>(TreeQueryStringParameters.StartNodeId)
: string.Empty;
2018-03-27 10:04:07 +02:00
if (string.IsNullOrEmpty(startNodeId) == false && startNodeId != "undefined" && startNodeId != rootIdString)
{
2018-03-27 10:04:07 +02:00
// request has been made to render from a specific, non-root, start node
id = startNodeId;
2018-03-27 10:04:07 +02:00
// 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)
{
2018-03-27 10:04:07 +02:00
Logger.Warn<ContentTreeControllerBase>("User " + Security.CurrentUser.Username + " does not have access to node with id " + id);
return nodes;
}
2018-03-27 10:04:07 +02:00
// 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;
}
2018-03-27 10:04:07 +02:00
// get child entities - if id is root, but user's start nodes do not contain the
2018-01-17 10:47:35 +01:00
// root node, this returns the start nodes instead of root's children
var culture = queryStrings["culture"].TryConvertTo<string>();
var entities = GetChildEntities(id, culture.Success ? culture.Result : null).ToList();
2018-03-27 10:04:07 +02:00
nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null));
2017-09-19 15:51:47 +02:00
2018-03-27 10:04:07 +02:00
// 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)
2017-09-19 15:51:47 +02:00
{
2018-03-27 10:04:07 +02:00
var topNodeIds = entities.Where(x => x.Level > 1).Select(GetTopNodeId).Where(x => x != 0).Distinct().ToArray();
if (topNodeIds.Length > 0)
2017-09-19 15:51:47 +02:00
{
2018-03-27 10:04:07 +02:00
var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray());
nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null));
2017-09-19 15:51:47 +02:00
}
}
return nodes;
2017-09-23 10:08:18 +02:00
}
2018-03-27 10:04:07 +02:00
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);
protected abstract UmbracoObjectTypes UmbracoObjectType { get; }
protected IEnumerable<IEntitySlim> GetChildEntities(string id, string culture)
{
2018-03-27 10:04:07 +02:00
// try to parse id as an integer else use GetEntityFromId
// which will grok Guids, Udis, etc and let use obtain the id
if (int.TryParse(id, out var entityId) == false)
{
2018-03-27 10:04:07 +02:00
var entity = GetEntityFromId(id);
if (entity == null)
2017-05-12 14:49:44 +02:00
throw new HttpResponseException(HttpStatusCode.NotFound);
2018-03-27 10:04:07 +02:00
entityId = entity.Id;
}
Merge remote-tracking branch 'origin/dev-v7' into dev-v8 # Conflicts: # src/SolutionInfo.cs # src/Umbraco.Core/Configuration/UmbracoVersion.cs # src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs # src/Umbraco.Core/Persistence/PetaPoco.cs # src/Umbraco.Core/Scoping/NoScope.cs # src/Umbraco.Core/Scoping/ScopeProvider.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/IContentService.cs # src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs # src/Umbraco.Tests/UI/LegacyDialogTests.cs # src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js # src/Umbraco.Web.UI/umbraco/config/create/UI.xml # src/Umbraco.Web.UI/umbraco/config/lang/zh.xml # src/Umbraco.Web/BatchedDatabaseServerMessenger.cs # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/MediaTypeController.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs # src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs # src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs
2018-04-19 23:41:35 +10:00
IEntitySlim[] result;
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
2018-03-27 10:04:07 +02:00
if (entityId == Constants.System.Root && UserStartNodes.Contains(Constants.System.Root) == false)
{
result = UserStartNodes.Length > 0
? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes).ToArray()
: Array.Empty<IEntitySlim>();
Merge remote-tracking branch 'origin/dev-v7' into dev-v8 # Conflicts: # src/SolutionInfo.cs # src/Umbraco.Core/Configuration/UmbracoVersion.cs # src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs # src/Umbraco.Core/Persistence/PetaPoco.cs # src/Umbraco.Core/Scoping/NoScope.cs # src/Umbraco.Core/Scoping/ScopeProvider.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/IContentService.cs # src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs # src/Umbraco.Tests/UI/LegacyDialogTests.cs # src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js # src/Umbraco.Web.UI/umbraco/config/create/UI.xml # src/Umbraco.Web.UI/umbraco/config/lang/zh.xml # src/Umbraco.Web/BatchedDatabaseServerMessenger.cs # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/MediaTypeController.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs # src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs # src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs
2018-04-19 23:41:35 +10:00
}
else
{
result = Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray();
}
2018-04-26 16:03:08 +02:00
//This should really never be null, but we'll error check anyways
culture = culture ?? Services.LocalizationService.GetDefaultLanguageIsoCode();
//Try to see if there is a variant name for the current language for the item and set the name accordingly.
//If any of this fails, the tree node name will remain the default invariant culture name.
//fixme - what if there is no name found at all ? This could occur if the doc type is variant and the user fills in all language values, then creates a new lang and sets it as the default
//fixme - what if the user changes this document type to not allow culture variants after it's already been created with culture variants, this means we'll be displaying the culture variant name when in fact we should be displaying the invariant name... but that would be null
if (!culture.IsNullOrWhiteSpace())
Merge remote-tracking branch 'origin/dev-v7' into dev-v8 # Conflicts: # src/SolutionInfo.cs # src/Umbraco.Core/Configuration/UmbracoVersion.cs # src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs # src/Umbraco.Core/Persistence/PetaPoco.cs # src/Umbraco.Core/Scoping/NoScope.cs # src/Umbraco.Core/Scoping/ScopeProvider.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/IContentService.cs # src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs # src/Umbraco.Tests/UI/LegacyDialogTests.cs # src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js # src/Umbraco.Web.UI/umbraco/config/create/UI.xml # src/Umbraco.Web.UI/umbraco/config/lang/zh.xml # src/Umbraco.Web/BatchedDatabaseServerMessenger.cs # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/MediaTypeController.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs # src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs # src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs
2018-04-19 23:41:35 +10:00
{
foreach (var e in result)
{
if (e is IDocumentEntitySlim doc)
{
if (doc.CultureNames.TryGetValue(culture, out var name))
{
e.Name = name;
}
Merge remote-tracking branch 'origin/dev-v7' into dev-v8 # Conflicts: # src/SolutionInfo.cs # src/Umbraco.Core/Configuration/UmbracoVersion.cs # src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs # src/Umbraco.Core/Persistence/PetaPoco.cs # src/Umbraco.Core/Scoping/NoScope.cs # src/Umbraco.Core/Scoping/ScopeProvider.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/IContentService.cs # src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs # src/Umbraco.Tests/UI/LegacyDialogTests.cs # src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js # src/Umbraco.Web.UI/umbraco/config/create/UI.xml # src/Umbraco.Web.UI/umbraco/config/lang/zh.xml # src/Umbraco.Web/BatchedDatabaseServerMessenger.cs # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/MediaTypeController.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs # src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs # src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web/umbraco.presentation/umbraco/create/MemberGroupTasks.cs
2018-04-19 23:41:35 +10:00
}
}
}
return result;
}
/// <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
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);
}
/// <summary>
/// Ensures the recycle bin is appended when required (i.e. user has access to the root and it's not in dialog mode)
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
/// <remarks>
/// This method is overwritten strictly to render the recycle bin, it should serve no other purpose
/// </remarks>
protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
{
//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))
{
var altStartId = string.Empty;
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
if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.Root.ToString(CultureInfo.InvariantCulture))
{
id = altStartId;
}
var nodes = GetTreeNodesInternal(id, queryStrings);
//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())
{
nodes.Add(CreateTreeNode(
RecycleBinId.ToInvariantString(),
id,
queryStrings,
2016-03-11 15:56:00 +01:00
Services.TextService.Localize("general/recycleBin"),
"icon-trash",
RecycleBinSmells,
queryStrings.GetValue<string>("application") + TreeAlias.EnsureStartsWith('/') + "/recyclebin"));
}
return nodes;
}
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
//test if the parent is a listview / container
if (current != null && current.IsContainer)
{
//no children!
return new TreeNodeCollection();
}
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);
return menu;
}
2017-09-19 15:51:47 +02:00
return PerformGetMenuForNode(id, queryStrings);
}
/// <summary>
/// Based on the allowed actions, this will filter the ones that the current user is allowed
/// </summary>
/// <param name="menuWithAllItems"></param>
/// <param name="userAllowedMenuItems"></param>
/// <returns></returns>
protected void FilterUserAllowedMenuItems(MenuItemCollection menuWithAllItems, IEnumerable<MenuItem> userAllowedMenuItems)
{
var userAllowedActions = userAllowedMenuItems.Where(x => x.Action != null).Select(x => x.Action).ToArray();
var notAllowed = menuWithAllItems.Items.Where(
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)
{
menuWithAllItems.Items.Remove(m);
}
}
internal IEnumerable<MenuItem> GetAllowedUserMenuItemsForNode(IUmbracoEntity dd)
{
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
// 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)
actions.Add(ActionDelete.Instance);
return actions.Select(x => new MenuItem(x));
}
/// <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, string culture)
{
//TODO: At some stage when we implement permissions on languages we'll need to take care of culture
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>
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
{
IEntitySlim entity;
2017-05-12 14:49:44 +02:00
if (Guid.TryParse(s, out var idGuid))
2017-09-19 15:51:47 +02:00
{
entity = Services.EntityService.Get(idGuid, UmbracoObjectType);
2017-09-19 15:51:47 +02:00
}
else if (int.TryParse(s, out var idInt))
2017-09-19 15:51:47 +02:00
{
entity = Services.EntityService.Get(idInt, UmbracoObjectType);
}
else if (Udi.TryParse(s, out var idUdi))
2017-09-19 15:51:47 +02:00
{
var guidUdi = idUdi as GuidUdi;
entity = guidUdi != null ? Services.EntityService.Get(guidUdi.Guid, UmbracoObjectType) : null;
2017-09-19 15:51:47 +02:00
}
else
{
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
private readonly ConcurrentDictionary<string, IEntitySlim> _entityCache = new ConcurrentDictionary<string, IEntitySlim>();
}
2017-07-20 11:21:28 +02:00
}