From cfe1b17eecc5a06b56530610d9e04158033e66a4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Jan 2019 16:40:11 +1100 Subject: [PATCH] Gets trees and sections loading now in a much more simplified way without any xml, now to cleanup --- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 +- src/Umbraco.Web/Composing/Current.cs | 7 + .../Editors/BackOfficeServerVariables.cs | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 6 +- src/Umbraco.Web/Editors/SectionController.cs | 8 +- .../Models/ContentEditing/ApplicationTree.cs | 177 --------------- .../Mvc/SurfaceControllerTypeCollection.cs | 4 +- src/Umbraco.Web/Runtime/WebRuntimeComposer.cs | 13 +- .../Search/SearchableTreeCollection.cs | 8 +- .../Services/IApplicationTreeService.cs | 153 ------------- src/Umbraco.Web/Services/ISectionService.cs | 89 +------- src/Umbraco.Web/Services/ITreeService.cs | 49 +++++ ...plicationTreeService.cs => TreeService.cs} | 40 ++-- src/Umbraco.Web/Trees/ApplicationTree.cs | 69 ++++++ .../Trees/ApplicationTreeController.cs | 173 +++++++++++---- src/Umbraco.Web/Trees/ITree.cs | 31 +++ src/Umbraco.Web/Trees/TreeAttribute.cs | 35 +-- src/Umbraco.Web/Trees/TreeCollection.cs | 21 +- src/Umbraco.Web/Trees/TreeController.cs | 55 +++-- src/Umbraco.Web/Trees/TreeControllerBase.cs | 14 +- .../Trees/TreeControllerResolver.cs | 203 ------------------ .../Trees/UserPermissionsTreeController.cs | 52 ----- src/Umbraco.Web/Umbraco.Web.csproj | 10 +- .../Filters/UmbracoTreeAuthorizeAttribute.cs | 20 +- src/Umbraco.Web/WebApi/MvcVersionCheck.cs | 10 - .../UmbracoApiControllerTypeCollection.cs | 2 - 26 files changed, 425 insertions(+), 828 deletions(-) delete mode 100644 src/Umbraco.Web/Models/ContentEditing/ApplicationTree.cs delete mode 100644 src/Umbraco.Web/Services/IApplicationTreeService.cs create mode 100644 src/Umbraco.Web/Services/ITreeService.cs rename src/Umbraco.Web/Services/{ApplicationTreeService.cs => TreeService.cs} (95%) create mode 100644 src/Umbraco.Web/Trees/ApplicationTree.cs create mode 100644 src/Umbraco.Web/Trees/ITree.cs delete mode 100644 src/Umbraco.Web/Trees/TreeControllerResolver.cs delete mode 100644 src/Umbraco.Web/Trees/UserPermissionsTreeController.cs delete mode 100644 src/Umbraco.Web/WebApi/MvcVersionCheck.cs diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index a79531af74..0a0999f2dc 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -342,7 +342,7 @@ namespace Umbraco.Tests.Testing Composition.ComposeServices(); // composition root is doing weird things, fix - Composition.RegisterUnique(); + Composition.RegisterUnique(); Composition.RegisterUnique(); // somehow property editor ends up wanting this diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 1e8f3d17f7..d887720063 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -25,6 +25,7 @@ using Umbraco.Web.HealthCheck; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; +using Umbraco.Web.Services; using Umbraco.Web.WebApi; using CoreCurrent = Umbraco.Core.Composing.Current; @@ -135,6 +136,12 @@ namespace Umbraco.Web.Composing internal static IPublishedSnapshotService PublishedSnapshotService => Factory.GetInstance(); + public static ITreeService TreeService + => Factory.GetInstance(); + + public static ISectionService SectionService + => Factory.GetInstance(); + #endregion #region Web Constants diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 65ab27d76a..1525dd5808 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -408,7 +408,7 @@ namespace Umbraco.Web.Editors let pluginAttr = p.attributes.OfType().Single() select new Dictionary { - {"alias", treeAttr.Alias}, {"packageFolder", pluginAttr.AreaName} + {"alias", treeAttr.TreeAlias}, {"packageFolder", pluginAttr.AreaName} }).ToArray(); } diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index c8368d11b0..ca1a6a12bb 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -42,10 +42,10 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class EntityController : UmbracoAuthorizedJsonController { - private readonly IApplicationTreeService _treeService; + private readonly ITreeService _treeService; public EntityController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, CacheHelper applicationCache, IProfilingLogger logger, IRuntimeState runtimeState, - IApplicationTreeService treeService) + ITreeService treeService) : base(globalSettings, umbracoContext, sqlContext, services, applicationCache, logger, runtimeState) { _treeService = treeService; @@ -148,7 +148,7 @@ namespace Umbraco.Web.Editors var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); - result[tree.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult + result[ApplicationTree.GetRootNodeDisplayName(tree, Services.TextService)] = new TreeSearchResult { Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out var total), TreeAlias = searchableTree.Key, diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 7b484b09b1..d68f062f61 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -24,16 +24,14 @@ namespace Umbraco.Web.Editors { private readonly Dashboards _dashboards; private readonly ISectionService _sectionService; - private readonly TreeControllerResolver _treeControllerResolver; - private readonly IApplicationTreeService _treeService; + private readonly ITreeService _treeService; public SectionController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, CacheHelper applicationCache, IProfilingLogger logger, IRuntimeState runtimeState, - Dashboards dashboards, ISectionService sectionService, TreeControllerResolver treeControllerResolver, IApplicationTreeService treeService) + Dashboards dashboards, ISectionService sectionService, ITreeService treeService) : base(globalSettings, umbracoContext, sqlContext, services, applicationCache, logger, runtimeState) { _dashboards = dashboards; _sectionService = sectionService; - _treeControllerResolver = treeControllerResolver; _treeService = treeService; } @@ -45,7 +43,7 @@ namespace Umbraco.Web.Editors // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that // since tree's by nature are controllers and require request contextual data - var appTreeController = new ApplicationTreeController(GlobalSettings, UmbracoContext, SqlContext, Services, ApplicationCache, Logger, RuntimeState, _treeControllerResolver, _treeService) + var appTreeController = new ApplicationTreeController(GlobalSettings, UmbracoContext, SqlContext, Services, ApplicationCache, Logger, RuntimeState, _treeService) { ControllerContext = ControllerContext }; diff --git a/src/Umbraco.Web/Models/ContentEditing/ApplicationTree.cs b/src/Umbraco.Web/Models/ContentEditing/ApplicationTree.cs deleted file mode 100644 index e9befb0e27..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/ApplicationTree.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Diagnostics; -using Umbraco.Core.Services; - -namespace Umbraco.Web.Models.ContentEditing -{ - [DebuggerDisplay("Tree - {Alias} ({ApplicationAlias})")] - public class ApplicationTree - { - //private static readonly ConcurrentDictionary ResolvedTypes = new ConcurrentDictionary(); - - ///// - ///// Initializes a new instance of the class. - ///// - //public ApplicationTree() { } - - ///// - ///// Initializes a new instance of the class. - ///// - ///// if set to true [initialize]. - ///// The sort order. - ///// The application alias. - ///// The tree alias. - ///// The tree title. - ///// The icon closed. - ///// The icon opened. - ///// The tree type. - //public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) - //{ - // //Initialize = initialize; - // SortOrder = sortOrder; - // ApplicationAlias = applicationAlias; - // Alias = alias; - // Title = title; - // IconClosed = iconClosed; - // IconOpened = iconOpened; - // Type = type; - - //} - - public ApplicationTree(int sortOrder, string applicationAlias, string alias, string title) - { - SortOrder = sortOrder; - ApplicationAlias = applicationAlias; - Alias = alias; - Title = title; - } - - ///// - ///// Gets or sets a value indicating whether this should initialize. - ///// - ///// true if initialize; otherwise, false. - //public bool Initialize { get; set; } - - /// - /// Gets or sets the sort order. - /// - /// The sort order. - public int SortOrder { get; set; } - - /// - /// Gets the application alias. - /// - /// The application alias. - public string ApplicationAlias { get; } - - /// - /// Gets the tree alias. - /// - /// The alias. - public string Alias { get; } - - /// - /// Gets or sets the tree title (fallback if the tree alias isn't localized) - /// - /// The title. - public string Title { get; set; } - - ///// - ///// Gets or sets the icon closed. - ///// - ///// The icon closed. - //public string IconClosed { get; set; } - - ///// - ///// Gets or sets the icon opened. - ///// - ///// The icon opened. - //public string IconOpened { get; set; } - - ///// - ///// Gets or sets the tree type assembly name. - ///// - ///// The type. - //public string Type { get; set; } - - /// - /// Returns the localized root node display name - /// - /// - /// - public string GetRootNodeDisplayName(ILocalizedTextService textService) - { - var label = $"[{Alias}]"; - - // try to look up a the localized tree header matching the tree alias - var localizedLabel = textService.Localize("treeHeaders/" + Alias); - - // if the localizedLabel returns [alias] then return the title if it's defined - if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase)) - { - if (string.IsNullOrEmpty(Title) == false) - label = Title; - } - else - { - // the localizedLabel translated into something that's not just [alias], so use the translation - label = localizedLabel; - } - - return label; - } - - //private Type _runtimeType; - - ///// - ///// Returns the CLR type based on it's assembly name stored in the config - ///// - ///// - //public Type GetRuntimeType() - //{ - // return _runtimeType ?? (_runtimeType = System.Type.GetType(Type)); - //} - - ///// - ///// Used to try to get and cache the tree type - ///// - ///// - ///// - //internal static Type TryGetType(string type) - //{ - // try - // { - // return ResolvedTypes.GetOrAdd(type, s => - // { - // var result = System.Type.GetType(type); - // if (result != null) - // { - // return result; - // } - - // //we need to implement a bit of a hack here due to some trees being renamed and backwards compat - // var parts = type.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - // if (parts.Length != 2) - // throw new InvalidOperationException("Could not resolve type"); - // if (parts[1].Trim() != "Umbraco.Web" || parts[0].StartsWith("Umbraco.Web.Trees") == false || parts[0].EndsWith("Controller")) - // throw new InvalidOperationException("Could not resolve type"); - - // //if it's one of our controllers but it's not suffixed with "Controller" then add it and try again - // var tempType = parts[0] + "Controller, Umbraco.Web"; - - // result = System.Type.GetType(tempType); - // if (result != null) - // return result; - - // throw new InvalidOperationException("Could not resolve type"); - // }); - // } - // catch (InvalidOperationException) - // { - // //swallow, this is our own exception, couldn't find the type - // // fixme bad use of exceptions here! - // return null; - // } - //} - } -} diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs index eef6ab1ca2..d9577417cc 100644 --- a/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs +++ b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs @@ -7,9 +7,7 @@ namespace Umbraco.Web.Mvc // unless we want to modify the content of the collection // which we are not doing at the moment // we can inherit from BuilderCollectionBase and just be enumerable - - //fixme: this should be LazyCollectionBuilderBase ? - + public class SurfaceControllerTypeCollection : BuilderCollectionBase { public SurfaceControllerTypeCollection(IEnumerable items) diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs index 0ef9adddb7..047d080a88 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs @@ -95,7 +95,7 @@ namespace Umbraco.Web.Runtime // replace some services composition.RegisterUnique(); composition.RegisterUnique(); - composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(factory => ExamineManager.Instance); @@ -125,9 +125,11 @@ namespace Umbraco.Web.Runtime composition.WithCollectionBuilder() .Add(() => composition.TypeLoader.GetTypes()); + //we need to eagerly scan controller types since they will need to be routed var surfaceControllerTypes = new SurfaceControllerTypeCollection(composition.TypeLoader.GetSurfaceControllers()); composition.RegisterUnique(surfaceControllerTypes); + //we need to eagerly scan controller types since they will need to be routed var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(composition.TypeLoader.GetUmbracoApiControllers()); composition.RegisterUnique(umbracoApiControllerTypes); @@ -198,8 +200,13 @@ namespace Umbraco.Web.Runtime .Add(() => composition.TypeLoader.GetTypes()); // register back office trees - composition.WithCollectionBuilder() - .Add(() => composition.TypeLoader.GetTypes()); + foreach (var treeControllerType in umbracoApiControllerTypes) + { + var attribute = treeControllerType.GetCustomAttribute(false); + if (attribute == null) continue; + var tree = new ApplicationTree(attribute.SortOrder, attribute.ApplicationAlias, attribute.TreeAlias, attribute.TreeTitle, treeControllerType, attribute.IsSingleNodeTree); + composition.WithCollectionBuilder().AddTree(tree); + } } } } diff --git a/src/Umbraco.Web/Search/SearchableTreeCollection.cs b/src/Umbraco.Web/Search/SearchableTreeCollection.cs index 2b1baa5194..032782b466 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollection.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollection.cs @@ -14,13 +14,13 @@ namespace Umbraco.Web.Search { private readonly Dictionary _dictionary; - public SearchableTreeCollection(IEnumerable items, IApplicationTreeService treeService) + public SearchableTreeCollection(IEnumerable items, ITreeService treeService) : base(items) { _dictionary = CreateDictionary(treeService); } - private Dictionary CreateDictionary(IApplicationTreeService treeService) + private Dictionary CreateDictionary(ITreeService treeService) { var appTrees = treeService.GetAll() .OrderBy(x => x.SortOrder) @@ -29,10 +29,10 @@ namespace Umbraco.Web.Search var searchableTrees = this.ToArray(); foreach (var appTree in appTrees) { - var found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.Alias)); + var found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias)); if (found != null) { - dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.ApplicationAlias, appTree.Alias, found); + dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.ApplicationAlias, appTree.TreeAlias, found); } } return dictionary; diff --git a/src/Umbraco.Web/Services/IApplicationTreeService.cs b/src/Umbraco.Web/Services/IApplicationTreeService.cs deleted file mode 100644 index 1fd7bc939a..0000000000 --- a/src/Umbraco.Web/Services/IApplicationTreeService.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Services -{ - public interface IApplicationTreeService - { - ///// - ///// Creates a new application tree. - ///// - ///// if set to true [initialize]. - ///// The sort order. - ///// The application alias. - ///// The alias. - ///// The title. - ///// The icon closed. - ///// The icon opened. - ///// The type. - //void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type); - - ///// - ///// Saves this instance. - ///// - //void SaveTree(ApplicationTree tree); - - ///// - ///// Deletes this instance. - ///// - //void DeleteTree(ApplicationTree tree); - - /// - /// Gets an ApplicationTree by it's tree alias. - /// - /// The tree alias. - /// An ApplicationTree instance - ApplicationTree GetByAlias(string treeAlias); - - /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. - /// - /// Returns a ApplicationTree Array - IEnumerable GetAll(); - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// Returns a ApplicationTree Array - IEnumerable GetApplicationTrees(string applicationAlias); - - ///// - ///// Gets the application tree for the applcation with the specified alias - ///// - ///// The application alias. - ///// - ///// Returns a ApplicationTree Array - //IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); - - /// - /// Gets the grouped application trees for the application with the specified alias - /// - /// - /// - IDictionary> GetGroupedApplicationTrees(string applicationAlias); - } - - /// - /// Purely used to allow a service context to create the default services - /// - internal class EmptyApplicationTreeService : IApplicationTreeService - { - ///// - ///// Creates a new application tree. - ///// - ///// if set to true [initialize]. - ///// The sort order. - ///// The application alias. - ///// The alias. - ///// The title. - ///// The icon closed. - ///// The icon opened. - ///// The type. - //public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) - //{ - // throw new System.NotImplementedException(); - //} - - ///// - ///// Saves this instance. - ///// - //public void SaveTree(ApplicationTree tree) - //{ - // throw new System.NotImplementedException(); - //} - - ///// - ///// Deletes this instance. - ///// - //public void DeleteTree(ApplicationTree tree) - //{ - // throw new System.NotImplementedException(); - //} - - /// - /// Gets an ApplicationTree by it's tree alias. - /// - /// The tree alias. - /// An ApplicationTree instance - public ApplicationTree GetByAlias(string treeAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. - /// - /// Returns a ApplicationTree Array - public IEnumerable GetAll() - { - throw new System.NotImplementedException(); - } - - public IDictionary> GetGroupedApplicationTrees(string applicationAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// - /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Web/Services/ISectionService.cs b/src/Umbraco.Web/Services/ISectionService.cs index 560805634f..c325020cf1 100644 --- a/src/Umbraco.Web/Services/ISectionService.cs +++ b/src/Umbraco.Web/Services/ISectionService.cs @@ -25,92 +25,7 @@ namespace Umbraco.Web.Services /// The application alias. /// IBackOfficeSection GetByAlias(string appAlias); - - ///// - ///// Creates a new applcation if no application with the specified alias is found. - ///// - ///// The application name. - ///// The application alias. - ///// The application icon, which has to be located in umbraco/images/tray folder. - //void MakeNew(string name, string alias, string icon); - - ///// - ///// Makes the new. - ///// - ///// The name. - ///// The alias. - ///// The icon. - ///// The sort order. - //void MakeNew(string name, string alias, string icon, int sortOrder); - - ///// - ///// Deletes the section - ///// - //void DeleteSection(Section section); - } - - /// - /// Purely used to allow a service context to create the default services - /// - internal class EmptySectionService : ISectionService - { - /// - /// The cache storage for all applications - /// - public IEnumerable GetSections() - { - throw new System.NotImplementedException(); - } - - /// - /// Get the user's allowed sections - /// - /// - /// - public IEnumerable GetAllowedSections(int userId) - { - throw new System.NotImplementedException(); - } - - /// - /// Gets the application by its alias. - /// - /// The application alias. - /// - public IBackOfficeSection GetByAlias(string appAlias) - { - throw new System.NotImplementedException(); - } - - ///// - ///// Creates a new applcation if no application with the specified alias is found. - ///// - ///// The application name. - ///// The application alias. - ///// The application icon, which has to be located in umbraco/images/tray folder. - //public void MakeNew(string name, string alias, string icon) - //{ - // throw new System.NotImplementedException(); - //} - - ///// - ///// Makes the new. - ///// - ///// The name. - ///// The alias. - ///// The icon. - ///// The sort order. - //public void MakeNew(string name, string alias, string icon, int sortOrder) - //{ - // throw new System.NotImplementedException(); - //} - - ///// - ///// Deletes the section - ///// - //public void DeleteSection(IBackOfficeSection section) - //{ - // throw new System.NotImplementedException(); - //} + } + } diff --git a/src/Umbraco.Web/Services/ITreeService.cs b/src/Umbraco.Web/Services/ITreeService.cs new file mode 100644 index 0000000000..ce734cc5e4 --- /dev/null +++ b/src/Umbraco.Web/Services/ITreeService.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Services +{ + public interface ITreeService + { + /// + /// Gets an ApplicationTree by it's tree alias. + /// + /// The tree alias. + /// An ApplicationTree instance + ApplicationTree GetByAlias(string treeAlias); + + /// + /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. + /// + /// Returns a ApplicationTree Array + IEnumerable GetAll(); + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// Returns a ApplicationTree Array + IEnumerable GetApplicationTrees(string applicationAlias); + + ///// + ///// Gets the application tree for the applcation with the specified alias + ///// + ///// The application alias. + ///// + ///// Returns a ApplicationTree Array + //IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); + + /// + /// Gets the grouped application trees for the application with the specified alias + /// + /// + /// + IDictionary> GetGroupedApplicationTrees(string applicationAlias); + } + +} diff --git a/src/Umbraco.Web/Services/ApplicationTreeService.cs b/src/Umbraco.Web/Services/TreeService.cs similarity index 95% rename from src/Umbraco.Web/Services/ApplicationTreeService.cs rename to src/Umbraco.Web/Services/TreeService.cs index 72b26e26d3..cf010f1480 100644 --- a/src/Umbraco.Web/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Web/Services/TreeService.cs @@ -18,18 +18,18 @@ using Umbraco.Web.Trees; namespace Umbraco.Web.Services { - internal class ApplicationTreeService : IApplicationTreeService + internal class TreeService : ITreeService { private readonly ILogger _logger; private readonly TreeCollection _treeCollection; private static readonly object Locker = new object(); private readonly Lazy>> _groupedTrees; - public ApplicationTreeService(ILogger logger, TreeCollection treeCollection) + public TreeService(ILogger logger, TreeCollection treeCollection) { _logger = logger; _treeCollection = treeCollection; - //_groupedTrees = new Lazy>>(InitGroupedTrees); + _groupedTrees = new Lazy>>(InitGroupedTrees); } ///// @@ -233,7 +233,7 @@ namespace Umbraco.Web.Services //} /// - public ApplicationTree GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(t => t.Alias == treeAlias); + public ApplicationTree GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(t => t.TreeAlias == treeAlias); /// public IEnumerable GetAll() => _treeCollection; @@ -273,7 +273,7 @@ namespace Umbraco.Web.Services { foreach(var treeAliasInGroup in treeGroup) { - if (tree.Alias == treeAliasInGroup) + if (tree.TreeAlias == treeAliasInGroup) { if (resultGroup == null) resultGroup = new List(); resultGroup.Add(tree); @@ -286,21 +286,21 @@ namespace Umbraco.Web.Services return result; } - ///// - ///// Creates a group of all tree groups and their tree aliases - ///// - ///// - ///// - ///// Used to initialize the field - ///// - //private IReadOnlyCollection> InitGroupedTrees() - //{ - // var result = GetAll() - // .Select(x => (treeAlias: x.Alias, treeGroup: x.GetRuntimeType().GetCustomAttribute(false)?.TreeGroup)) - // .GroupBy(x => x.treeGroup, x => x.treeAlias) - // .ToList(); - // return result; - //} + /// + /// Creates a group of all tree groups and their tree aliases + /// + /// + /// + /// Used to initialize the field + /// + private IReadOnlyCollection> InitGroupedTrees() + { + var result = GetAll() + .Select(x => (treeAlias: x.TreeAlias, treeGroup: x.TreeControllerType.GetCustomAttribute(false)?.TreeGroup)) + .GroupBy(x => x.treeGroup, x => x.treeAlias) + .ToList(); + return result; + } ///// ///// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the diff --git a/src/Umbraco.Web/Trees/ApplicationTree.cs b/src/Umbraco.Web/Trees/ApplicationTree.cs new file mode 100644 index 0000000000..5ed9847be3 --- /dev/null +++ b/src/Umbraco.Web/Trees/ApplicationTree.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + [DebuggerDisplay("Tree - {TreeAlias} ({ApplicationAlias})")] + public class ApplicationTree : ITree + { + public ApplicationTree(int sortOrder, string applicationAlias, string alias, string title, Type treeControllerType, bool isSingleNodeTree) + { + SortOrder = sortOrder; + ApplicationAlias = applicationAlias; + TreeAlias = alias; + TreeTitle = title; + TreeControllerType = treeControllerType; + IsSingleNodeTree = isSingleNodeTree; + } + + /// + /// + /// Gets or sets the sort order. + /// + public int SortOrder { get; set; } + + /// + /// Gets the application alias. + /// + public string ApplicationAlias { get; set; } + + /// + public string TreeAlias { get; } + + /// + /// + /// Gets or sets the tree title (fallback if the tree alias isn't localized) + /// + /// The title. + public string TreeTitle { get; set; } + + public bool IsSingleNodeTree { get; } + + public Type TreeControllerType { get; } + + public static string GetRootNodeDisplayName(ITree tree, ILocalizedTextService textService) + { + var label = $"[{tree.TreeAlias}]"; + + // try to look up a the localized tree header matching the tree alias + var localizedLabel = textService.Localize("treeHeaders/" + tree.TreeAlias); + + // if the localizedLabel returns [alias] then return the title if it's defined + if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(tree.TreeTitle) == false) + label = tree.TreeTitle; + } + else + { + // the localizedLabel translated into something that's not just [alias], so use the translation + label = localizedLabel; + } + + return label; + } + + } +} diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7e7c77d607..0e405ca3f3 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -3,9 +3,13 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Http.Formatting; using System.Threading.Tasks; using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Routing; +using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; @@ -24,19 +28,20 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { + /// + /// Used to return tree root nodes + /// [AngularJsonOnlyConfiguration] [PluginController("UmbracoTrees")] public class ApplicationTreeController : UmbracoAuthorizedApiController { - private readonly TreeControllerResolver _treeControllerResolver; - private readonly IApplicationTreeService _treeService; + private readonly ITreeService _treeService; public ApplicationTreeController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, CacheHelper applicationCache, IProfilingLogger logger, - IRuntimeState runtimeState, TreeControllerResolver treeControllerResolver, IApplicationTreeService treeService) + IRuntimeState runtimeState, ITreeService treeService) : base(globalSettings, umbracoContext, sqlContext, services, applicationCache, logger, runtimeState) { - _treeControllerResolver = treeControllerResolver; _treeService = treeService; } @@ -46,10 +51,9 @@ namespace Umbraco.Web.Trees /// The application to load tree for /// An optional single tree alias, if specified will only load the single tree for the request app /// - /// An optional bool (defaults to true), if set to false it will also load uninitialized trees /// [HttpQueryStringFilter("queryStrings")] - public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings/*, bool onlyInitialized = true*/) + public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings) { application = application.CleanForXss(); @@ -62,7 +66,7 @@ namespace Umbraco.Web.Trees if (string.IsNullOrEmpty(tree) == false || allTrees.Count == 1) { var apptree = !tree.IsNullOrWhiteSpace() - ? allTrees.FirstOrDefault(x => x.Alias == tree) + ? allTrees.FirstOrDefault(x => x.TreeAlias == tree) : allTrees.FirstOrDefault(); if (apptree == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -153,15 +157,15 @@ namespace Umbraco.Web.Trees /// /// Get the root node for an application with multiple trees /// - /// + /// /// /// - private async Task GetRootForMultipleAppTree(ApplicationTree configTree, FormDataCollection queryStrings) + private async Task GetRootForMultipleAppTree(ApplicationTree tree, FormDataCollection queryStrings) { - if (configTree == null) throw new ArgumentNullException(nameof(configTree)); + if (tree == null) throw new ArgumentNullException(nameof(tree)); try { - var byControllerAttempt = await _treeControllerResolver.TryGetRootNodeFromControllerTree(configTree, queryStrings, ControllerContext); + var byControllerAttempt = await TryGetRootNodeFromControllerTree(tree, queryStrings, ControllerContext); if (byControllerAttempt.Success) { return byControllerAttempt.Result; @@ -174,54 +178,133 @@ namespace Umbraco.Web.Trees return null; } - throw new ApplicationException("Could not get root node for tree type " + configTree.Alias); + throw new ApplicationException("Could not get root node for tree type " + tree.TreeAlias); } /// /// Get the root node for an application with one tree /// - /// + /// /// /// /// /// - private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) + private async Task GetRootForSingleAppTree(ApplicationTree tree, string id, FormDataCollection queryStrings, string application) { var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - if (configTree == null) throw new ArgumentNullException(nameof(configTree)); - var byControllerAttempt = _treeControllerResolver.TryLoadFromControllerTree(configTree, id, queryStrings, ControllerContext); - if (byControllerAttempt.Success) + 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) { - var rootNode = await _treeControllerResolver.TryGetRootNodeFromControllerTree(configTree, 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 " + configTree.Alias); - } - - var treeAttribute = _treeControllerResolver.GetTreeAttribute(configTree); - - var sectionRoot = TreeRootNode.CreateSingleTreeRoot( - rootId, - rootNode.Result.ChildNodesUrl, - rootNode.Result.MenuUrl, - rootNode.Result.Name, - byControllerAttempt.Result, - treeAttribute.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; - - foreach (var d in rootNode.Result.AdditionalData) - { - sectionRoot.AdditionalData[d.Key] = d.Value; - } - return sectionRoot; - + //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); } - throw new ApplicationException("Could not render a tree for type " + configTree.Alias); + var sectionRoot = TreeRootNode.CreateSingleTreeRoot( + rootId, + rootNode.Result.ChildNodesUrl, + rootNode.Result.MenuUrl, + rootNode.Result.Name, + byControllerAttempt.Result, + 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; + + foreach (var d in rootNode.Result.AdditionalData) + { + sectionRoot.AdditionalData[d.Key] = d.Value; + } + return sectionRoot; + } + + /// + /// Proxies a request to the destination tree controller to get it's root tree node + /// + /// + /// + /// + /// + /// + /// This ensures that authorization filters are applied to the sub request + /// + private async Task> TryGetRootNodeFromControllerTree(ApplicationTree appTree, 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); + + //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()) })); + + //create a proxied controller context + var proxiedControllerContext = new HttpControllerContext( + controllerContext.Configuration, + proxiedRouteData, + new HttpRequestMessage(HttpMethod.Get, proxiedUrl)) + { + ControllerDescriptor = new HttpControllerDescriptor(controllerContext.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(instance.GetType()), instance.GetType()), + RequestContext = controllerContext.RequestContext + }; + + instance.ControllerContext = proxiedControllerContext; + instance.Request = controllerContext.Request; + instance.RequestContext.RouteData = proxiedRouteData; + + //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); + } + + //return the root + var node = instance.GetRootNode(formCollection); + return node == null + ? Attempt.Fail(new InvalidOperationException("Could not return a root node for tree " + appTree.TreeAlias)) + : Attempt.Succeed(node); + } + + /// + /// Proxies a request to the destination tree controller to get it's tree node collection + /// + /// + /// + /// + /// + /// + private Attempt TryLoadFromControllerTree(ApplicationTree 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)); + } + } } diff --git a/src/Umbraco.Web/Trees/ITree.cs b/src/Umbraco.Web/Trees/ITree.cs new file mode 100644 index 0000000000..40be7338b0 --- /dev/null +++ b/src/Umbraco.Web/Trees/ITree.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Web.Trees +{ + public interface ITree + { + /// + /// Gets or sets the sort order. + /// + /// The sort order. + int SortOrder { get; } + + /// + /// Gets the section alias. + /// + string ApplicationAlias { get; } + + /// + /// Gets the tree alias. + /// + string TreeAlias { get; } + + /// + /// Gets or sets the tree title (fallback if the tree alias isn't localized) + /// + string TreeTitle { get; } + + /// + /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) + /// + bool IsSingleNodeTree { get; } + } +} diff --git a/src/Umbraco.Web/Trees/TreeAttribute.cs b/src/Umbraco.Web/Trees/TreeAttribute.cs index 1cf23c549f..dd63f8c172 100644 --- a/src/Umbraco.Web/Trees/TreeAttribute.cs +++ b/src/Umbraco.Web/Trees/TreeAttribute.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { @@ -6,15 +7,15 @@ namespace Umbraco.Web.Trees /// Identifies an application tree /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class TreeAttribute : Attribute + public class TreeAttribute : Attribute, ITree { /// /// Initializes a new instance of the class. /// /// The app alias. - /// The alias. + /// public TreeAttribute(string appAlias, - string alias) : this(appAlias, alias, null) + string treeAlias) : this(appAlias, treeAlias, null) { } @@ -22,16 +23,16 @@ namespace Umbraco.Web.Trees /// Initializes a new instance of the class. /// /// The app alias. - /// The alias. - /// The title. + /// + /// /// The icon closed. /// The icon open. /// if set to true [initialize]. /// The sort order. /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) public TreeAttribute(string appAlias, - string alias, - string title, + string treeAlias, + string treeTitle, string iconClosed = "icon-folder", string iconOpen = "icon-folder-open", bool initialize = true, @@ -39,8 +40,8 @@ namespace Umbraco.Web.Trees bool isSingleNodeTree = false) { ApplicationAlias = appAlias; - Alias = alias; - Title = title; + TreeAlias = treeAlias; + TreeTitle = treeTitle; IconClosed = iconClosed; IconOpen = iconOpen; Initialize = initialize; @@ -48,17 +49,17 @@ namespace Umbraco.Web.Trees IsSingleNodeTree = isSingleNodeTree; } - public string ApplicationAlias { get; private set; } - public string Alias { get; private set; } - public string Title { get; private set; } - public string IconClosed { get; private set; } - public string IconOpen { get; private set; } - public bool Initialize { get; private set; } - public int SortOrder { get; private set; } + 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; } /// /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) /// - public bool IsSingleNodeTree { get; private set; } + public bool IsSingleNodeTree { get; } } } diff --git a/src/Umbraco.Web/Trees/TreeCollection.cs b/src/Umbraco.Web/Trees/TreeCollection.cs index 8f913fb2d7..c7b60dea16 100644 --- a/src/Umbraco.Web/Trees/TreeCollection.cs +++ b/src/Umbraco.Web/Trees/TreeCollection.cs @@ -21,7 +21,26 @@ namespace Umbraco.Web.Trees { protected override TreeCollectionBuilder This => this; - //TODO: can we allow for re-ordering OOTB without exposing other methods? + private readonly List _instances = new List(); + + public void AddTree(ApplicationTree tree) + { + _instances.Add(tree); + } + + protected override IEnumerable CreateItems(IFactory factory) + { + return _instances; + + //var items = base.CreateItems(factory).ToList(); + //throw new NotImplementedException(); + ////validate the items, no actions should exist that do not either expose notifications or permissions + //var invalidItems = items.Where(x => !x.CanBePermissionAssigned && !x.ShowInNotifier).ToList(); + //if (invalidItems.Count == 0) return items; + + //var invalidActions = string.Join(", ", invalidItems.Select(x => "'" + x.Alias + "'")); + //throw new InvalidOperationException($"Invalid actions {invalidActions}'. All {typeof(IAction)} implementations must be true for either {nameof(IAction.CanBePermissionAssigned)} or {nameof(IAction.ShowInNotifier)}."); + } } } diff --git a/src/Umbraco.Web/Trees/TreeController.cs b/src/Umbraco.Web/Trees/TreeController.cs index 102671ec29..63e8f5aad5 100644 --- a/src/Umbraco.Web/Trees/TreeController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Concurrent; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; @@ -14,10 +16,10 @@ namespace Umbraco.Web.Trees public abstract class TreeController : TreeControllerBase { private TreeAttribute _attribute; - private string _rootNodeDisplayName; protected TreeController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, CacheHelper applicationCache, IProfilingLogger logger, IRuntimeState runtimeState) : base(globalSettings, umbracoContext, sqlContext, services, applicationCache, logger, runtimeState) { + Initialize(); } protected TreeController() @@ -25,29 +27,42 @@ namespace Umbraco.Web.Trees Initialize(); } - /// - /// The name to display on the root node - /// - public override string RootNodeDisplayName - { - get - { - throw new NotImplementedException(); - //return _rootNodeDisplayName - // ?? (_rootNodeDisplayName = Services.ApplicationTreeService.GetByAlias(_attribute.Alias) - // ?.GetRootNodeDisplayName(Services.TextService)); - } - } + /// + public override string RootNodeDisplayName => ApplicationTree.GetRootNodeDisplayName(this, Services.TextService); - /// - /// Gets the current tree alias from the attribute assigned to it. - /// - public override string TreeAlias => _attribute.Alias; + /// + public override string TreeAlias => _attribute.TreeAlias; + /// + public override string TreeTitle => _attribute.TreeTitle; + /// + public override string ApplicationAlias => _attribute.ApplicationAlias; + /// + public override int SortOrder => _attribute.SortOrder; + /// + public override bool IsSingleNodeTree => _attribute.IsSingleNodeTree; private void Initialize() { - throw new NotImplementedException(); - //_attribute = GetType().GetTreeAttribute(); + _attribute = GetTreeAttribute(); + } + + private static readonly ConcurrentDictionary TreeAttributeCache = new ConcurrentDictionary(); + + private TreeAttribute GetTreeAttribute() + { + return TreeAttributeCache.GetOrAdd(GetType(), type => + { + //Locate the tree attribute + var treeAttributes = type + .GetCustomAttributes(false) + .ToArray(); + + if (treeAttributes.Length == 0) + 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]; + }); } } } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index c00d6053c8..7a027033fc 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.Trees /// Developers should generally inherit from TreeController. /// [AngularJsonOnlyConfiguration] - public abstract class TreeControllerBase : UmbracoAuthorizedApiController + public abstract class TreeControllerBase : UmbracoAuthorizedApiController, ITree { protected TreeControllerBase() { @@ -62,10 +62,16 @@ namespace Umbraco.Web.Trees /// public abstract string RootNodeDisplayName { get; } - /// - /// Gets the current tree alias from the attribute assigned to it. - /// + /// public abstract string TreeAlias { get; } + /// + public abstract string TreeTitle { get; } + /// + public abstract string ApplicationAlias { get; } + /// + public abstract int SortOrder { get; } + /// + public abstract bool IsSingleNodeTree { get; } /// /// Returns the root node for the tree diff --git a/src/Umbraco.Web/Trees/TreeControllerResolver.cs b/src/Umbraco.Web/Trees/TreeControllerResolver.cs deleted file mode 100644 index 62cafbb5dd..0000000000 --- a/src/Umbraco.Web/Trees/TreeControllerResolver.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Management.Instrumentation; -using System.Net.Http; -using System.Net.Http.Formatting; -using System.Threading.Tasks; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Routing; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Core.Composing; -using ApplicationTree = Umbraco.Web.Models.ContentEditing.ApplicationTree; - -namespace Umbraco.Web.Trees -{ - public class TreeControllerResolver - { - private readonly TreeCollection _trees; - private readonly UmbracoApiControllerTypeCollection _apiControllers; - - public TreeControllerResolver(TreeCollection trees, UmbracoApiControllerTypeCollection apiControllers) - { - _trees = trees; - _apiControllers = apiControllers; - } - - private static readonly ConcurrentDictionary TreeAttributeCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary ResolvedControllerTypes = new ConcurrentDictionary(); - - private TreeAttribute GetTreeAttribute(Type treeControllerType) - { - return TreeAttributeCache.GetOrAdd(treeControllerType, type => - { - //Locate the tree attribute - var treeAttributes = type - .GetCustomAttributes(false) - .ToArray(); - - if (treeAttributes.Length == 0) - { - 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]; - }); - } - - internal TreeAttribute GetTreeAttribute(ApplicationTree tree) - { - throw new NotImplementedException(); - //return ResolvedControllerTypes.GetOrAdd(tree.Alias, s => - //{ - // var controllerType = _apiControllers - // .OfType() - // .FirstOrDefault(x => x.) - //}); - - //return GetTreeAttribute(tree.GetRuntimeType()); - } - - private Type GetControllerType(ApplicationTree tree) - { - throw new NotImplementedException(); - } - - - internal Attempt TryGetControllerTree(ApplicationTree appTree) - { - throw new NotImplementedException(); - - ////get reference to all TreeApiControllers - //var controllerTrees = _apiControllers - // .Where(TypeHelper.IsTypeAssignableFrom) - // .ToArray(); - - ////find the one we're looking for - //var foundControllerTree = controllerTrees.FirstOrDefault(x => x == appTree.GetRuntimeType()); - //if (foundControllerTree == null) - //{ - // return Attempt.Fail(new InstanceNotFoundException("Could not find tree of type " + appTree.Type + " in any loaded DLLs")); - //} - //return Attempt.Succeed(foundControllerTree); - } - - /// - /// This will go and get the root node from a controller tree by executing the tree's GetRootNode method - /// - /// - /// - /// - /// - /// - /// This ensures that authorization filters are applied to the sub request - /// - internal async Task> TryGetRootNodeFromControllerTree(ApplicationTree appTree, FormDataCollection formCollection, HttpControllerContext controllerContext) - { - var foundControllerTreeAttempt = TryGetControllerTree(appTree); - if (foundControllerTreeAttempt.Success == false) - { - return Attempt.Fail(foundControllerTreeAttempt.Exception); - } - - var foundControllerTree = foundControllerTreeAttempt.Result; - //instantiate it, since we are proxying, we need to setup the instance with our current context - var instance = (TreeController)DependencyResolver.Current.GetService(foundControllerTree); - - //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())})); - - //create a proxied controller context - var proxiedControllerContext = new HttpControllerContext( - controllerContext.Configuration, - proxiedRouteData, - new HttpRequestMessage(HttpMethod.Get, proxiedUrl)) - { - ControllerDescriptor = new HttpControllerDescriptor(controllerContext.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(instance.GetType()), instance.GetType()) - }; - - if (WebApiVersionCheck.WebApiVersion >= Version.Parse("5.0.0")) - { - //fixme - will this 'just' work now? - proxiedControllerContext.RequestContext = controllerContext.RequestContext; - - ////In WebApi2, this is required to be set: - //// proxiedControllerContext.RequestContext = controllerContext.RequestContext - //// but we need to do this with reflection because of codebase changes between version 4/5 - ////NOTE: Use TypeHelper here since the reflection is cached - //var controllerContextRequestContext = TypeHelper.GetProperty(controllerContext.GetType(), "RequestContext").GetValue(controllerContext); - //TypeHelper.GetProperty(proxiedControllerContext.GetType(), "RequestContext").SetValue(proxiedControllerContext, controllerContextRequestContext); - } - - instance.ControllerContext = proxiedControllerContext; - instance.Request = controllerContext.Request; - - if (WebApiVersionCheck.WebApiVersion >= Version.Parse("5.0.0")) - { - - //fixme - will this 'just' work now? - instance.RequestContext.RouteData = proxiedRouteData; - - ////now we can change the request context's route data to be the proxied route data - NOTE: we cannot do this directly above - //// because it will detect that the request context is different throw an exception. This is a change in webapi2 and we need to set - //// this with reflection due to codebase changes between version 4/5 - //// instance.RequestContext.RouteData = proxiedRouteData; - ////NOTE: Use TypeHelper here since the reflection is cached - //var instanceRequestContext = TypeHelper.GetProperty(typeof(ApiController), "RequestContext").GetValue(instance); - //TypeHelper.GetProperty(instanceRequestContext.GetType(), "RouteData").SetValue(instanceRequestContext, proxiedRouteData); - } - - //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); - } - - //return the root - var node = instance.GetRootNode(formCollection); - return node == null - ? Attempt.Fail(new InvalidOperationException("Could not return a root node for tree " + appTree.Alias)) - : Attempt.Succeed(node); - } - - internal Attempt TryLoadFromControllerTree(ApplicationTree appTree, string id, FormDataCollection formCollection, HttpControllerContext controllerContext) - { - var foundControllerTreeAttempt = TryGetControllerTree(appTree); - if (foundControllerTreeAttempt.Success == false) - return Attempt.Fail(foundControllerTreeAttempt.Exception); - - // instantiate it, since we are proxying, we need to setup the instance with our current context - var foundControllerTree = foundControllerTreeAttempt.Result; - var instance = (TreeController) DependencyResolver.Current.GetService(foundControllerTree); - if (instance == null) - throw new Exception("Failed to get tree " + foundControllerTree.FullName + "."); - - instance.ControllerContext = controllerContext; - instance.Request = controllerContext.Request; - - // return its data - return Attempt.Succeed(instance.GetNodes(id, formCollection)); - } - - } - -} diff --git a/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs b/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs deleted file mode 100644 index 5473fee7bb..0000000000 --- a/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; - -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.UserPermissions)] - [Tree(Constants.Applications.Users, Constants.Trees.UserPermissions, null, sortOrder: 2)] - [PluginController("UmbracoTrees")] - [CoreTree] - public class UserPermissionsTreeController : TreeController - { - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); - long totalUsers; - nodes.AddRange( - Services.UserService.GetAll(0, int.MaxValue, out totalUsers) - .Where(x => x.Id != Constants.Security.SuperUserId && x.IsApproved) - .Select(x => CreateTreeNode(x.Id.ToString(), - id, - queryStrings, - x.Name, - "icon-user", - false, - "/" + queryStrings.GetValue("application") + "/framed/" - + Uri.EscapeDataString("users/PermissionEditor.aspx?id=" + x.Id)))); - - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - // root actions - menu.Items.Add(new RefreshNode(Services.TextService, true)); - return menu; - } - - return menu; - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f1d15e9ea6..0b1603c9e3 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -166,8 +166,9 @@ - + + @@ -183,7 +184,7 @@ - + @@ -551,7 +552,6 @@ - @@ -649,7 +649,7 @@ - + @@ -972,7 +972,6 @@ - @@ -1056,7 +1055,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs index cc483db416..5ad3da7f4d 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -41,18 +41,16 @@ namespace Umbraco.Web.WebApi.Filters return true; } - throw new NotImplementedException(); + var apps = _treeAliases.Select(x => Current.TreeService + .GetByAlias(x)) + .WhereNotNull() + .Select(x => x.ApplicationAlias) + .Distinct() + .ToArray(); - //var apps = _treeAliases.Select(x => Current.Services.ApplicationTreeService - // .GetByAlias(x)) - // .WhereNotNull() - // .Select(x => x.ApplicationAlias) - // .Distinct() - // .ToArray(); - - //return Current.UmbracoContext.Security.CurrentUser != null - // && apps.Any(app => Current.UmbracoContext.Security.UserHasSectionAccess( - // app, Current.UmbracoContext.Security.CurrentUser)); + return Current.UmbracoContext.Security.CurrentUser != null + && apps.Any(app => Current.UmbracoContext.Security.UserHasSectionAccess( + app, Current.UmbracoContext.Security.CurrentUser)); } } } diff --git a/src/Umbraco.Web/WebApi/MvcVersionCheck.cs b/src/Umbraco.Web/WebApi/MvcVersionCheck.cs deleted file mode 100644 index 3c84fa91ce..0000000000 --- a/src/Umbraco.Web/WebApi/MvcVersionCheck.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Web.WebApi -{ - internal class WebApiVersionCheck - { - public static System.Version WebApiVersion - { - get { return typeof(System.Web.Http.ApiController).Assembly.GetName().Version; } - } - } -} diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs index d84ab3fe4a..cdb1aa2f51 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs @@ -8,8 +8,6 @@ namespace Umbraco.Web.WebApi // which we are not doing at the moment // we can inherit from BuilderCollectionBase and just be enumerable - //fixme: this should be LazyCollectionBuilderBase ? - public class UmbracoApiControllerTypeCollection : BuilderCollectionBase { public UmbracoApiControllerTypeCollection(IEnumerable items)