2018-06-29 19:52:40 +02:00
using System ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Linq ;
using System.Net ;
2020-09-21 09:52:58 +02:00
using Microsoft.Extensions.Logging ;
2018-06-29 19:52:40 +02:00
using Umbraco.Core ;
using Umbraco.Core.Services ;
using Umbraco.Core.Models ;
using Umbraco.Web.Models.Trees ;
using Umbraco.Core.Models.Entities ;
2020-06-09 07:49:26 +02:00
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.Mvc ;
2018-10-29 17:27:33 +11:00
using Umbraco.Web.Actions ;
2019-06-28 13:24:13 +10:00
using Umbraco.Core.Security ;
2020-06-09 07:49:26 +02:00
using Umbraco.Extensions ;
using Umbraco.Web.Common.Exceptions ;
using Umbraco.Web.Common.ModelBinders ;
using Umbraco.Web.Security ;
2020-10-26 13:34:08 +01:00
using Umbraco.Web.Trees ;
2020-06-09 07:49:26 +02:00
using Umbraco.Web.WebApi ;
2018-06-29 19:52:40 +02:00
2020-10-26 13:34:08 +01:00
namespace Umbraco.Web.BackOffice.Trees
2018-06-29 19:52:40 +02:00
{
2020-02-26 10:33:05 +01:00
public abstract class ContentTreeControllerBase : TreeController , ITreeNodeController
2018-06-29 19:52:40 +02:00
{
2020-06-09 07:49:26 +02:00
private readonly IEntityService _entityService ;
2020-10-21 16:51:00 +11:00
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor ;
2020-09-21 09:52:58 +02:00
private readonly ILogger < ContentTreeControllerBase > _logger ;
2020-06-09 07:49:26 +02:00
private readonly ActionCollection _actionCollection ;
private readonly IUserService _userService ;
private readonly IDataTypeService _dataTypeService ;
2020-02-17 14:58:34 +01:00
public IMenuItemCollectionFactory MenuItemCollectionFactory { get ; }
2019-10-15 00:07:44 +11:00
2020-02-14 13:04:49 +01:00
protected ContentTreeControllerBase (
2020-06-09 07:49:26 +02:00
ILocalizedTextService localizedTextService ,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection ,
IMenuItemCollectionFactory menuItemCollectionFactory ,
IEntityService entityService ,
2020-10-21 16:51:00 +11:00
IBackOfficeSecurityAccessor backofficeSecurityAccessor ,
2020-09-21 09:52:58 +02:00
ILogger < ContentTreeControllerBase > logger ,
2020-06-09 07:49:26 +02:00
ActionCollection actionCollection ,
IUserService userService ,
IDataTypeService dataTypeService
)
: base ( localizedTextService , umbracoApiControllerTypeCollection )
2019-10-15 00:07:44 +11:00
{
2020-06-09 07:49:26 +02:00
_entityService = entityService ;
2020-09-22 10:01:00 +02:00
_backofficeSecurityAccessor = backofficeSecurityAccessor ;
2020-06-09 07:49:26 +02:00
_logger = logger ;
_actionCollection = actionCollection ;
_userService = userService ;
_dataTypeService = dataTypeService ;
2020-02-17 14:58:34 +01:00
MenuItemCollectionFactory = menuItemCollectionFactory ;
2019-10-15 00:07:44 +11:00
}
2018-06-29 19:52:40 +02:00
#region Actions
/// <summary>
/// Gets an individual tree node
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
2020-09-08 13:12:36 +02:00
public ActionResult < TreeNode > GetTreeNode ( [ FromRoute ] string id , [ ModelBinder ( typeof ( HttpQueryStringModelBinder ) ) ] FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
int asInt ;
Guid asGuid = Guid . Empty ;
if ( int . TryParse ( id , out asInt ) = = false )
{
if ( Guid . TryParse ( id , out asGuid ) = = false )
{
2020-06-09 07:49:26 +02:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2018-05-10 18:20:33 +10:00
}
2018-06-29 19:52:40 +02:00
var entity = asGuid = = Guid . Empty
2020-06-09 07:49:26 +02:00
? _entityService . Get ( asInt , UmbracoObjectType )
: _entityService . Get ( asGuid , UmbracoObjectType ) ;
2018-06-29 19:52:40 +02:00
if ( entity = = null )
{
2020-06-09 07:49:26 +02:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
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
/// <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>
2020-06-09 07:49:26 +02:00
protected override TreeNode CreateRootNode ( FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
var node = base . CreateRootNode ( queryStrings ) ;
2019-11-05 13:45:42 +01:00
if ( IsDialog ( queryStrings ) & & UserStartNodes . Contains ( Constants . System . Root ) = = false & & IgnoreUserStartNodes ( queryStrings ) = = false )
2018-06-29 19:52:40 +02:00
{
node . AdditionalData [ "noAccess" ] = true ;
}
return node ;
}
2020-06-09 07:49:26 +02:00
protected abstract TreeNode GetSingleTreeNode ( IEntitySlim entity , string parentId , FormCollection queryStrings ) ;
2018-06-29 19:52:40 +02:00
/// <summary>
/// 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>
/// <param name="parentId"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
2020-06-09 07:49:26 +02:00
internal TreeNode GetSingleTreeNodeWithAccessCheck ( IEntitySlim e , string parentId , FormCollection queryStrings ,
2019-07-01 18:23:36 +10:00
int [ ] startNodeIds , string [ ] startNodePaths )
2018-06-29 19:52:40 +02:00
{
2019-06-28 13:24:13 +10:00
var entityIsAncestorOfStartNodes = ContentPermissionsHelper . IsInBranchOfStartNode ( e . Path , startNodeIds , startNodePaths , out var hasPathAccess ) ;
2019-07-01 18:23:36 +10:00
var ignoreUserStartNodes = IgnoreUserStartNodes ( queryStrings ) ;
2019-06-28 13:24:13 +10:00
if ( ignoreUserStartNodes = = false & & entityIsAncestorOfStartNodes = = false )
2018-06-29 19:52:40 +02:00
return null ;
var treeNode = GetSingleTreeNode ( e , parentId , queryStrings ) ;
2019-06-28 13:24:13 +10:00
if ( treeNode = = null )
{
//this means that the user has NO access to this node via permissions! They at least need to have browse permissions to see
//the node so we need to return null;
return null ;
}
2019-07-01 18:23:36 +10:00
if ( ! ignoreUserStartNodes & & ! hasPathAccess )
2018-06-29 19:52:40 +02:00
{
treeNode . AdditionalData [ "noAccess" ] = true ;
}
return treeNode ;
}
2019-06-28 13:03:36 +10:00
private void GetUserStartNodes ( out int [ ] startNodeIds , out string [ ] startNodePaths )
{
switch ( RecycleBinId )
{
2019-11-05 13:45:42 +01:00
case Constants . System . RecycleBinMedia :
2020-10-21 16:51:00 +11:00
startNodeIds = _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . CalculateMediaStartNodeIds ( _entityService ) ;
startNodePaths = _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . GetMediaStartNodePaths ( _entityService ) ;
2019-06-28 13:03:36 +10:00
break ;
2019-11-05 13:45:42 +01:00
case Constants . System . RecycleBinContent :
2020-10-21 16:51:00 +11:00
startNodeIds = _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . CalculateContentStartNodeIds ( _entityService ) ;
startNodePaths = _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . GetContentStartNodePaths ( _entityService ) ;
2019-06-28 13:03:36 +10:00
break ;
default :
throw new NotSupportedException ( "Path access is only determined on content or media" ) ;
}
}
2018-06-29 19:52:40 +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 ; }
/// <summary>
/// Returns the user's start node for this tree
/// </summary>
protected abstract int [ ] UserStartNodes { get ; }
2020-06-09 07:49:26 +02:00
protected virtual TreeNodeCollection PerformGetTreeNodes ( string id , FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
var nodes = new TreeNodeCollection ( ) ;
2019-11-05 13:45:42 +01:00
var rootIdString = Constants . System . RootString ;
var hasAccessToRoot = UserStartNodes . Contains ( Constants . System . Root ) ;
2018-06-29 19:52:40 +02:00
var startNodeId = queryStrings . HasKey ( TreeQueryStringParameters . StartNodeId )
? queryStrings . GetValue < string > ( TreeQueryStringParameters . StartNodeId )
: string . Empty ;
2019-06-28 13:03:36 +10:00
var ignoreUserStartNodes = IgnoreUserStartNodes ( queryStrings ) ;
2018-06-29 19:52:40 +02:00
if ( string . IsNullOrEmpty ( startNodeId ) = = false & & startNodeId ! = "undefined" & & startNodeId ! = rootIdString )
{
// request has been made to render from a specific, non-root, start node
id = startNodeId ;
// 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
2019-06-28 13:03:36 +10:00
if ( ignoreUserStartNodes = = false & & HasPathAccess ( id , queryStrings ) = = false )
2018-06-29 19:52:40 +02:00
{
2020-10-21 16:51:00 +11:00
_logger . LogWarning ( "User {Username} does not have access to node with id {Id}" , _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . Username , id ) ;
2018-06-29 19:52:40 +02:00
return nodes ;
}
// 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 , queryStrings ) . ToList ( ) ;
2019-06-28 13:03:36 +10:00
//get the current user start node/paths
GetUserStartNodes ( out var userStartNodes , out var userStartNodePaths ) ;
2018-06-29 19:52:40 +02:00
// if the user does not have access to the root node, what we have is the start nodes,
2019-10-22 14:13:02 +02:00
// but to provide some context we need to add their topmost nodes when they are not
2018-06-29 19:52:40 +02:00
// topmost nodes themselves (level > 1).
if ( id = = rootIdString & & hasAccessToRoot = = false )
{
2019-10-22 14:13:02 +02:00
// first add the entities that are topmost to the nodes collection
var topMostEntities = entities . Where ( x = > x . Level = = 1 ) . ToArray ( ) ;
nodes . AddRange ( topMostEntities . Select ( x = > GetSingleTreeNodeWithAccessCheck ( x , id , queryStrings , userStartNodes , userStartNodePaths ) ) . Where ( x = > x ! = null ) ) ;
// now add the topmost nodes of the entities that aren't topmost to the nodes collection as well
// - these will appear as "no-access" nodes in the tree, but will allow the editors to drill down through the tree
// until they reach their start nodes
var topNodeIds = entities . Except ( topMostEntities ) . Select ( GetTopNodeId ) . Where ( x = > x ! = 0 ) . Distinct ( ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
if ( topNodeIds . Length > 0 )
{
2020-06-09 07:49:26 +02:00
var topNodes = _entityService . GetAll ( UmbracoObjectType , topNodeIds . ToArray ( ) ) ;
2019-07-01 18:23:36 +10:00
nodes . AddRange ( topNodes . Select ( x = > GetSingleTreeNodeWithAccessCheck ( x , id , queryStrings , userStartNodes , userStartNodePaths ) ) . Where ( x = > x ! = null ) ) ;
2018-06-29 19:52:40 +02:00
}
}
2019-10-22 14:13:02 +02:00
else
{
// the user has access to the root, just add the entities
nodes . AddRange ( entities . Select ( x = > GetSingleTreeNodeWithAccessCheck ( x , id , queryStrings , userStartNodes , userStartNodePaths ) ) . Where ( x = > x ! = null ) ) ;
}
2018-06-29 19:52:40 +02:00
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 ;
}
2020-06-09 07:49:26 +02:00
protected abstract MenuItemCollection PerformGetMenuForNode ( string id , FormCollection queryStrings ) ;
2018-06-29 19:52:40 +02:00
protected abstract UmbracoObjectTypes UmbracoObjectType { get ; }
2020-06-09 07:49:26 +02:00
protected virtual IEnumerable < IEntitySlim > GetChildEntities ( string id , FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
// try to parse id as an integer else use GetEntityFromId
// which will grok Guids, Udis, etc and let use obtain the id
2019-07-01 18:23:36 +10:00
if ( ! int . TryParse ( id , out var entityId ) )
2018-06-29 19:52:40 +02:00
{
var entity = GetEntityFromId ( id ) ;
if ( entity = = null )
throw new HttpResponseException ( HttpStatusCode . NotFound ) ;
entityId = entity . Id ;
}
2019-07-01 18:23:36 +10:00
var ignoreUserStartNodes = IgnoreUserStartNodes ( queryStrings ) ;
2018-06-29 19:52:40 +02:00
IEntitySlim [ ] result ;
// if a request is made for the root node but user has no access to
// root node, return start nodes instead
2019-11-05 13:45:42 +01:00
if ( ! ignoreUserStartNodes & & entityId = = Constants . System . Root & & UserStartNodes . Contains ( Constants . System . Root ) = = false )
2018-06-29 19:52:40 +02:00
{
result = UserStartNodes . Length > 0
2020-06-09 07:49:26 +02:00
? _entityService . GetAll ( UmbracoObjectType , UserStartNodes ) . ToArray ( )
2018-06-29 19:52:40 +02:00
: Array . Empty < IEntitySlim > ( ) ;
}
else
{
2018-11-19 17:54:36 +11:00
result = GetChildrenFromEntityService ( entityId ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
}
return result ;
}
2019-06-28 15:03:52 +10:00
private IEnumerable < IEntitySlim > GetChildrenFromEntityService ( int entityId )
2020-06-09 07:49:26 +02:00
= > _entityService . GetChildren ( entityId , UmbracoObjectType ) . ToList ( ) ;
2018-11-19 15:32:26 +11:00
2018-06-29 19:52:40 +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="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
//we should remove this in v8, it's now here for backwards compat only
2020-06-09 07:49:26 +02:00
protected abstract bool HasPathAccess ( string id , FormCollection queryStrings ) ;
2018-06-29 19:52:40 +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>
2020-06-09 07:49:26 +02:00
protected bool HasPathAccess ( IUmbracoEntity entity , FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
if ( entity = = null ) return false ;
2019-11-05 13:45:42 +01:00
return RecycleBinId = = Constants . System . RecycleBinContent
2020-10-21 16:51:00 +11:00
? _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . HasContentPathAccess ( entity , _entityService )
: _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . HasMediaPathAccess ( entity , _entityService ) ;
2018-06-29 19:52:40 +02:00
}
/// <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>
2020-06-09 07:49:26 +02:00
protected sealed override TreeNodeCollection GetTreeNodes ( string id , FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
//check if we're rendering the root
2019-11-05 13:45:42 +01:00
if ( id = = Constants . System . RootString & & UserStartNodes . Contains ( Constants . System . Root ) )
2018-06-29 19:52:40 +02:00
{
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
2019-11-05 13:45:42 +01:00
if ( string . IsNullOrEmpty ( altStartId ) = = false & & altStartId ! = "undefined" & & altStartId ! = Constants . System . RootString )
2018-06-29 19:52:40 +02:00
{
id = altStartId ;
}
var nodes = GetTreeNodesInternal ( id , queryStrings ) ;
2020-05-05 09:41:04 +02:00
//only render the recycle bin if we are not in dialog and the start id is still the root
//we need to check for the "application" key in the queryString because its value is required here,
2020-06-09 07:49:26 +02:00
//and for some reason when there are no dashboards, this parameter is missing
2020-05-05 09:41:04 +02:00
if ( IsDialog ( queryStrings ) = = false & & id = = Constants . System . RootString & & queryStrings . HasKey ( "application" ) )
2018-06-29 19:52:40 +02:00
{
nodes . Add ( CreateTreeNode (
RecycleBinId . ToInvariantString ( ) ,
id ,
queryStrings ,
2020-06-09 07:49:26 +02:00
LocalizedTextService . Localize ( "general/recycleBin" ) ,
2018-06-29 19:52:40 +02:00
"icon-trash" ,
RecycleBinSmells ,
2019-01-30 19:48:42 +11:00
queryStrings . GetRequiredValue < string > ( "application" ) + TreeAlias . EnsureStartsWith ( '/' ) + "/recyclebin" ) ) ;
2018-06-29 19:52:40 +02:00
}
return nodes ;
}
return GetTreeNodesInternal ( id , queryStrings ) ;
}
2018-07-11 15:58:48 +10:00
/// <summary>
/// Check to see if we should return children of a container node
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
/// <remarks>
/// This is required in case a user has custom start nodes that are children of a list view since in that case we'll need to render the tree node. In normal cases we don't render
/// children of a list view.
/// </remarks>
protected bool ShouldRenderChildrenOfContainer ( IEntitySlim e )
{
var isContainer = e . IsContainer ;
var renderChildren = e . HasChildren & & ( isContainer = = false ) ;
//Here we need to figure out if the node is a container and if so check if the user has a custom start node, then check if that start node is a child
// of this container node. If that is true, the HasChildren must be true so that the tree node still renders even though this current node is a container/list view.
2019-11-05 13:45:42 +01:00
if ( isContainer & & UserStartNodes . Length > 0 & & UserStartNodes . Contains ( Constants . System . Root ) = = false )
2019-07-03 08:33:30 +02:00
{
2020-06-09 07:49:26 +02:00
var startNodes = _entityService . GetAll ( UmbracoObjectType , UserStartNodes ) ;
2018-07-11 15:58:48 +10:00
//if any of these start nodes' parent is current, then we need to render children normally so we need to switch some logic and tell
// the UI that this node does have children and that it isn't a container
2020-02-12 11:44:57 +11:00
if ( startNodes . Any ( x = >
{
var pathParts = x . Path . Split ( ',' ) ;
return pathParts . Contains ( e . Id . ToInvariantString ( ) ) ;
} ) )
2018-07-11 15:58:48 +10:00
{
renderChildren = true ;
}
}
return renderChildren ;
}
2018-06-29 19:52:40 +02:00
/// <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>
2020-06-09 07:49:26 +02:00
private TreeNodeCollection GetTreeNodesInternal ( string id , FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
var current = GetEntityFromId ( id ) ;
//before we get the children we need to see if this is a container node
//test if the parent is a listview / container
2018-07-11 15:58:48 +10:00
if ( current ! = null & & ShouldRenderChildrenOfContainer ( current ) = = false )
2018-06-29 19:52:40 +02:00
{
//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>
2020-06-09 07:49:26 +02:00
protected sealed override MenuItemCollection GetMenuForNode ( string id , FormCollection queryStrings )
2018-06-29 19:52:40 +02:00
{
if ( RecycleBinId . ToInvariantString ( ) = = id )
{
2019-01-22 22:46:15 +01:00
// get the default assigned permissions for this user
var deleteAllowed = false ;
2020-06-09 07:49:26 +02:00
var deleteAction = _actionCollection . FirstOrDefault ( y = > y . Letter = = ActionDelete . ActionLetter ) ;
2019-01-22 22:46:15 +01:00
if ( deleteAction ! = null )
{
2020-10-21 16:51:00 +11:00
var perms = _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser . GetPermissions ( Constants . System . RecycleBinContentString , _userService ) ;
2019-01-22 22:46:15 +01:00
deleteAllowed = perms . FirstOrDefault ( x = > x . Contains ( deleteAction . Letter ) ) ! = null ;
}
2020-02-17 14:58:34 +01:00
var menu = MenuItemCollectionFactory . Create ( ) ;
2019-07-03 08:33:30 +02:00
// only add empty recycle bin if the current user is allowed to delete by default
2019-01-22 22:46:15 +01:00
if ( deleteAllowed )
2018-10-29 23:23:21 +11:00
{
2020-06-09 07:49:26 +02:00
menu . Items . Add ( new MenuItem ( "emptyRecycleBin" , LocalizedTextService )
2019-01-22 22:46:15 +01:00
{
Icon = "trash" ,
OpensDialog = true
} ) ;
2020-06-09 07:49:26 +02:00
menu . Items . Add ( new RefreshNode ( LocalizedTextService , true ) ) ;
2019-01-22 22:46:15 +01:00
}
2018-06-29 19:52:40 +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>
/// <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 ) ;
2018-12-06 17:13:23 +11:00
// if the disallowed action is set as default action, make sure to reset the default action as well
if ( menuWithAllItems . DefaultMenuAlias = = m . Alias )
{
menuWithAllItems . DefaultMenuAlias = null ;
}
2018-06-29 19:52:40 +02:00
}
}
internal IEnumerable < MenuItem > GetAllowedUserMenuItemsForNode ( IUmbracoEntity dd )
{
2020-10-21 16:51:00 +11:00
var permissionsForPath = _userService . GetPermissionsForPath ( _backofficeSecurityAccessor . BackOfficeSecurity . CurrentUser , dd . Path ) . GetAllPermissions ( ) ;
2020-06-09 07:49:26 +02:00
return _actionCollection . GetByLetters ( permissionsForPath ) . Select ( x = > new MenuItem ( x ) ) ;
2018-06-29 19:52:40 +02:00
}
/// <summary>
2019-01-26 10:52:19 -05:00
/// Determines if the user has access to view the node/document
2018-06-29 19:52:40 +02:00
/// </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 )
{
2019-01-27 01:17:32 -05:00
// TODO: At some stage when we implement permissions on languages we'll need to take care of culture
2018-06-29 19:52:40 +02:00
return allowedUserOptions . Select ( x = > x . Action ) . OfType < ActionBrowse > ( ) . Any ( ) ;
}
/// <summary>
/// this will parse the string into either a GUID or INT
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
internal Tuple < Guid ? , int? > GetIdentifierFromString ( string id )
{
Guid idGuid ;
int idInt ;
Udi idUdi ;
if ( Guid . TryParse ( id , out idGuid ) )
{
return new Tuple < Guid ? , int? > ( idGuid , null ) ;
}
if ( int . TryParse ( id , out idInt ) )
{
return new Tuple < Guid ? , int? > ( null , idInt ) ;
}
2019-11-13 16:33:40 +11:00
if ( UdiParser . TryParse ( id , out idUdi ) )
2018-06-29 19:52:40 +02:00
{
var guidUdi = idUdi as GuidUdi ;
if ( guidUdi ! = 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
/// </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 )
{
return _entityCache . GetOrAdd ( id , s = >
{
IEntitySlim entity ;
if ( Guid . TryParse ( s , out var idGuid ) )
{
2020-06-09 07:49:26 +02:00
entity = _entityService . Get ( idGuid , UmbracoObjectType ) ;
2018-06-29 19:52:40 +02:00
}
else if ( int . TryParse ( s , out var idInt ) )
{
2020-06-09 07:49:26 +02:00
entity = _entityService . Get ( idInt , UmbracoObjectType ) ;
2018-06-29 19:52:40 +02:00
}
2019-11-13 16:33:40 +11:00
else if ( UdiParser . TryParse ( s , out var idUdi ) )
2018-06-29 19:52:40 +02:00
{
var guidUdi = idUdi as GuidUdi ;
2020-06-09 07:49:26 +02:00
entity = guidUdi ! = null ? _entityService . Get ( guidUdi . Guid , UmbracoObjectType ) : null ;
2018-06-29 19:52:40 +02:00
}
else
{
entity = null ;
}
return entity ;
} ) ;
}
private readonly ConcurrentDictionary < string , IEntitySlim > _entityCache = new ConcurrentDictionary < string , IEntitySlim > ( ) ;
2019-06-28 13:03:36 +10:00
2019-07-01 18:23:36 +10:00
private bool? _ignoreUserStartNodes ;
2019-11-05 12:54:22 +01:00
2019-10-15 00:07:44 +11:00
2019-06-28 13:03:36 +10:00
/// <summary>
/// If the request should allows a user to choose nodes that they normally don't have access to
/// </summary>
/// <param name="queryStrings"></param>
/// <returns></returns>
2020-06-09 07:49:26 +02:00
internal bool IgnoreUserStartNodes ( FormCollection queryStrings )
2019-06-28 13:03:36 +10:00
{
2019-07-01 18:23:36 +10:00
if ( _ignoreUserStartNodes . HasValue ) return _ignoreUserStartNodes . Value ;
2019-07-03 08:33:30 +02:00
var dataTypeKey = queryStrings . GetValue < Guid ? > ( TreeQueryStringParameters . DataTypeKey ) ;
2020-06-09 07:49:26 +02:00
_ignoreUserStartNodes = dataTypeKey . HasValue & & _dataTypeService . IsDataTypeIgnoringUserStartNodes ( dataTypeKey . Value ) ;
2019-06-28 13:03:36 +10:00
2019-07-01 18:23:36 +10:00
return _ignoreUserStartNodes . Value ;
2019-06-28 13:03:36 +10:00
}
2018-06-29 19:52:40 +02:00
}
}