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

481 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-05-07 18:22:23 +02:00
// should really never be null, but we'll error check anyways
culture = culture ?? Services.LocalizationService.GetDefaultLanguageIsoCode();
// set names according to variations
if (!culture.IsNullOrWhiteSpace())
foreach (var entity in result)
EnsureName((IDocumentEntitySlim)entity, culture);
2018-05-07 18:22:23 +02:00
return result;
}
// set name according to variations
2018-05-07 18:22:23 +02:00
//
private void EnsureName(IDocumentEntitySlim entity, string culture)
2018-05-07 18:22:23 +02:00
{
if (culture == null)
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
{
2018-05-07 18:22:23 +02:00
if (string.IsNullOrWhiteSpace(entity.Name))
entity.Name = "[[" + entity.Id + "]]";
return;
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
}
// we are getting the tree for a given culture,
// for those items that DO support cultures, we need to get the proper name, IF it exists
// otherwise, invariant is fine
if (entity.Variations.Has(ContentVariation.CultureNeutral) &&
entity.CultureNames.TryGetValue(culture, out var name) &&
2018-05-07 18:22:23 +02:00
!string.IsNullOrWhiteSpace(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
{
2018-05-07 18:22:23 +02:00
entity.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
}
2018-05-07 18:22:23 +02:00
if (string.IsNullOrWhiteSpace(entity.Name))
entity.Name = "[[" + entity.Id + "]]";
}
/// <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
}