diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs
index dec9d2fb16..0efa678d4e 100644
--- a/src/Umbraco.Core/Constants-Applications.cs
+++ b/src/Umbraco.Core/Constants-Applications.cs
@@ -42,5 +42,26 @@
///
public const string Users = "users";
}
+
+ ///
+ /// Defines the alias identifiers for Umbraco's core trees.
+ ///
+ public static class Trees
+ {
+ ///
+ /// alias for the content tree.
+ ///
+ public const string Content = "content";
+
+ ///
+ /// alias for the media tree.
+ ///
+ public const string Media = "media";
+
+ //TODO: Fill in the rest!
+
+ }
}
+
+
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs
index 7b941c1f00..031d4f587d 100644
--- a/src/Umbraco.Core/PluginManager.cs
+++ b/src/Umbraco.Core/PluginManager.cs
@@ -424,6 +424,7 @@ namespace Umbraco.Core
private readonly HashSet _types = new HashSet();
private IEnumerable _assemblies;
+
///
/// Returns all found property editors
///
diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs
index dd4ced35d9..f2c976d144 100644
--- a/src/Umbraco.Core/StringExtensions.cs
+++ b/src/Umbraco.Core/StringExtensions.cs
@@ -436,6 +436,16 @@ namespace Umbraco.Core
return String.Format(CultureInfo.InvariantCulture, format, args);
}
+ ///
+ /// Converts an integer to an invariant formatted string
+ ///
+ ///
+ ///
+ public static string ToInvariantString(this int str)
+ {
+ return str.ToString(CultureInfo.InvariantCulture);
+ }
+
///
/// Compares 2 strings with invariant culture and case ignored
///
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js
index e7ecf60a97..fde4494d1e 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js
@@ -18,7 +18,10 @@ function treeService($q, treeResource, iconHelper) {
var childLevel = (level ? level : 1);
for (var i = 0; i < treeNodes.length; i++) {
treeNodes[i].level = childLevel;
- treeNodes[i].view = section + "/edit/" + treeNodes[i].id;
+ //if there is not route path specified, then set it automatically
+ if (!treeNodes[i].routePath) {
+ treeNodes[i].routePath = section + "/edit/" + treeNodes[i].id;
+ }
treeNodes[i].parent = parentNode;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js
index f4e6b62b25..6a0516e495 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js
@@ -46,15 +46,15 @@ function NavigationController($scope,$rootScope, $location, $log, navigationServ
var n = args.node;
//here we need to check for some legacy tree code
- if (n.jsClickCallback && n.jsClickCallback !== "") {
+ if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") {
//this is a legacy tree node!
var jsPrefix = "javascript:";
var js;
- if (n.jsClickCallback.startsWith(jsPrefix)) {
- js = n.jsClickCallback.substr(jsPrefix.length);
+ if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) {
+ js = n.metaData["jsClickCallback"].substr(jsPrefix.length);
}
else {
- js = n.jsClickCallback;
+ js = n.metaData["jsClickCallback"];
}
try {
var func = eval(js);
@@ -69,9 +69,9 @@ function NavigationController($scope,$rootScope, $location, $log, navigationServ
}
else {
//add action to the history service
- historyService.add({name: n.name, link: n.view, icon: n.icon});
+ historyService.add({ name: n.name, link: n.routePath, icon: n.icon });
//not legacy, lets just set the route value and clear the query string if there is one.
- $location.path(n.view).search(null);
+ $location.path(n.routePath).search(null);
}
});
diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config
index 6834440077..d8d4957c6d 100644
--- a/src/Umbraco.Web.UI/config/trees.config
+++ b/src/Umbraco.Web.UI/config/trees.config
@@ -1,8 +1,8 @@
-
-
+
@@ -39,4 +39,6 @@
+
+
\ No newline at end of file
diff --git a/src/Umbraco.Web/PluginManagerExtensions.cs b/src/Umbraco.Web/PluginManagerExtensions.cs
index ff331fe8d9..6196e00fd3 100644
--- a/src/Umbraco.Web/PluginManagerExtensions.cs
+++ b/src/Umbraco.Web/PluginManagerExtensions.cs
@@ -5,6 +5,7 @@ using Umbraco.Core;
using Umbraco.Core.Media;
using Umbraco.Web.Mvc;
using Umbraco.Web.Routing;
+using Umbraco.Web.Trees;
using Umbraco.Web.WebApi;
using umbraco;
using umbraco.interfaces;
@@ -16,6 +17,18 @@ namespace Umbraco.Web
///
public static class PluginManagerExtensions
{
+
+ ///
+ /// Returns all available TreeApiController's in application that are attribute with TreeAttribute
+ ///
+ ///
+ ///
+ internal static IEnumerable ResolveAttributedTreeControllers(this PluginManager resolver)
+ {
+ //don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason
+ return resolver.ResolveTypesWithAttribute(cacheResult: false);
+ }
+
internal static IEnumerable ResolveSurfaceControllers(this PluginManager resolver)
{
return resolver.ResolveTypes();
diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs
index 0b5cd3eb5a..41509062e3 100644
--- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs
+++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs
@@ -10,6 +10,7 @@ using Umbraco.Core.Services;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
+using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Trees
{
@@ -39,28 +40,24 @@ namespace Umbraco.Web.Trees
{
if (application == null) throw new ArgumentNullException("application");
- var rootId = Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture);
+ var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
//find all tree definitions that have the current application alias
var appTrees = ApplicationContext.Current.Services.ApplicationTreeService.GetApplicationTrees(application).Where(x => x.Initialize).ToArray();
if (appTrees.Count() == 1)
- {
- return new SectionRootNode(
- rootId,
- Url.GetUmbracoApiService("GetMenu", rootId)
- + "&parentId=" + rootId
- + "&treeType=" + application
- + "§ion=" + application)
- {
- Children = GetNodeCollection(appTrees.Single(), "-1", queryStrings)
- };
+ {
+ return GetRootForSingleAppTree(
+ appTrees.Single(),
+ Constants.System.Root.ToString(CultureInfo.InvariantCulture),
+ queryStrings,
+ application);
}
var collection = new TreeNodeCollection();
foreach (var tree in appTrees)
{
//return the root nodes for each tree in the app
- var rootNode = GetRoot(tree, queryStrings);
+ var rootNode = GetRootForMultipleAppTree(tree, queryStrings);
collection.Add(rootNode);
}
@@ -71,27 +68,13 @@ namespace Umbraco.Web.Trees
};
}
- /////
- ///// Returns the tree data for a specific tree for the children of the id
- /////
- /////
- /////
- /////
- /////
- //[HttpQueryStringFilter("queryStrings")]
- //public TreeNodeCollection GetTreeData(string treeType, string id, FormDataCollection queryStrings)
- //{
- // if (treeType == null) throw new ArgumentNullException("treeType");
-
- // //get the configured tree
- // var foundConfigTree = ApplicationTreeCollection.GetByAlias(treeType);
- // if (foundConfigTree == null)
- // throw new InstanceNotFoundException("Could not find tree of type " + treeType + " in the trees.config");
-
- // return GetNodeCollection(foundConfigTree, id, queryStrings);
- //}
-
- private TreeNode GetRoot(ApplicationTree configTree, FormDataCollection queryStrings)
+ ///
+ /// Get the root node for an application with multiple trees
+ ///
+ ///
+ ///
+ ///
+ private TreeNode GetRootForMultipleAppTree(ApplicationTree configTree, FormDataCollection queryStrings)
{
if (configTree == null) throw new ArgumentNullException("configTree");
var byControllerAttempt = configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext, Request);
@@ -110,24 +93,46 @@ namespace Umbraco.Web.Trees
}
///
- /// Get the node collection for the tree, try loading from new controllers first, then from legacy trees
+ /// Get the root node for an application with one tree
///
///
///
///
///
- private TreeNodeCollection GetNodeCollection(ApplicationTree configTree, string id, FormDataCollection queryStrings)
+ private SectionRootNode GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application)
{
+ var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
if (configTree == null) throw new ArgumentNullException("configTree");
var byControllerAttempt = configTree.TryLoadFromControllerTree(id, queryStrings, ControllerContext, Request);
if (byControllerAttempt.Success)
{
- return byControllerAttempt.Result;
+ var rootNode = configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext, Request);
+ 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);
+ }
+
+ return new SectionRootNode(
+ rootId,
+ rootNode.Result.MenuUrl)
+ {
+ Children = byControllerAttempt.Result
+ };
+
}
var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias);
if (legacyAttempt.Success)
{
- return legacyAttempt.Result;
+ return new SectionRootNode(
+ rootId,
+ Url.GetUmbracoApiService("GetMenu", rootId)
+ + "&parentId=" + rootId
+ + "&treeType=" + application
+ + "§ion=" + application)
+ {
+ Children = legacyAttempt.Result
+ };
}
throw new ApplicationException("Could not render a tree for type " + configTree.Alias);
diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs
index 62812ec8e0..b8475ac059 100644
--- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs
+++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs
@@ -50,10 +50,10 @@ namespace Umbraco.Web.Trees
instance.ControllerContext = controllerContext;
instance.Request = request;
//return the root
- var nodes = instance.GetNodes(Constants.System.Root.ToString(CultureInfo.InvariantCulture), formCollection);
- return nodes.Any() == false
+ var node = instance.GetRootNode(formCollection);
+ return node == null
? new Attempt(new InvalidOperationException("Could not return a root node for tree " + appTree.Type))
- : new Attempt(true, nodes.First());
+ : new Attempt(true, node);
}
internal static Attempt TryLoadFromControllerTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, HttpControllerContext controllerContext, HttpRequestMessage request)
diff --git a/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs b/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs
new file mode 100644
index 0000000000..d7c0db9a13
--- /dev/null
+++ b/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Umbraco.Core;
+using umbraco.businesslogic;
+
+namespace Umbraco.Web.Trees
+{
+ ///
+ /// A startup handler for putting the tree config in the config file based on attributes found
+ ///
+ public sealed class ApplicationTreeRegistrar : ApplicationEventHandler
+ {
+ protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
+ {
+ ScanTrees(applicationContext);
+ }
+
+ ///
+ /// Scans for all attributed trees and ensures they exist in the tree xml
+ ///
+ private static void ScanTrees(ApplicationContext applicationContext)
+ {
+ applicationContext.Services.ApplicationTreeService.LoadXml(doc =>
+ {
+ var added = new List();
+
+ // Load all Controller Trees by attribute and add them to the XML config
+ // we also need to make sure that any alias added with the new trees is not also added
+ // with the legacy trees.
+ var types = PluginManager.Current.ResolveAttributedTreeControllers();
+
+ var items = types
+ .Select(x =>
+ new Tuple(x, x.GetCustomAttributes(false).Single()))
+ .Where(x => applicationContext.Services.ApplicationTreeService.GetByAlias(x.Item2.Alias) == null);
+
+ foreach (var tuple in items)
+ {
+ var type = tuple.Item1;
+ var attr = tuple.Item2;
+
+ //Add the new tree that doesn't exist in the config that was found by type finding
+
+ doc.Root.Add(new XElement("add",
+ new XAttribute("initialize", attr.Initialize),
+ new XAttribute("sortOrder", attr.SortOrder),
+ new XAttribute("alias", attr.Alias),
+ new XAttribute("application", attr.ApplicationAlias),
+ new XAttribute("title", attr.Title),
+ new XAttribute("iconClosed", attr.IconClosed),
+ new XAttribute("iconOpen", attr.IconOpen),
+ new XAttribute("type", type.GetFullNameWithAssembly())));
+
+ added.Add(attr.Alias);
+ }
+
+
+ // Load all LEGACY Trees by attribute and add them to the XML config
+ var legacyTreeTypes = PluginManager.Current.ResolveAttributedTrees();
+
+ var legacyItems = legacyTreeTypes
+ .Select(x =>
+ new Tuple(x, x.GetCustomAttributes(false).Single()))
+ .Where(x => applicationContext.Services.ApplicationTreeService.GetByAlias(x.Item2.Alias) == null
+ //make sure the legacy tree isn't added on top of the controller tree!
+ && !added.InvariantContains(x.Item2.Alias));
+
+ foreach (var tuple in legacyItems)
+ {
+ var type = tuple.Item1;
+ var attr = tuple.Item2;
+
+ //Add the new tree that doesn't exist in the config that was found by type finding
+ doc.Root.Add(new XElement("add",
+ new XAttribute("initialize", attr.Initialize),
+ new XAttribute("sortOrder", attr.SortOrder),
+ new XAttribute("alias", attr.Alias),
+ new XAttribute("application", attr.ApplicationAlias),
+ new XAttribute("title", attr.Title),
+ new XAttribute("iconClosed", attr.IconClosed),
+ new XAttribute("iconOpen", attr.IconOpen),
+ new XAttribute("type", type.GetFullNameWithAssembly())));
+ }
+
+ }, true);
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs
new file mode 100644
index 0000000000..a1fbdabaeb
--- /dev/null
+++ b/src/Umbraco.Web/Trees/ContentTreeController.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http.Formatting;
+using System.Web.Http;
+using Umbraco.Core;
+using Umbraco.Core.Models;
+using umbraco.BusinessLogic.Actions;
+using umbraco.businesslogic;
+using umbraco.interfaces;
+
+namespace Umbraco.Web.Trees
+{
+ public abstract class ContentTreeControllerBase : TreeApiController
+ {
+ ///
+ /// Based on the allowed actions, this will filter the ones that the current user is allowed
+ ///
+ ///
+ ///
+ ///
+ protected MenuItemCollection GetUserAllowedMenuItems(IEnumerable