Merge remote-tracking branch 'origin/temp8' into temp8-remove-legacy-controls
# Conflicts: # src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Web;
|
||||
using System.Web.Configuration;
|
||||
using System.Web.Mvc;
|
||||
@@ -158,7 +159,7 @@ namespace Umbraco.Web.Editors
|
||||
},
|
||||
{
|
||||
"treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ApplicationTreeController>(
|
||||
controller => controller.GetApplicationTrees(null, null, null))
|
||||
controller => controller.GetApplicationTrees(null, null, null, TreeUse.None))
|
||||
},
|
||||
{
|
||||
"contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentTypeController>(
|
||||
@@ -348,7 +349,10 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
"umbracoPlugins", new Dictionary<string, object>
|
||||
{
|
||||
{"trees", GetTreePluginsMetaData()}
|
||||
// for each tree that is [PluginController], get
|
||||
// alias -> areaName
|
||||
// so that routing (route.js) can look for views
|
||||
{ "trees", GetPluginTrees().ToArray() }
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -389,43 +393,42 @@ namespace Umbraco.Web.Editors
|
||||
return defaultVals;
|
||||
}
|
||||
|
||||
private IEnumerable<Dictionary<string, string>> GetTreePluginsMetaData()
|
||||
[DataContract]
|
||||
private class PluginTree
|
||||
{
|
||||
var treeTypes = TreeControllerTypes.Value;
|
||||
//get all plugin trees with their attributes
|
||||
var treesWithAttributes = treeTypes.Select(x => new
|
||||
{
|
||||
tree = x,
|
||||
attributes =
|
||||
x.GetCustomAttributes(false)
|
||||
}).ToArray();
|
||||
|
||||
var pluginTreesWithAttributes = treesWithAttributes
|
||||
//don't resolve any tree decorated with CoreTreeAttribute
|
||||
.Where(x => x.attributes.All(a => (a is CoreTreeAttribute) == false))
|
||||
//we only care about trees with the PluginControllerAttribute
|
||||
.Where(x => x.attributes.Any(a => a is PluginControllerAttribute))
|
||||
.ToArray();
|
||||
|
||||
return (from p in pluginTreesWithAttributes
|
||||
let treeAttr = p.attributes.OfType<TreeAttribute>().Single()
|
||||
let pluginAttr = p.attributes.OfType<PluginControllerAttribute>().Single()
|
||||
select new Dictionary<string, string>
|
||||
{
|
||||
{"alias", treeAttr.TreeAlias}, {"packageFolder", pluginAttr.AreaName}
|
||||
}).ToArray();
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[DataMember(Name = "packageFolder")]
|
||||
public string PackageFolder { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A lazy reference to all tree controller types
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We are doing this because if we constantly resolve the tree controller types from the PluginManager it will re-scan and also re-log that
|
||||
/// it's resolving which is unnecessary and annoying.
|
||||
/// </remarks>
|
||||
private static readonly Lazy<IEnumerable<Type>> TreeControllerTypes
|
||||
= new Lazy<IEnumerable<Type>>(() => Current.TypeLoader.GetAttributedTreeControllers().ToArray()); // TODO: inject
|
||||
private IEnumerable<PluginTree> GetPluginTrees()
|
||||
{
|
||||
// used to be (cached)
|
||||
//var treeTypes = Current.TypeLoader.GetAttributedTreeControllers();
|
||||
//
|
||||
// ie inheriting from TreeController and marked with TreeAttribute
|
||||
//
|
||||
// do this instead
|
||||
// inheriting from TreeControllerBase and marked with TreeAttribute
|
||||
var trees = Current.Factory.GetInstance<TreeCollection>();
|
||||
|
||||
foreach (var tree in trees)
|
||||
{
|
||||
var treeType = tree.TreeControllerType;
|
||||
|
||||
// exclude anything marked with CoreTreeAttribute
|
||||
var coreTree = treeType.GetCustomAttribute<CoreTreeAttribute>(false);
|
||||
if (coreTree != null) continue;
|
||||
|
||||
// exclude anything not marked with PluginControllerAttribute
|
||||
var pluginController = treeType.GetCustomAttribute<PluginControllerAttribute>(false);
|
||||
if (pluginController == null) continue;
|
||||
|
||||
yield return new PluginTree { Alias = tree.TreeAlias, PackageFolder = pluginController.AreaName };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the server variables regarding the application state
|
||||
|
||||
@@ -75,11 +75,14 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="name">The name of the container/folder</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public CodeFileDisplay PostCreateContainer(string type, string parentId, string name)
|
||||
public HttpResponseMessage PostCreateContainer(string type, string parentId, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
|
||||
if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId");
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
|
||||
if (name.ContainsAny(Path.GetInvalidPathChars())) {
|
||||
return Request.CreateNotificationValidationErrorResponse(Services.TextService.Localize("codefile/createFolderIllegalChars"));
|
||||
}
|
||||
|
||||
// if the parentId is root (-1) then we just need an empty string as we are
|
||||
// creating the path below and we don't want -1 in the path
|
||||
@@ -118,11 +121,11 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
}
|
||||
|
||||
return new CodeFileDisplay
|
||||
return Request.CreateResponse(HttpStatusCode.OK, new CodeFileDisplay
|
||||
{
|
||||
VirtualPath = virtualPath,
|
||||
Path = Url.GetTreePathFromFilePath(virtualPath)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Umbraco.Web.Install.InstallSteps
|
||||
/// <returns></returns>
|
||||
public override Task<InstallSetupResult> ExecuteAsync(bool? model)
|
||||
{
|
||||
if (model.HasValue && model.Value == false) return null;
|
||||
if (model.HasValue && model.Value == false) return Task.FromResult<InstallSetupResult>(null);
|
||||
|
||||
//install the machine key
|
||||
var fileName = IOHelper.MapPath($"{SystemDirectories.Root}/web.config");
|
||||
@@ -43,7 +43,7 @@ namespace Umbraco.Web.Install.InstallSteps
|
||||
|
||||
// Update appSetting if it exists, or else create a new appSetting for the given key and value
|
||||
var machineKey = systemWeb.Descendants("machineKey").FirstOrDefault();
|
||||
if (machineKey != null) return null;
|
||||
if (machineKey != null) return Task.FromResult<InstallSetupResult>(null);
|
||||
|
||||
var generator = new MachineKeyGenerator();
|
||||
var generatedSection = generator.GenerateConfigurationBlock();
|
||||
|
||||
@@ -44,14 +44,6 @@ namespace Umbraco.Web.Mvc
|
||||
id = @"[a-zA-Z]*"
|
||||
},
|
||||
new[] {typeof (BackOfficeController).Namespace});
|
||||
|
||||
//Create the REST/web/script service routes
|
||||
context.MapRoute(
|
||||
"Umbraco_web_services",
|
||||
_globalSettings.GetUmbracoMvcArea() + "/RestServices/{controller}/{action}/{id}",
|
||||
new {controller = "SaveFileController", action = "Index", id = UrlParameter.Optional},
|
||||
//look in this namespace for controllers
|
||||
new[] {"Umbraco.Web.WebServices"});
|
||||
}
|
||||
|
||||
public override string AreaName => _globalSettings.GetUmbracoMvcArea();
|
||||
|
||||
@@ -4,23 +4,27 @@ using System.Linq;
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute applied to a plugin controller that requires that it is routed to its own area
|
||||
/// Indicates that a controller is a plugin tree controller and should be routed to its own area.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class PluginControllerAttribute : Attribute
|
||||
{
|
||||
public string AreaName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginControllerAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="areaName"></param>
|
||||
public PluginControllerAttribute(string areaName)
|
||||
{
|
||||
//validate this, only letters and digits allowed.
|
||||
if (areaName.Any(c => !Char.IsLetterOrDigit(c)))
|
||||
{
|
||||
throw new FormatException("The areaName specified " + areaName + " can only contains letters and digits");
|
||||
}
|
||||
// validate this, only letters and digits allowed.
|
||||
if (areaName.Any(c => !char.IsLetterOrDigit(c)))
|
||||
throw new FormatException($"Invalid area name \"{areaName}\": the area name can only contains letters and digits.");
|
||||
|
||||
AreaName = areaName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the area.
|
||||
/// </summary>
|
||||
public string AreaName { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||
if (udi == null)
|
||||
return null;
|
||||
content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udi.Guid);
|
||||
if (content != null)
|
||||
if (content != null && content.ItemType == PublishedItemType.Content)
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||
break;
|
||||
}
|
||||
|
||||
if (multiNodeTreePickerItem != null)
|
||||
if (multiNodeTreePickerItem != null && multiNodeTreePickerItem.ItemType != PublishedItemType.Element)
|
||||
{
|
||||
multiNodeTreePicker.Add(multiNodeTreePickerItem);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||
_publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(preview, dto.Udi.Guid) :
|
||||
_publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(preview, dto.Udi.Guid);
|
||||
|
||||
if (content == null)
|
||||
if (content == null || content.ItemType == PublishedItemType.Element)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -13,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// <remarks>
|
||||
/// This pre-value editor is shared with editors like drop down, checkbox list, etc....
|
||||
/// </remarks>
|
||||
internal class ValueListConfigurationEditor : ConfigurationEditor<ValueListConfiguration>
|
||||
public class ValueListConfigurationEditor : ConfigurationEditor<ValueListConfiguration>
|
||||
{
|
||||
public ValueListConfigurationEditor(ILocalizedTextService textService)
|
||||
{
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
//public class WebServicesRouteConstraint : IRouteConstraint
|
||||
//{
|
||||
// public bool Match(
|
||||
// HttpContextBase httpContext,
|
||||
// Route route,
|
||||
// string parameterName,
|
||||
// RouteValueDictionary values,
|
||||
// RouteDirection routeDirection)
|
||||
// {
|
||||
// if (routeDirection == RouteDirection.UrlGeneration)
|
||||
// {
|
||||
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
@@ -208,14 +208,10 @@ namespace Umbraco.Web.Runtime
|
||||
.Add(composition.TypeLoader.GetTypes<IDashboard>());
|
||||
|
||||
// register back office trees
|
||||
foreach (var treeControllerType in umbracoApiControllerTypes
|
||||
.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x)))
|
||||
{
|
||||
var attribute = treeControllerType.GetCustomAttribute<TreeAttribute>(false);
|
||||
if (attribute == null) continue;
|
||||
var tree = new Tree(attribute.SortOrder, attribute.ApplicationAlias, attribute.TreeAlias, attribute.TreeTitle, treeControllerType, attribute.IsSingleNodeTree);
|
||||
composition.WithCollectionBuilder<TreeCollectionBuilder>().Trees.Add(tree);
|
||||
}
|
||||
// the collection builder only accepts types inheriting from TreeControllerBase
|
||||
// and will filter out those that are not attributed with TreeAttribute
|
||||
composition.WithCollectionBuilder<TreeCollectionBuilder>()
|
||||
.AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Web.Search
|
||||
var found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias));
|
||||
if (found != null)
|
||||
{
|
||||
dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.ApplicationAlias, appTree.TreeAlias, found);
|
||||
dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.SectionAlias, appTree.TreeAlias, found);
|
||||
}
|
||||
}
|
||||
return dictionary;
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a service which manages section trees.
|
||||
/// </summary>
|
||||
public interface ITreeService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an ApplicationTree by it's tree alias.
|
||||
/// Gets a tree.
|
||||
/// </summary>
|
||||
/// <param name="treeAlias">The tree alias.</param>
|
||||
/// <returns>An ApplicationTree instance</returns>
|
||||
Tree GetByAlias(string treeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all applicationTrees registered in umbraco from the umbracoAppTree table..
|
||||
/// Gets all trees.
|
||||
/// </summary>
|
||||
/// <returns>Returns a ApplicationTree Array</returns>
|
||||
IEnumerable<Tree> GetAll();
|
||||
|
||||
IEnumerable<Tree> GetAll(TreeUse use = TreeUse.Main);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application tree for the applcation with the specified alias
|
||||
/// Gets all trees for a section.
|
||||
/// </summary>
|
||||
/// <param name="sectionAlias">The application alias.</param>
|
||||
/// <returns>Returns a ApplicationTree Array</returns>
|
||||
IEnumerable<Tree> GetTrees(string sectionAlias);
|
||||
|
||||
IEnumerable<Tree> GetBySection(string sectionAlias, TreeUse use = TreeUse.Main);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the grouped application trees for the application with the specified alias
|
||||
/// Gets all trees for a section, grouped.
|
||||
/// </summary>
|
||||
/// <param name="sectionAlias"></param>
|
||||
/// <returns></returns>
|
||||
IDictionary<string, IEnumerable<Tree>> GetGroupedTrees(string sectionAlias);
|
||||
IDictionary<string, IEnumerable<Tree>> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,65 +6,41 @@ using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements <see cref="ITreeService"/>.
|
||||
/// </summary>
|
||||
internal class TreeService : ITreeService
|
||||
{
|
||||
private readonly TreeCollection _treeCollection;
|
||||
private readonly Lazy<IReadOnlyCollection<IGrouping<string, string>>> _groupedTrees;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TreeService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="treeCollection"></param>
|
||||
public TreeService(TreeCollection treeCollection)
|
||||
{
|
||||
_treeCollection = treeCollection;
|
||||
_groupedTrees = new Lazy<IReadOnlyCollection<IGrouping<string, string>>>(InitGroupedTrees);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Tree GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(t => t.TreeAlias == treeAlias);
|
||||
public Tree GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(x => x.TreeAlias == treeAlias);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Tree> GetAll() => _treeCollection;
|
||||
public IEnumerable<Tree> GetAll(TreeUse use = TreeUse.Main)
|
||||
// use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees
|
||||
=> _treeCollection.Where(x => x.TreeUse.HasFlagAny(use));
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Tree> GetTrees(string sectionAlias)
|
||||
=> GetAll().Where(x => x.ApplicationAlias.InvariantEquals(sectionAlias)).OrderBy(x => x.SortOrder).ToList();
|
||||
public IEnumerable<Tree> GetBySection(string sectionAlias, TreeUse use = TreeUse.Main)
|
||||
// use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees
|
||||
=> _treeCollection.Where(x => x.SectionAlias.InvariantEquals(sectionAlias) && x.TreeUse.HasFlagAny(use)).OrderBy(x => x.SortOrder).ToList();
|
||||
|
||||
public IDictionary<string, IEnumerable<Tree>> GetGroupedTrees(string sectionAlias)
|
||||
/// <inheritdoc />
|
||||
public IDictionary<string, IEnumerable<Tree>> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main)
|
||||
{
|
||||
var result = new Dictionary<string, IEnumerable<Tree>>();
|
||||
var foundTrees = GetTrees(sectionAlias).ToList();
|
||||
foreach(var treeGroup in _groupedTrees.Value)
|
||||
{
|
||||
List<Tree> resultGroup = null;
|
||||
foreach(var tree in foundTrees)
|
||||
{
|
||||
foreach(var treeAliasInGroup in treeGroup)
|
||||
{
|
||||
if (tree.TreeAlias != treeAliasInGroup) continue;
|
||||
|
||||
if (resultGroup == null) resultGroup = new List<Tree>();
|
||||
resultGroup.Add(tree);
|
||||
}
|
||||
}
|
||||
if (resultGroup != null)
|
||||
result[treeGroup.Key ?? string.Empty] = resultGroup; //key cannot be null so make empty string
|
||||
}
|
||||
return result;
|
||||
return GetBySection(sectionAlias, use).GroupBy(x => x.TreeGroup).ToDictionary(
|
||||
x => x.Key ?? "",
|
||||
x => (IEnumerable<Tree>) x.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a group of all tree groups and their tree aliases
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Used to initialize the <see cref="_groupedTrees"/> field
|
||||
/// </remarks>
|
||||
private IReadOnlyCollection<IGrouping<string, string>> InitGroupedTrees()
|
||||
{
|
||||
var result = GetAll()
|
||||
.Select(x => (treeAlias: x.TreeAlias, treeGroup: x.TreeControllerType.GetCustomAttribute<CoreTreeAttribute>(false)?.TreeGroup))
|
||||
.GroupBy(x => x.treeGroup, x => x.treeAlias)
|
||||
.ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,8 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Services;
|
||||
@@ -50,262 +47,235 @@ namespace Umbraco.Web.Trees
|
||||
/// </summary>
|
||||
/// <param name="application">The application to load tree for</param>
|
||||
/// <param name="tree">An optional single tree alias, if specified will only load the single tree for the request app</param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <param name="querystring"></param>
|
||||
/// <param name="use">Tree use.</param>
|
||||
/// <returns></returns>
|
||||
[HttpQueryStringFilter("queryStrings")]
|
||||
public async Task<TreeRootNode> GetApplicationTrees(string application, string tree, FormDataCollection queryStrings)
|
||||
public async Task<TreeRootNode> GetApplicationTrees(string application, string tree, FormDataCollection querystring, TreeUse use = TreeUse.Main)
|
||||
{
|
||||
application = application.CleanForXss();
|
||||
|
||||
if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
if (string.IsNullOrEmpty(application))
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
//find all tree definitions that have the current application alias
|
||||
var groupedTrees = _treeService.GetGroupedTrees(application);
|
||||
var groupedTrees = _treeService.GetBySectionGrouped(application, use);
|
||||
var allTrees = groupedTrees.Values.SelectMany(x => x).ToList();
|
||||
|
||||
if (string.IsNullOrEmpty(tree) == false || allTrees.Count == 1)
|
||||
if (allTrees.Count == 0)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
// handle request for a specific tree / or when there is only one tree
|
||||
if (!tree.IsNullOrWhiteSpace() || allTrees.Count == 1)
|
||||
{
|
||||
var apptree = !tree.IsNullOrWhiteSpace()
|
||||
? allTrees.FirstOrDefault(x => x.TreeAlias == tree)
|
||||
: allTrees.FirstOrDefault();
|
||||
var t = tree.IsNullOrWhiteSpace()
|
||||
? allTrees[0]
|
||||
: allTrees.FirstOrDefault(x => x.TreeAlias == tree);
|
||||
|
||||
if (apptree == null) throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
if (t == null)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
var result = await GetRootForSingleAppTree(
|
||||
apptree,
|
||||
Constants.System.Root.ToString(CultureInfo.InvariantCulture),
|
||||
queryStrings,
|
||||
application);
|
||||
var treeRootNode = await GetTreeRootNode(t, Constants.System.Root, querystring);
|
||||
if (treeRootNode != null)
|
||||
return treeRootNode;
|
||||
|
||||
//this will be null if it cannot convert to a single root section
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
//Don't apply fancy grouping logic futher down, if we only have one group of items
|
||||
var hasGroups = groupedTrees.Count > 1;
|
||||
if (!hasGroups)
|
||||
// handle requests for all trees
|
||||
// for only 1 group
|
||||
if (groupedTrees.Count == 1)
|
||||
{
|
||||
var collection = new TreeNodeCollection();
|
||||
foreach (var apptree in allTrees)
|
||||
var nodes = new TreeNodeCollection();
|
||||
foreach (var t in allTrees)
|
||||
{
|
||||
//return the root nodes for each tree in the app
|
||||
var rootNode = await GetRootForMultipleAppTree(apptree, queryStrings);
|
||||
//This could be null if the tree decides not to return it's root (i.e. the member type tree does this when not in umbraco membership mode)
|
||||
if (rootNode != null)
|
||||
{
|
||||
collection.Add(rootNode);
|
||||
}
|
||||
var node = await TryGetRootNode(t, querystring);
|
||||
if (node != null)
|
||||
nodes.Add(node);
|
||||
}
|
||||
|
||||
if(collection.Count > 0)
|
||||
{
|
||||
var multiTree = TreeRootNode.CreateMultiTreeRoot(collection);
|
||||
multiTree.Name = Services.TextService.Localize("sections/" + application);
|
||||
var name = Services.TextService.Localize("sections/" + application);
|
||||
|
||||
return multiTree;
|
||||
if (nodes.Count > 0)
|
||||
{
|
||||
var treeRootNode = TreeRootNode.CreateMultiTreeRoot(nodes);
|
||||
treeRootNode.Name = name;
|
||||
return treeRootNode;
|
||||
}
|
||||
|
||||
//Otherwise its a application/section with no trees (aka a full screen app)
|
||||
//For example we do not have a Forms tree defined in C# & can not attribute with [Tree(isSingleNodeTree:true0]
|
||||
var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
|
||||
var section = Services.TextService.Localize("sections/" + application);
|
||||
|
||||
return TreeRootNode.CreateSingleTreeRoot(rootId, null, null, section, TreeNodeCollection.Empty, true);
|
||||
// otherwise it's a section with no tree, aka a fullscreen section
|
||||
// todo is this true? what if we just failed to TryGetRootNode on all of them?
|
||||
return TreeRootNode.CreateSingleTreeRoot(Constants.System.Root.ToInvariantString(), null, null, name, TreeNodeCollection.Empty, true);
|
||||
}
|
||||
|
||||
var rootNodeGroups = new List<TreeRootNode>();
|
||||
|
||||
//Group trees by [CoreTree] attribute with a TreeGroup property
|
||||
foreach (var treeSectionGroup in groupedTrees)
|
||||
// for many groups
|
||||
var treeRootNodes = new List<TreeRootNode>();
|
||||
foreach (var (groupName, trees) in groupedTrees)
|
||||
{
|
||||
var treeGroupName = treeSectionGroup.Key;
|
||||
|
||||
var groupNodeCollection = new TreeNodeCollection();
|
||||
foreach (var appTree in treeSectionGroup.Value)
|
||||
var nodes = new TreeNodeCollection();
|
||||
foreach (var t in trees)
|
||||
{
|
||||
var rootNode = await GetRootForMultipleAppTree(appTree, queryStrings);
|
||||
if (rootNode != null)
|
||||
{
|
||||
//Add to a new list/collection
|
||||
groupNodeCollection.Add(rootNode);
|
||||
}
|
||||
var node = await TryGetRootNode(t, querystring);
|
||||
if (node != null)
|
||||
nodes.Add(node);
|
||||
}
|
||||
|
||||
//If treeGroupName == null then its third party
|
||||
if (treeGroupName.IsNullOrWhiteSpace())
|
||||
{
|
||||
//This is used for the localization key
|
||||
//treeHeaders/thirdPartyGroup
|
||||
treeGroupName = "thirdPartyGroup";
|
||||
}
|
||||
if (nodes.Count == 0)
|
||||
continue;
|
||||
|
||||
if (groupNodeCollection.Count > 0)
|
||||
{
|
||||
var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection, application);
|
||||
groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName);
|
||||
// no name => third party
|
||||
// use localization key treeHeaders/thirdPartyGroup
|
||||
// todo this is an odd convention
|
||||
var name = groupName.IsNullOrWhiteSpace() ? "thirdPartyGroup" : groupName;
|
||||
|
||||
rootNodeGroups.Add(groupRoot);
|
||||
}
|
||||
var groupRootNode = TreeRootNode.CreateGroupNode(nodes, application);
|
||||
groupRootNode.Name = Services.TextService.Localize("treeHeaders/" + name);
|
||||
treeRootNodes.Add(groupRootNode);
|
||||
}
|
||||
|
||||
return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(rootNodeGroups.OrderBy(x => x.Name)));
|
||||
return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(treeRootNodes.OrderBy(x => x.Name)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the root node for an application with multiple trees
|
||||
/// Tries to get the root node of a tree.
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<TreeNode> GetRootForMultipleAppTree(Tree tree, FormDataCollection queryStrings)
|
||||
/// <remarks>
|
||||
/// <para>Returns null if the root node could not be obtained due to an HttpResponseException,
|
||||
/// which probably indicates that the user isn't authorized to view that tree.</para>
|
||||
/// </remarks>
|
||||
private async Task<TreeNode> TryGetRootNode(Tree tree, FormDataCollection querystring)
|
||||
{
|
||||
if (tree == null) throw new ArgumentNullException(nameof(tree));
|
||||
|
||||
try
|
||||
{
|
||||
var byControllerAttempt = await TryGetRootNodeFromControllerTree(tree, queryStrings, ControllerContext);
|
||||
if (byControllerAttempt.Success)
|
||||
{
|
||||
return byControllerAttempt.Result;
|
||||
}
|
||||
return await GetRootNode(tree, querystring);
|
||||
}
|
||||
catch (HttpResponseException)
|
||||
{
|
||||
//if this occurs its because the user isn't authorized to view that tree, in this case since we are loading multiple trees we
|
||||
//will just return null so that it's not added to the list.
|
||||
// if this occurs its because the user isn't authorized to view that tree,
|
||||
// in this case since we are loading multiple trees we will just return
|
||||
// null so that it's not added to the list.
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new ApplicationException("Could not get root node for tree type " + tree.TreeAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the root node for an application with one tree
|
||||
/// Get the tree root node of a tree.
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <param name="application"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<TreeRootNode> GetRootForSingleAppTree(Tree tree, string id, FormDataCollection queryStrings, string application)
|
||||
private async Task<TreeRootNode> GetTreeRootNode(Tree tree, int id, FormDataCollection querystring)
|
||||
{
|
||||
var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
|
||||
if (tree == null) throw new ArgumentNullException(nameof(tree));
|
||||
var byControllerAttempt = TryLoadFromControllerTree(tree, id, queryStrings, ControllerContext);
|
||||
if (!byControllerAttempt.Success)
|
||||
throw new ApplicationException("Could not render a tree for type " + tree.TreeAlias);
|
||||
|
||||
var rootNode = await TryGetRootNodeFromControllerTree(tree, queryStrings, ControllerContext);
|
||||
if (rootNode.Success == false)
|
||||
{
|
||||
//This should really never happen if we've successfully got the children above.
|
||||
throw new InvalidOperationException("Could not create root node for tree " + tree.TreeAlias);
|
||||
}
|
||||
var children = await GetChildren(tree, id, querystring);
|
||||
var rootNode = await GetRootNode(tree, querystring);
|
||||
|
||||
var sectionRoot = TreeRootNode.CreateSingleTreeRoot(
|
||||
rootId,
|
||||
rootNode.Result.ChildNodesUrl,
|
||||
rootNode.Result.MenuUrl,
|
||||
rootNode.Result.Name,
|
||||
byControllerAttempt.Result,
|
||||
Constants.System.Root.ToInvariantString(),
|
||||
rootNode.ChildNodesUrl,
|
||||
rootNode.MenuUrl,
|
||||
rootNode.Name,
|
||||
children,
|
||||
tree.IsSingleNodeTree);
|
||||
|
||||
//assign the route path based on the root node, this means it will route there when the section is navigated to
|
||||
//and no dashboards will be available for this section
|
||||
sectionRoot.RoutePath = rootNode.Result.RoutePath;
|
||||
sectionRoot.Path = rootNode.Result.Path;
|
||||
// assign the route path based on the root node, this means it will route there when the
|
||||
// section is navigated to and no dashboards will be available for this section
|
||||
sectionRoot.RoutePath = rootNode.RoutePath;
|
||||
sectionRoot.Path = rootNode.Path;
|
||||
|
||||
foreach (var d in rootNode.Result.AdditionalData)
|
||||
{
|
||||
foreach (var d in rootNode.AdditionalData)
|
||||
sectionRoot.AdditionalData[d.Key] = d.Value;
|
||||
}
|
||||
return sectionRoot;
|
||||
|
||||
return sectionRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proxies a request to the destination tree controller to get it's root tree node
|
||||
/// Gets the root node of a tree.
|
||||
/// </summary>
|
||||
/// <param name="appTree"></param>
|
||||
/// <param name="formCollection"></param>
|
||||
/// <param name="controllerContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This ensures that authorization filters are applied to the sub request
|
||||
/// </remarks>
|
||||
private async Task<Attempt<TreeNode>> TryGetRootNodeFromControllerTree(Tree appTree, FormDataCollection formCollection, HttpControllerContext controllerContext)
|
||||
private async Task<TreeNode> GetRootNode(Tree tree, FormDataCollection querystring)
|
||||
{
|
||||
//instantiate it, since we are proxying, we need to setup the instance with our current context
|
||||
var instance = (TreeController)DependencyResolver.Current.GetService(appTree.TreeControllerType);
|
||||
if (tree == null) throw new ArgumentNullException(nameof(tree));
|
||||
|
||||
//NOTE: This is all required in order to execute the auth-filters for the sub request, we
|
||||
var controller = (TreeController) await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring);
|
||||
var rootNode = controller.GetRootNode(querystring);
|
||||
if (rootNode == null)
|
||||
throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\".");
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the child nodes of a tree node.
|
||||
/// </summary>
|
||||
private async Task<TreeNodeCollection> GetChildren(Tree tree, int id, FormDataCollection querystring)
|
||||
{
|
||||
if (tree == null) throw new ArgumentNullException(nameof(tree));
|
||||
|
||||
// the method we proxy has an 'id' parameter which is *not* in the querystring,
|
||||
// we need to add it for the proxy to work (else, it does not find the method,
|
||||
// when trying to run auth filters etc).
|
||||
var d = querystring?.ToDictionary(x => x.Key, x => x.Value) ?? new Dictionary<string, string>();
|
||||
d["id"] = null;
|
||||
var proxyQuerystring = new FormDataCollection(d);
|
||||
|
||||
var controller = (TreeController) await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring);
|
||||
return controller.GetNodes(id.ToInvariantString(), querystring);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a proxy to a controller for a specified action.
|
||||
/// </summary>
|
||||
/// <param name="controllerType">The type of the controller.</param>
|
||||
/// <param name="action">The action.</param>
|
||||
/// <param name="querystring">The querystring.</param>
|
||||
/// <returns>An instance of the controller.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Creates an instance of the <paramref name="controllerType"/> and initializes it with a route
|
||||
/// and context etc. so it can execute the specified <paramref name="action"/>. Runs the authorization
|
||||
/// filters for that action, to ensure that the user has permission to execute it.</para>
|
||||
/// </remarks>
|
||||
private async Task<object> GetApiControllerProxy(Type controllerType, string action, FormDataCollection querystring)
|
||||
{
|
||||
// note: this is all required in order to execute the auth-filters for the sub request, we
|
||||
// need to "trick" web-api into thinking that it is actually executing the proxied controller.
|
||||
|
||||
var urlHelper = controllerContext.Request.GetUrlHelper();
|
||||
//create the proxied URL for the controller action
|
||||
var proxiedUrl = controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Authority) +
|
||||
urlHelper.GetUmbracoApiService("GetRootNode", instance.GetType());
|
||||
//add the query strings to it
|
||||
proxiedUrl += "?" + formCollection.ToQueryString();
|
||||
//create proxy route data specifying the action / controller to execute
|
||||
var proxiedRouteData = new HttpRouteData(
|
||||
controllerContext.RouteData.Route,
|
||||
new HttpRouteValueDictionary(new { action = "GetRootNode", controller = ControllerExtensions.GetControllerName(instance.GetType()) }));
|
||||
var context = ControllerContext;
|
||||
|
||||
//create a proxied controller context
|
||||
var proxiedControllerContext = new HttpControllerContext(
|
||||
controllerContext.Configuration,
|
||||
proxiedRouteData,
|
||||
new HttpRequestMessage(HttpMethod.Get, proxiedUrl))
|
||||
// get the controller
|
||||
var controller = (ApiController) DependencyResolver.Current.GetService(controllerType)
|
||||
?? throw new Exception($"Failed to create controller of type {controllerType.FullName}.");
|
||||
|
||||
// create the proxy URL for the controller action
|
||||
var proxyUrl = context.Request.RequestUri.GetLeftPart(UriPartial.Authority)
|
||||
+ context.Request.GetUrlHelper().GetUmbracoApiService(action, controllerType)
|
||||
+ "?" + querystring.ToQueryString();
|
||||
|
||||
// create proxy route data specifying the action & controller to execute
|
||||
var proxyRoute = new HttpRouteData(
|
||||
context.RouteData.Route,
|
||||
new HttpRouteValueDictionary(new { action, controller = ControllerExtensions.GetControllerName(controllerType) }));
|
||||
|
||||
// create a proxy request
|
||||
var proxyRequest = new HttpRequestMessage(HttpMethod.Get, proxyUrl);
|
||||
|
||||
// create a proxy controller context
|
||||
var proxyContext = new HttpControllerContext(context.Configuration, proxyRoute, proxyRequest)
|
||||
{
|
||||
ControllerDescriptor = new HttpControllerDescriptor(controllerContext.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(instance.GetType()), instance.GetType()),
|
||||
RequestContext = controllerContext.RequestContext
|
||||
ControllerDescriptor = new HttpControllerDescriptor(context.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(controllerType), controllerType),
|
||||
RequestContext = context.RequestContext,
|
||||
Controller = controller
|
||||
};
|
||||
|
||||
instance.ControllerContext = proxiedControllerContext;
|
||||
instance.Request = controllerContext.Request;
|
||||
instance.RequestContext.RouteData = proxiedRouteData;
|
||||
// wire everything
|
||||
controller.ControllerContext = proxyContext;
|
||||
controller.Request = proxyContext.Request;
|
||||
controller.RequestContext.RouteData = proxyRoute;
|
||||
|
||||
//invoke auth filters for this sub request
|
||||
var result = await instance.ControllerContext.InvokeAuthorizationFiltersForRequest();
|
||||
//if a result is returned it means they are unauthorized, just throw the response.
|
||||
if (result != null)
|
||||
{
|
||||
throw new HttpResponseException(result);
|
||||
}
|
||||
// auth
|
||||
var authResult = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest();
|
||||
if (authResult != null)
|
||||
throw new HttpResponseException(authResult);
|
||||
|
||||
//return the root
|
||||
var node = instance.GetRootNode(formCollection);
|
||||
return node == null
|
||||
? Attempt<TreeNode>.Fail(new InvalidOperationException("Could not return a root node for tree " + appTree.TreeAlias))
|
||||
: Attempt<TreeNode>.Succeed(node);
|
||||
return controller;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proxies a request to the destination tree controller to get it's tree node collection
|
||||
/// </summary>
|
||||
/// <param name="appTree"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="formCollection"></param>
|
||||
/// <param name="controllerContext"></param>
|
||||
/// <returns></returns>
|
||||
private Attempt<TreeNodeCollection> TryLoadFromControllerTree(Tree appTree, string id, FormDataCollection formCollection, HttpControllerContext controllerContext)
|
||||
{
|
||||
// instantiate it, since we are proxying, we need to setup the instance with our current context
|
||||
var instance = (TreeController)DependencyResolver.Current.GetService(appTree.TreeControllerType);
|
||||
if (instance == null)
|
||||
throw new Exception("Failed to create tree " + appTree.TreeControllerType + ".");
|
||||
|
||||
// TODO: Shouldn't we be applying the same proxying logic as above so that filters work? seems like an oversight
|
||||
|
||||
instance.ControllerContext = controllerContext;
|
||||
instance.Request = controllerContext.Request;
|
||||
|
||||
// return its data
|
||||
return Attempt.Succeed(instance.GetNodes(id, formCollection));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ namespace Umbraco.Web.Trees
|
||||
/// This authorizes based on access to the content section even though it exists in the settings
|
||||
/// </remarks>
|
||||
[UmbracoApplicationAuthorize(Constants.Applications.Content)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 12)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, SortOrder = 12, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
public class ContentBlueprintTreeController : TreeController
|
||||
{
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ using Umbraco.Web.WebApi.Filters;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 0)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, SortOrder = 0, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[Mvc.PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
public class ContentTypeTreeController : TreeController, ISearchableTree
|
||||
{
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
|
||||
@@ -3,19 +3,12 @@
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a tree is a core tree and shouldn't be treated as a plugin tree
|
||||
/// Indicates that a tree is a core tree and should not be treated as a plugin tree.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This ensures that umbraco will look in the umbraco folders for views for this tree
|
||||
/// This ensures that umbraco will look in the umbraco folders for views for this tree.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
internal class CoreTreeAttribute : Attribute
|
||||
{
|
||||
public string TreeGroup { get; set; }
|
||||
|
||||
public CoreTreeAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
{ }
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.DataTypes)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:3)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, SortOrder = 3, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
public class DataTypeTreeController : TreeController, ISearchableTree
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
@@ -121,7 +121,7 @@ namespace Umbraco.Web.Trees
|
||||
});
|
||||
|
||||
if (container.HasChildren == false)
|
||||
{
|
||||
{
|
||||
//can delete data type
|
||||
menu.Items.Add<ActionDelete>(Services.TextService, opensDialog: true);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace Umbraco.Web.Trees
|
||||
// dictionary items in templates, even when we dont have authorization to manage the dictionary items
|
||||
)]
|
||||
[Mvc.PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, null)]
|
||||
[CoreTree]
|
||||
[Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
public class DictionaryTreeController : TreeController
|
||||
{
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
|
||||
@@ -4,11 +4,11 @@ using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[Tree(Constants.Applications.Settings, "files", "Files", "icon-folder", "icon-folder", sortOrder: 13, initialize: false)]
|
||||
[Tree(Constants.Applications.Settings, "files", TreeTitle = "Files", IconOpen = "icon-folder", IconClosed = "icon-folder", TreeUse = TreeUse.Dialog)]
|
||||
[CoreTree]
|
||||
public class FilesTreeController : FileSystemTreeController
|
||||
{
|
||||
protected override IFileSystem FileSystem => new PhysicalFileSystem("~/"); // TODO: inject
|
||||
protected override IFileSystem FileSystem => new PhysicalFileSystem("~/");
|
||||
|
||||
private static readonly string[] ExtensionsStatic = { "*" };
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
// TODO: we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used
|
||||
//leave as internal for now, maybe we'll use in the future, means we could pass around ITree
|
||||
//leave as internal for now, maybe we'll use in the future, means we could pass around ITree
|
||||
internal interface ITree
|
||||
{
|
||||
/// <summary>
|
||||
@@ -13,7 +13,12 @@
|
||||
/// <summary>
|
||||
/// Gets the section alias.
|
||||
/// </summary>
|
||||
string ApplicationAlias { get; }
|
||||
string SectionAlias { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree group.
|
||||
/// </summary>
|
||||
string TreeGroup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree alias.
|
||||
@@ -25,6 +30,11 @@
|
||||
/// </summary>
|
||||
string TreeTitle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree use.
|
||||
/// </summary>
|
||||
TreeUse TreeUse { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app)
|
||||
/// </summary>
|
||||
|
||||
@@ -7,9 +7,9 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Languages)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 11)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Languages, SortOrder = 11, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
public class LanguageTreeController : TreeController
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
|
||||
@@ -7,9 +7,8 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.LogViewer)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.LogViewer, null, sortOrder: 9)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.LogViewer, SortOrder= 9, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
public class LogViewerTreeController : TreeController
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
|
||||
@@ -12,9 +12,9 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Macros)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Macros, TreeTitle = "Macros", SortOrder = 4, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
public class MacrosTreeController : TreeController
|
||||
{
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
|
||||
@@ -16,9 +16,9 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:1)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, SortOrder = 1, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[Mvc.PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
public class MediaTypeTreeController : TreeController, ISearchableTree
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
|
||||
@@ -8,7 +8,7 @@ using Umbraco.Web.WebApi.Filters;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.MemberGroups)]
|
||||
[Tree(Constants.Applications.Members, Constants.Trees.MemberGroups, null, sortOrder: 1)]
|
||||
[Tree(Constants.Applications.Members, Constants.Trees.MemberGroups, SortOrder = 1)]
|
||||
[Mvc.PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class MemberGroupTreeController : MemberTypeAndGroupTreeControllerBase
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Web.Trees
|
||||
Constants.Applications.Content,
|
||||
Constants.Applications.Media,
|
||||
Constants.Applications.Members)]
|
||||
[Tree(Constants.Applications.Members, Constants.Trees.Members, null, sortOrder: 0)]
|
||||
[Tree(Constants.Applications.Members, Constants.Trees.Members, SortOrder = 0)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
[SearchableTree("searchResultFormatter", "configureMemberResult")]
|
||||
|
||||
@@ -7,9 +7,9 @@ using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
[UmbracoTreeAuthorize(Constants.Trees.MemberTypes)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 2)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, SortOrder = 2, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase
|
||||
{
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
|
||||
@@ -8,7 +8,7 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Packages)]
|
||||
[Tree(Constants.Applications.Packages, Constants.Trees.Packages, null, sortOrder: 0, isSingleNodeTree: true)]
|
||||
[Tree(Constants.Applications.Packages, Constants.Trees.Packages, SortOrder = 0, IsSingleNodeTree = true)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class PackagesTreeController : TreeController
|
||||
|
||||
@@ -10,10 +10,10 @@ namespace Umbraco.Web.Trees
|
||||
/// <summary>
|
||||
/// Tree for displaying partial view macros in the developer app
|
||||
/// </summary>
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, null, sortOrder: 8)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, SortOrder = 8, TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[CoreTree]
|
||||
public class PartialViewMacrosTreeController : PartialViewsTreeController
|
||||
{
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.MacroPartialsFileSystem;
|
||||
|
||||
@@ -10,10 +10,10 @@ namespace Umbraco.Web.Trees
|
||||
/// <summary>
|
||||
/// Tree for displaying partial views in the settings app
|
||||
/// </summary>
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 7)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, SortOrder = 7, TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[UmbracoTreeAuthorize(Constants.Trees.PartialViews)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[CoreTree]
|
||||
public class PartialViewsTreeController : FileSystemTreeController
|
||||
{
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.PartialViewsFileSystem;
|
||||
|
||||
@@ -9,9 +9,9 @@ using Umbraco.Web.Actions;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.RelationTypes)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, null, sortOrder: 5)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, SortOrder = 5, TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[Mvc.PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[CoreTree]
|
||||
public class RelationTypeTreeController : TreeController
|
||||
{
|
||||
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
|
||||
@@ -5,8 +5,8 @@ using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 10)]
|
||||
[CoreTree]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Scripts, TreeTitle = "Scripts", IconOpen = "icon-folder", IconClosed = "icon-folder", SortOrder = 10, TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
public class ScriptsTreeController : FileSystemTreeController
|
||||
{
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.ScriptsFileSystem; // TODO: inject
|
||||
|
||||
@@ -4,8 +4,8 @@ using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 9)]
|
||||
[CoreTree]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, TreeTitle = "Stylesheets", IconOpen = "icon-folder", IconClosed = "icon-folder", SortOrder = 9, TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
public class StylesheetsTreeController : FileSystemTreeController
|
||||
{
|
||||
protected override IFileSystem FileSystem => Current.FileSystems.StylesheetsFileSystem; // TODO: inject
|
||||
|
||||
@@ -16,9 +16,9 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Templates)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:6)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Templates, SortOrder = 6, TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Templating)]
|
||||
[CoreTree]
|
||||
public class TemplatesTreeController : TreeController, ISearchableTree
|
||||
{
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
|
||||
@@ -1,46 +1,48 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[DebuggerDisplay("Tree - {TreeAlias} ({ApplicationAlias})")]
|
||||
[DebuggerDisplay("Tree - {SectionAlias}/{TreeAlias}")]
|
||||
public class Tree : ITree
|
||||
{
|
||||
public Tree(int sortOrder, string applicationAlias, string alias, string title, Type treeControllerType, bool isSingleNodeTree)
|
||||
public Tree(int sortOrder, string applicationAlias, string group, string alias, string title, TreeUse use, Type treeControllerType, bool isSingleNodeTree)
|
||||
{
|
||||
SortOrder = sortOrder;
|
||||
ApplicationAlias = applicationAlias;
|
||||
SectionAlias = applicationAlias;
|
||||
TreeGroup = group;
|
||||
TreeAlias = alias;
|
||||
TreeTitle = title;
|
||||
TreeUse = use;
|
||||
TreeControllerType = treeControllerType;
|
||||
IsSingleNodeTree = isSingleNodeTree;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Gets or sets the sort order.
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application alias.
|
||||
/// </summary>
|
||||
public string ApplicationAlias { get; set; }
|
||||
/// <inheritdoc />
|
||||
public string SectionAlias { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string TreeGroup { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string TreeAlias { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Gets or sets the tree title (fallback if the tree alias isn't localized)
|
||||
/// </summary>
|
||||
/// <value>The title.</value>
|
||||
public string TreeTitle { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TreeUse TreeUse { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSingleNodeTree { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree controller type.
|
||||
/// </summary>
|
||||
public Type TreeControllerType { get; }
|
||||
|
||||
internal static string GetRootNodeDisplayName(ITree tree, ILocalizedTextService textService)
|
||||
@@ -64,6 +66,5 @@ namespace Umbraco.Web.Trees
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies an application tree
|
||||
/// Identifies a section tree.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class TreeAttribute : Attribute, ITree
|
||||
@@ -12,54 +11,55 @@ namespace Umbraco.Web.Trees
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TreeAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appAlias">The app alias.</param>
|
||||
/// <param name="treeAlias"></param>
|
||||
public TreeAttribute(string appAlias,
|
||||
string treeAlias) : this(appAlias, treeAlias, null)
|
||||
public TreeAttribute(string sectionAlias, string treeAlias)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TreeAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appAlias">The app alias.</param>
|
||||
/// <param name="treeAlias"></param>
|
||||
/// <param name="treeTitle"></param>
|
||||
/// <param name="iconClosed">The icon closed.</param>
|
||||
/// <param name="iconOpen">The icon open.</param>
|
||||
/// <param name="initialize">if set to <c>true</c> [initialize].</param>
|
||||
/// <param name="sortOrder">The sort order.</param>
|
||||
/// <param name="isSingleNodeTree">Flag to define if this tree is a single node tree (will never contain child nodes, full screen app)</param>
|
||||
public TreeAttribute(string appAlias,
|
||||
string treeAlias,
|
||||
string treeTitle,
|
||||
string iconClosed = "icon-folder",
|
||||
string iconOpen = "icon-folder-open",
|
||||
bool initialize = true,
|
||||
int sortOrder = 0,
|
||||
bool isSingleNodeTree = false)
|
||||
{
|
||||
ApplicationAlias = appAlias;
|
||||
SectionAlias = sectionAlias;
|
||||
TreeAlias = treeAlias;
|
||||
TreeTitle = treeTitle;
|
||||
IconClosed = iconClosed;
|
||||
IconOpen = iconOpen;
|
||||
Initialize = initialize;
|
||||
SortOrder = sortOrder;
|
||||
IsSingleNodeTree = isSingleNodeTree;
|
||||
}
|
||||
|
||||
public string ApplicationAlias { get; }
|
||||
public string TreeAlias { get; }
|
||||
public string TreeTitle { get; }
|
||||
public string IconClosed { get; }
|
||||
public string IconOpen { get; }
|
||||
public bool Initialize { get; }
|
||||
public int SortOrder { get; }
|
||||
/// <summary>
|
||||
/// Gets the section alias.
|
||||
/// </summary>
|
||||
public string SectionAlias { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app)
|
||||
/// Gets the tree alias.
|
||||
/// </summary>
|
||||
public bool IsSingleNodeTree { get; }
|
||||
public string TreeAlias { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tree title.
|
||||
/// </summary>
|
||||
public string TreeTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the group of the tree.
|
||||
/// </summary>
|
||||
public string TreeGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the usage of the tree.
|
||||
/// </summary>
|
||||
public TreeUse TreeUse { get; set; } = TreeUse.Main;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tree icon when closed.
|
||||
/// </summary>
|
||||
public string IconClosed { get; set; } = "icon-folder";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tree icon when open.
|
||||
/// </summary>
|
||||
public string IconOpen { get; set; } = "icon-folder-open";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tree sort order.
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the tree is a single-node tree (no child nodes, full screen app).
|
||||
/// </summary>
|
||||
public bool IsSingleNodeTree { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the collection of section trees.
|
||||
/// </summary>
|
||||
public class TreeCollection : BuilderCollectionBase<Tree>
|
||||
{
|
||||
public TreeCollection(IEnumerable<Tree> items)
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a <see cref="TreeCollection"/>.
|
||||
/// </summary>
|
||||
public class TreeCollectionBuilder : ICollectionBuilder<TreeCollection, Tree>
|
||||
{
|
||||
/// <summary>
|
||||
/// expose the list of trees which developers can manipulate before the collection is created
|
||||
/// </summary>
|
||||
public List<Tree> Trees { get; } = new List<Tree>();
|
||||
private readonly List<Tree> _trees = new List<Tree>();
|
||||
|
||||
public TreeCollection CreateCollection(IFactory factory) => new TreeCollection(Trees);
|
||||
public TreeCollection CreateCollection(IFactory factory) => new TreeCollection(_trees);
|
||||
|
||||
public void RegisterWith(IRegister register) => register.Register(CreateCollection, Lifetime.Singleton);
|
||||
|
||||
public void AddTreeController<TController>()
|
||||
where TController : TreeControllerBase
|
||||
=> AddTreeController(typeof(TController));
|
||||
|
||||
public void AddTreeController(Type controllerType)
|
||||
{
|
||||
if (!typeof(TreeControllerBase).IsAssignableFrom(controllerType))
|
||||
throw new ArgumentException($"Type {controllerType} does not inherit from {typeof(TreeControllerBase).FullName}.");
|
||||
|
||||
// no all TreeControllerBase are meant to be used here,
|
||||
// ignore those that don't have the attribute
|
||||
|
||||
var attribute = controllerType.GetCustomAttribute<TreeAttribute>(false);
|
||||
if (attribute == null) return;
|
||||
var tree = new Tree(attribute.SortOrder, attribute.SectionAlias, attribute.TreeGroup, attribute.TreeAlias, attribute.TreeTitle, attribute.TreeUse, controllerType, attribute.IsSingleNodeTree);
|
||||
_trees.Add(tree);
|
||||
}
|
||||
|
||||
public void AddTreeControllers(IEnumerable<Type> controllerTypes)
|
||||
{
|
||||
foreach (var controllerType in controllerTypes)
|
||||
AddTreeController(controllerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -15,53 +14,53 @@ namespace Umbraco.Web.Trees
|
||||
/// </summary>
|
||||
public abstract class TreeController : TreeControllerBase
|
||||
{
|
||||
private TreeAttribute _attribute;
|
||||
private static readonly ConcurrentDictionary<Type, TreeAttribute> TreeAttributeCache = new ConcurrentDictionary<Type, TreeAttribute>();
|
||||
|
||||
protected TreeController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState) : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState)
|
||||
private readonly TreeAttribute _treeAttribute;
|
||||
|
||||
protected TreeController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState)
|
||||
: base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState)
|
||||
{
|
||||
Initialize();
|
||||
_treeAttribute = GetTreeAttribute();
|
||||
}
|
||||
|
||||
protected TreeController()
|
||||
{
|
||||
Initialize();
|
||||
_treeAttribute = GetTreeAttribute();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string RootNodeDisplayName => Tree.GetRootNodeDisplayName(this, Services.TextService);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string TreeAlias => _attribute.TreeAlias;
|
||||
/// <inheritdoc />
|
||||
public override string TreeTitle => _attribute.TreeTitle;
|
||||
/// <inheritdoc />
|
||||
public override string ApplicationAlias => _attribute.ApplicationAlias;
|
||||
/// <inheritdoc />
|
||||
public override int SortOrder => _attribute.SortOrder;
|
||||
/// <inheritdoc />
|
||||
public override bool IsSingleNodeTree => _attribute.IsSingleNodeTree;
|
||||
public override string TreeGroup => _treeAttribute.TreeGroup;
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_attribute = GetTreeAttribute();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override string TreeAlias => _treeAttribute.TreeAlias;
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, TreeAttribute> TreeAttributeCache = new ConcurrentDictionary<Type, TreeAttribute>();
|
||||
/// <inheritdoc />
|
||||
public override string TreeTitle => _treeAttribute.TreeTitle;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TreeUse TreeUse => _treeAttribute.TreeUse;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string SectionAlias => _treeAttribute.SectionAlias;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int SortOrder => _treeAttribute.SortOrder;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsSingleNodeTree => _treeAttribute.IsSingleNodeTree;
|
||||
|
||||
private TreeAttribute GetTreeAttribute()
|
||||
{
|
||||
return TreeAttributeCache.GetOrAdd(GetType(), type =>
|
||||
{
|
||||
//Locate the tree attribute
|
||||
var treeAttributes = type
|
||||
.GetCustomAttributes<TreeAttribute>(false)
|
||||
.ToArray();
|
||||
|
||||
if (treeAttributes.Length == 0)
|
||||
var treeAttribute = type.GetCustomAttribute<TreeAttribute>(false);
|
||||
if (treeAttribute == null)
|
||||
throw new InvalidOperationException("The Tree controller is missing the " + typeof(TreeAttribute).FullName + " attribute");
|
||||
|
||||
//assign the properties of this object to those of the metadata attribute
|
||||
return treeAttributes[0];
|
||||
return treeAttribute;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http.Routing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -19,7 +18,7 @@ using Umbraco.Web.WebApi.Filters;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// A base controller reference for non-attributed trees (un-registered).
|
||||
/// A base controller reference for non-attributed trees (un-registered).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Developers should generally inherit from TreeController.
|
||||
@@ -28,13 +27,11 @@ namespace Umbraco.Web.Trees
|
||||
public abstract class TreeControllerBase : UmbracoAuthorizedApiController, ITree
|
||||
{
|
||||
protected TreeControllerBase()
|
||||
{
|
||||
}
|
||||
|
||||
protected TreeControllerBase(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState) : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
protected TreeControllerBase(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState)
|
||||
: base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// The method called to render the contents of the tree structure
|
||||
@@ -62,14 +59,24 @@ namespace Umbraco.Web.Trees
|
||||
/// </summary>
|
||||
public abstract string RootNodeDisplayName { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string TreeGroup { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string TreeAlias { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string TreeTitle { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string ApplicationAlias { get; }
|
||||
public abstract TreeUse TreeUse { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string SectionAlias { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract int SortOrder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsSingleNodeTree { get; }
|
||||
|
||||
@@ -402,5 +409,4 @@ namespace Umbraco.Web.Trees
|
||||
handler?.Invoke(instance, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
26
src/Umbraco.Web/Trees/TreeUse.cs
Normal file
26
src/Umbraco.Web/Trees/TreeUse.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines tree uses.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TreeUse
|
||||
{
|
||||
/// <summary>
|
||||
/// The tree is not used.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The tree is used as a main (section) tree.
|
||||
/// </summary>
|
||||
Main = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The tree is used as a dialog.
|
||||
/// </summary>
|
||||
Dialog = 2,
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Users)]
|
||||
[Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 0, isSingleNodeTree: true)]
|
||||
[Tree(Constants.Applications.Users, Constants.Trees.Users, SortOrder = 0, IsSingleNodeTree = true)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class UserTreeController : TreeController
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Trees;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
@@ -10,29 +8,20 @@ using Umbraco.Core.Composing;
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for the PluginTypemgr
|
||||
/// Provides extension methods for the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
public static class TypeLoaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all available TreeApiController's in application that are attribute with TreeAttribute
|
||||
/// Gets all types implementing <see cref="SurfaceController"/>.
|
||||
/// </summary>
|
||||
/// <param name="mgr"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<Type> GetAttributedTreeControllers(this TypeLoader mgr)
|
||||
{
|
||||
return mgr.GetTypesWithAttribute<TreeController, TreeAttribute>();
|
||||
}
|
||||
internal static IEnumerable<Type> GetSurfaceControllers(this TypeLoader typeLoader)
|
||||
=> typeLoader.GetTypes<SurfaceController>();
|
||||
|
||||
internal static IEnumerable<Type> GetSurfaceControllers(this TypeLoader mgr)
|
||||
{
|
||||
return mgr.GetTypes<SurfaceController>();
|
||||
}
|
||||
|
||||
internal static IEnumerable<Type> GetUmbracoApiControllers(this TypeLoader mgr)
|
||||
{
|
||||
return mgr.GetTypes<UmbracoApiController>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all types implementing <see cref="UmbracoApiController"/>.
|
||||
/// </summary>
|
||||
internal static IEnumerable<Type> GetUmbracoApiControllers(this TypeLoader typeLoader)
|
||||
=> typeLoader.GetTypes<UmbracoApiController>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
<Compile Include="Trees\SettingsBackOfficeSection.cs" />
|
||||
<Compile Include="Trees\TranslationBackOfficeSection.cs" />
|
||||
<Compile Include="Trees\TreeCollectionBuilder.cs" />
|
||||
<Compile Include="Trees\TreeUse.cs" />
|
||||
<Compile Include="Trees\UsersBackOfficeSection.cs" />
|
||||
<Compile Include="Trees\Tree.cs" />
|
||||
<Compile Include="Trees\ITree.cs" />
|
||||
@@ -1127,7 +1128,6 @@
|
||||
<Compile Include="Routing\EnsureRoutableOutcome.cs" />
|
||||
<Compile Include="Routing\PublishedRouter.cs" />
|
||||
<Compile Include="Routing\UrlProvider.cs" />
|
||||
<Compile Include="Routing\WebServicesRouteConstraint.cs" />
|
||||
<Compile Include="Search\ExamineSearcherModel.cs" />
|
||||
<Compile Include="Search\ExamineComponent.cs" />
|
||||
<Compile Include="Components\DatabaseServerRegistrarAndMessengerComponent.cs" />
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
var apps = _treeAliases.Select(x => Current.TreeService
|
||||
.GetByAlias(x))
|
||||
.WhereNotNull()
|
||||
.Select(x => x.ApplicationAlias)
|
||||
.Select(x => x.SectionAlias)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.Remoting.Contexts;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
@@ -15,52 +13,42 @@ namespace Umbraco.Web.WebApi
|
||||
internal static class HttpControllerContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This method will go an execute the authorization filters for the controller action, if any fail
|
||||
/// it will return their response, otherwise we'll return null.
|
||||
/// Invokes the authorization filters for the controller action.
|
||||
/// </summary>
|
||||
/// <param name="controllerContext"></param>
|
||||
/// <returns>The response of the first filter returning a result, if any, otherwise null (authorized).</returns>
|
||||
internal static async Task<HttpResponseMessage> InvokeAuthorizationFiltersForRequest(this HttpControllerContext controllerContext)
|
||||
{
|
||||
var controllerDescriptor = controllerContext.ControllerDescriptor;
|
||||
var controllerServices = controllerDescriptor.Configuration.Services;
|
||||
var actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
|
||||
var actionContext = new HttpActionContext(controllerContext, actionDescriptor);
|
||||
|
||||
var filters = actionDescriptor.GetFilterPipeline();
|
||||
var filterGrouping = new FilterGrouping(filters);
|
||||
var actionFilters = filterGrouping.ActionFilters;
|
||||
// Because the continuation gets built from the inside out we need to reverse the filter list
|
||||
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
|
||||
var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToArray();
|
||||
|
||||
if (authorizationFilters.Any())
|
||||
{
|
||||
var cancelToken = new CancellationToken();
|
||||
var filterResult = await FilterContinuation(actionContext, cancelToken, authorizationFilters, 0);
|
||||
if (filterResult != null)
|
||||
{
|
||||
//this means that the authorization filter has returned a result - unauthorized so we cannot continue
|
||||
return filterResult;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
// because the continuation gets built from the inside out we need to reverse the filter list
|
||||
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
|
||||
var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToList();
|
||||
|
||||
if (authorizationFilters.Count == 0)
|
||||
return null;
|
||||
|
||||
// if the authorization filter returns a result, it means it failed to authorize
|
||||
var actionContext = new HttpActionContext(controllerContext, actionDescriptor);
|
||||
return await ExecuteAuthorizationFiltersAsync(actionContext, CancellationToken.None, authorizationFilters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is how you execute a chain of filters, it needs to recursively call in to itself as the continuation for the next filter in the chain
|
||||
/// Executes a chain of filters.
|
||||
/// </summary>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="filters"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<HttpResponseMessage> FilterContinuation(HttpActionContext actionContext, CancellationToken token, IList<IAuthorizationFilter> filters, int index)
|
||||
/// <remarks>
|
||||
/// Recursively calls in to itself as its continuation for the next filter in the chain.
|
||||
/// </remarks>
|
||||
private static async Task<HttpResponseMessage> ExecuteAuthorizationFiltersAsync(HttpActionContext actionContext, CancellationToken token, IList<IAuthorizationFilter> filters, int index = 0)
|
||||
{
|
||||
return await filters[index].ExecuteAuthorizationFilterAsync(actionContext, token,
|
||||
() => (index + 1) == filters.Count
|
||||
() => ++index == filters.Count
|
||||
? Task.FromResult<HttpResponseMessage>(null)
|
||||
: FilterContinuation(actionContext, token, filters, ++index));
|
||||
: ExecuteAuthorizationFiltersAsync(actionContext, token, filters, index));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user