Fixes a bunch of N+1, content/media trees should be much more snappy
This commit is contained in:
@@ -193,19 +193,6 @@ namespace Umbraco.Core.Models
|
||||
|
||||
// check for a start node in the path
|
||||
return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x, ",")));
|
||||
}
|
||||
|
||||
internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess)
|
||||
{
|
||||
switch (recycleBinId)
|
||||
{
|
||||
case Constants.System.RecycleBinMedia:
|
||||
return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess);
|
||||
case Constants.System.RecycleBinContent:
|
||||
return IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess);
|
||||
default:
|
||||
throw new NotSupportedException("Path access is only determined on content or media");
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsInBranchOfStartNode(string path, int[] startNodeIds, string[] startNodePaths, out bool hasPathAccess)
|
||||
|
||||
@@ -12,14 +12,18 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal class UmbracoEntityFactory
|
||||
{
|
||||
private static readonly Lazy<string[]> EntityProperties = new Lazy<string[]>(() => typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="originalEntityProperties"></param>
|
||||
internal void AddAdditionalData(UmbracoEntity entity, IDictionary<string, object> originalEntityProperties)
|
||||
{
|
||||
var entityProps = typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray();
|
||||
|
||||
//figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data
|
||||
{
|
||||
foreach (var k in originalEntityProperties.Keys
|
||||
.Select(x => new { orig = x, title = x.ToCleanString(CleanStringType.PascalCase | CleanStringType.Ascii | CleanStringType.ConvertCase) })
|
||||
.Where(x => entityProps.InvariantContains(x.title) == false))
|
||||
.Where(x => EntityProperties.Value.InvariantContains(x.title) == false))
|
||||
{
|
||||
entity.AdditionalData[k.title] = originalEntityProperties[k.orig];
|
||||
}
|
||||
@@ -78,4 +82,4 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
<Compile Include="Models\Consent.cs" />
|
||||
<Compile Include="Models\ConsentExtensions.cs" />
|
||||
<Compile Include="Models\ConsentState.cs" />
|
||||
<Compile Include="Models\DataTypeDefinitionExtensions.cs" />
|
||||
<Compile Include="Models\DataTypeExtensions.cs" />
|
||||
<Compile Include="Models\EntityBase\EntityPath.cs" />
|
||||
<Compile Include="Models\EntityBase\IDeletableEntity.cs" />
|
||||
<Compile Include="Models\IAuditEntry.cs" />
|
||||
|
||||
@@ -87,11 +87,12 @@ namespace Umbraco.Web.Trees
|
||||
/// <param name="parentId"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings)
|
||||
internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings,
|
||||
int[] startNodeIds, string[] startNodePaths, bool ignoreUserStartNodes)
|
||||
{
|
||||
bool hasPathAccess;
|
||||
var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess);
|
||||
if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false)
|
||||
var entityIsAncestorOfStartNodes = UserExtensions.IsInBranchOfStartNode(e.Path, startNodeIds, startNodePaths, out hasPathAccess);
|
||||
if (ignoreUserStartNodes == false && entityIsAncestorOfStartNodes == false)
|
||||
return null;
|
||||
|
||||
var treeNode = GetSingleTreeNode(e, parentId, queryStrings);
|
||||
@@ -101,13 +102,30 @@ namespace Umbraco.Web.Trees
|
||||
//the node so we need to return null;
|
||||
return null;
|
||||
}
|
||||
if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false)
|
||||
if (ignoreUserStartNodes == false && hasPathAccess == false)
|
||||
{
|
||||
treeNode.AdditionalData["noAccess"] = true;
|
||||
}
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
private void GetUserStartNodes(out int[] startNodeIds, out string[] startNodePaths)
|
||||
{
|
||||
switch (RecycleBinId)
|
||||
{
|
||||
case Constants.System.RecycleBinMedia:
|
||||
startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService);
|
||||
startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService);
|
||||
break;
|
||||
case Constants.System.RecycleBinContent:
|
||||
startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService);
|
||||
startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Path access is only determined on content or media");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the
|
||||
/// </summary>
|
||||
@@ -134,6 +152,8 @@ namespace Umbraco.Web.Trees
|
||||
? queryStrings.GetValue<string>(TreeQueryStringParameters.StartNodeId)
|
||||
: string.Empty;
|
||||
|
||||
var ignoreUserStartNodes = IgnoreUserStartNodes(queryStrings);
|
||||
|
||||
if (string.IsNullOrEmpty(startNodeId) == false && startNodeId != "undefined" && startNodeId != rootIdString)
|
||||
{
|
||||
// request has been made to render from a specific, non-root, start node
|
||||
@@ -141,7 +161,7 @@ namespace Umbraco.Web.Trees
|
||||
|
||||
// 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 (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false)
|
||||
if (ignoreUserStartNodes == false && HasPathAccess(id, queryStrings) == false)
|
||||
{
|
||||
LogHelper.Warn<ContentTreeControllerBase>("User " + Security.CurrentUser.Username + " does not have access to node with id " + id);
|
||||
return nodes;
|
||||
@@ -159,7 +179,11 @@ namespace Umbraco.Web.Trees
|
||||
// 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();
|
||||
nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null));
|
||||
|
||||
//get the current user start node/paths
|
||||
GetUserStartNodes(out var userStartNodes, out var userStartNodePaths);
|
||||
|
||||
nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null));
|
||||
|
||||
// if the user does not have access to the root node, what we have is the start nodes,
|
||||
// but to provide some context we also need to add their topmost nodes when they are not
|
||||
@@ -170,7 +194,7 @@ namespace Umbraco.Web.Trees
|
||||
if (topNodeIds.Length > 0)
|
||||
{
|
||||
var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray());
|
||||
nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null));
|
||||
nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,5 +506,21 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, IUmbracoEntity> _entityCache = new ConcurrentDictionary<string, IUmbracoEntity>();
|
||||
|
||||
/// <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>
|
||||
internal bool IgnoreUserStartNodes(FormDataCollection queryStrings)
|
||||
{
|
||||
var dataTypeId = queryStrings.GetValue<Guid?>(TreeQueryStringParameters.DataTypeId);
|
||||
if (dataTypeId.HasValue)
|
||||
{
|
||||
return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,22 +350,6 @@ namespace Umbraco.Web.Trees
|
||||
return queryStrings.GetValue<bool>(TreeQueryStringParameters.IsDialog);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
protected bool IgnoreUserStartNodes(FormDataCollection queryStrings)
|
||||
{
|
||||
var dataTypeId = queryStrings.GetValue<Guid?>(TreeQueryStringParameters.DataTypeId);
|
||||
if (dataTypeId.HasValue)
|
||||
{
|
||||
return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event that allows developers to modify the tree node collection that is being rendered
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user