diff --git a/src/Umbraco.Core/Models/IApplication.cs b/src/Umbraco.Core/Models/IApplication.cs deleted file mode 100644 index cac8e5dd48..0000000000 --- a/src/Umbraco.Core/Models/IApplication.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Models -{ - /// - /// Interface for created applications in the umbraco backoffice - /// - public interface IApplication - { } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IApplicationTreeService.cs b/src/Umbraco.Core/Services/IApplicationTreeService.cs index 03d1793c18..9721cd9a56 100644 --- a/src/Umbraco.Core/Services/IApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/IApplicationTreeService.cs @@ -5,18 +5,6 @@ namespace Umbraco.Core.Services { public interface IApplicationTreeService { - /// - /// Initializes the service with any trees found in plugins - /// - /// - /// A collection of all available tree found in assemblies in the application - /// - /// - /// This will update the trees.config with the found tree plugins that are not currently listed in the file when the first - /// access is made to resolve the tree collection - /// - void Intitialize(IEnumerable allAvailableTrees); - /// /// Creates a new application tree. /// @@ -68,4 +56,82 @@ namespace Umbraco.Core.Services /// Returns a ApplicationTree Array IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); } + + /// + /// 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(); + } + + /// + /// 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(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ISectionService.cs b/src/Umbraco.Core/Services/ISectionService.cs index c6a56e5f4a..52c86093c7 100644 --- a/src/Umbraco.Core/Services/ISectionService.cs +++ b/src/Umbraco.Core/Services/ISectionService.cs @@ -46,4 +46,69 @@ namespace Umbraco.Core.Services /// 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 Section 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(Section section) + { + throw new System.NotImplementedException(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 600a8d120d..c28c4e19e5 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -279,10 +279,10 @@ namespace Umbraco.Core.Services _relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, _entityService.Value)); if (_treeService == null) - _treeService = new Lazy(() => new ApplicationTreeService(logger, cache)); + _treeService = new Lazy(() => new EmptyApplicationTreeService()); if (_sectionService == null) - _sectionService = new Lazy(() => new SectionService(_userService.Value, _treeService.Value, provider, cache)); + _sectionService = new Lazy(() => new EmptySectionService()); if (_macroService == null) _macroService = new Lazy(() => new MacroService(provider, repositoryFactory, logger, eventMessagesFactory)); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 379ea863b2..b10b9ae2e8 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -342,13 +342,11 @@ + - - - @@ -1095,7 +1093,6 @@ - @@ -1262,7 +1259,6 @@ - @@ -1313,7 +1309,6 @@ - diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index 10d8e43771..414221bd5b 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -20,6 +20,8 @@ using Umbraco.Core.IO; using umbraco.DataLayer; using umbraco.interfaces; using umbraco.uicontrols; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Trees; namespace Umbraco.Tests.Plugins { @@ -80,7 +82,7 @@ namespace Umbraco.Tests.Plugins [Test] public void Find_Classes_With_Attribute() { - var typesFound = TypeFinder.FindClassesWithAttribute(_assemblies); + var typesFound = TypeFinder.FindClassesWithAttribute(_assemblies); //TODO: Fix this with the correct count Assert.AreEqual(1, typesFound.Count()); } diff --git a/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs b/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs index 42144864c2..f31cd1a7a1 100644 --- a/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs +++ b/src/Umbraco.Tests/TreesAndSections/ApplicationTreeTest.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using System; using System.Linq; +using Umbraco.Web.Services; namespace Umbraco.Tests.TreesAndSections { diff --git a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs index 67ef50af38..c18b9ca7c0 100644 --- a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs +++ b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs @@ -6,6 +6,7 @@ using umbraco.BusinessLogic; using System; using System.Linq; using Umbraco.Core; +using Umbraco.Web.Services; namespace Umbraco.Tests.TreesAndSections { diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 4249276302..f5465dcca6 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -12,6 +12,7 @@ using System.Linq; using umbraco.cms.businesslogic.web; using Umbraco.Core.Logging; using Umbraco.Core.Publishing; +using Umbraco.Web.Services; using Content = Umbraco.Core.Models.Content; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs; diff --git a/src/Umbraco.Core/Models/ApplicationAttribute.cs b/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs similarity index 96% rename from src/Umbraco.Core/Models/ApplicationAttribute.cs rename to src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs index 8ef78f411e..9eba9b31fd 100644 --- a/src/Umbraco.Core/Models/ApplicationAttribute.cs +++ b/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Core.Models +namespace Umbraco.Web.Models.Trees { /// /// Identifies an application tree diff --git a/src/Umbraco.Core/Models/ApplicationDefinitions.cs b/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs similarity index 94% rename from src/Umbraco.Core/Models/ApplicationDefinitions.cs rename to src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs index 9c0a69df66..002b96bc64 100644 --- a/src/Umbraco.Core/Models/ApplicationDefinitions.cs +++ b/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core.Models +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.Trees { // The application definitions are intended as a means to auto populate // the application.config. On app startup, Umbraco will look for any diff --git a/src/Umbraco.Web/PluginManagerExtensions.cs b/src/Umbraco.Web/PluginManagerExtensions.cs index 1f269ac638..0f4150c59d 100644 --- a/src/Umbraco.Web/PluginManagerExtensions.cs +++ b/src/Umbraco.Web/PluginManagerExtensions.cs @@ -9,6 +9,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using umbraco; using umbraco.interfaces; +using Umbraco.Web.Models.Trees; namespace Umbraco.Web { @@ -28,7 +29,7 @@ namespace Umbraco.Web return resolver.ResolveTypesWithAttribute(); } - internal static IEnumerable ResolveSurfaceControllers(this PluginManager resolver) + internal static IEnumerable ResolveSurfaceControllers(this PluginManager resolver) { return resolver.ResolveTypes(); } diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index c881ba448a..c187b754c3 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -21,12 +21,6 @@ using System.Security; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("ce9d3539-299e-40d3-b605-42ac423e24fa")] -//This is required so that Medium trust works and this is because of this class: -// umbraco.presentation.templateControls.ItemDesigner since this class cannot inherit from -// the System.Web.UI.Design.ControlDesigner in partial trust (or something along those lines) -// if we remove this class then we won't need to do this. -[assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1)] - [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("umbraco.MacroEngines")] [assembly: InternalsVisibleTo("Umbraco.Web.UI")] diff --git a/src/Umbraco.Core/Services/ApplicationTreeService.cs b/src/Umbraco.Web/Services/ApplicationTreeService.cs similarity index 72% rename from src/Umbraco.Core/Services/ApplicationTreeService.cs rename to src/Umbraco.Web/Services/ApplicationTreeService.cs index 763176b141..4d3c767a79 100644 --- a/src/Umbraco.Core/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Web/Services/ApplicationTreeService.cs @@ -1,400 +1,423 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Cache; -using File = System.IO.File; - -namespace Umbraco.Core.Services -{ - internal class ApplicationTreeService : IApplicationTreeService - { - private readonly ILogger _logger; - private readonly CacheHelper _cache; - private IEnumerable _allAvailableTrees; - private volatile bool _isInitialized = false; - internal const string TreeConfigFileName = "trees.config"; - private static string _treeConfig; - private static readonly object Locker = new object(); - - public ApplicationTreeService(ILogger logger, CacheHelper cache) - { - _logger = logger; - _cache = cache; - } - - - /// - /// gets/sets the trees.config file path - /// - /// - /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath - /// - internal static string TreeConfigFilePath - { - get - { - if (string.IsNullOrWhiteSpace(_treeConfig)) - { - _treeConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + TreeConfigFileName); - } - return _treeConfig; - } - set { _treeConfig = value; } - } - - /// - /// The main entry point to get application trees - /// - /// - /// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins - /// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned. - /// - private List GetAppTrees() - { - return _cache.RuntimeCache.GetCacheItem>( - CacheKeys.ApplicationTreeCacheKey, - () => - { - var list = ReadFromXmlAndSort(); - - //On first access we need to do some initialization - if (_isInitialized == false) - { - lock (Locker) - { - if (_isInitialized == false) - { - //now we can check the non-volatile flag - if (_allAvailableTrees != null) - { - var hasChanges = false; - - LoadXml(doc => - { - //Now, load in the xml structure and update it with anything that is not declared there and save the file. - - //NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy - // based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this - // currently because of the legacy assemblies and types not in the Core. - - //Get all the trees not registered in the config - var unregistered = _allAvailableTrees - .Where(x => list.Any(l => l.Alias == x.Alias) == false) - .ToArray(); - - hasChanges = unregistered.Any(); - - if (hasChanges == false) return false; - - //add the unregistered ones to the list and re-save the file if any changes were found - var count = 0; - foreach (var tree in unregistered) - { - doc.Root.Add(new XElement("add", - new XAttribute("initialize", tree.Initialize), - new XAttribute("sortOrder", tree.SortOrder), - new XAttribute("alias", tree.Alias), - new XAttribute("application", tree.ApplicationAlias), - new XAttribute("title", tree.Title), - new XAttribute("iconClosed", tree.IconClosed), - new XAttribute("iconOpen", tree.IconOpened), - new XAttribute("type", tree.Type))); - count++; - } - - //don't save if there's no changes - return count > 0; - }, true); - - if (hasChanges) - { - //If there were changes, we need to re-read the structures from the XML - list = ReadFromXmlAndSort(); - } - } - - _isInitialized = true; - } - } - } - - - return list; - - - }, new TimeSpan(0, 10, 0)); - } - - /// - /// Initializes the service with any trees found in plugins - /// - /// - /// A collection of all available tree found in assemblies in the application - /// - /// - /// This will update the trees.config with the found tree plugins that are not currently listed in the file when the first - /// access is made to resolve the tree collection - /// - public void Intitialize(IEnumerable allAvailableTrees) - { - _allAvailableTrees = allAvailableTrees; - } - - /// - /// 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) - { - LoadXml(doc => - { - var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == alias && x.Attribute("application").Value == applicationAlias); - - if (el == null) - { - doc.Root.Add(new XElement("add", - new XAttribute("initialize", initialize), - new XAttribute("sortOrder", sortOrder), - new XAttribute("alias", alias), - new XAttribute("application", applicationAlias), - new XAttribute("title", title), - new XAttribute("iconClosed", iconClosed), - new XAttribute("iconOpen", iconOpened), - new XAttribute("type", type))); - } - - return true; - - }, true); - - OnNew(new ApplicationTree(initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, type), new EventArgs()); - } - - /// - /// Saves this instance. - /// - public void SaveTree(ApplicationTree tree) - { - LoadXml(doc => - { - var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == tree.Alias && x.Attribute("application").Value == tree.ApplicationAlias); - - if (el != null) - { - el.RemoveAttributes(); - - el.Add(new XAttribute("initialize", tree.Initialize)); - el.Add(new XAttribute("sortOrder", tree.SortOrder)); - el.Add(new XAttribute("alias", tree.Alias)); - el.Add(new XAttribute("application", tree.ApplicationAlias)); - el.Add(new XAttribute("title", tree.Title)); - el.Add(new XAttribute("iconClosed", tree.IconClosed)); - el.Add(new XAttribute("iconOpen", tree.IconOpened)); - el.Add(new XAttribute("type", tree.Type)); - } - - return true; - - }, true); - - OnUpdated(tree, new EventArgs()); - } - - /// - /// Deletes this instance. - /// - public void DeleteTree(ApplicationTree tree) - { - LoadXml(doc => - { - doc.Root.Elements("add") - .Where(x => x.Attribute("application") != null - && x.Attribute("application").Value == tree.ApplicationAlias - && x.Attribute("alias") != null && x.Attribute("alias").Value == tree.Alias).Remove(); - - return true; - - }, true); - - OnDeleted(tree, new EventArgs()); - } - - /// - /// Gets an ApplicationTree by it's tree alias. - /// - /// The tree alias. - /// An ApplicationTree instance - public ApplicationTree GetByAlias(string treeAlias) - { - return GetAppTrees().Find(t => (t.Alias == treeAlias)); - - } - - /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. - /// - /// Returns a ApplicationTree Array - public IEnumerable GetAll() - { - return GetAppTrees().OrderBy(x => x.SortOrder); - } - - /// - /// Gets the application tree for the applcation with the specified alias - /// - /// The application alias. - /// Returns a ApplicationTree Array - public IEnumerable GetApplicationTrees(string applicationAlias) - { - return GetApplicationTrees(applicationAlias, false); - } - - /// - /// 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) - { - var list = GetAppTrees().FindAll( - t => - { - if (onlyInitialized) - return (t.ApplicationAlias == applicationAlias && t.Initialize); - return (t.ApplicationAlias == applicationAlias); - } - ); - - return list.OrderBy(x => x.SortOrder).ToArray(); - } - - /// - /// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the - /// callback with the xml document and saves the structure back to disk if saveAfterCallback is true. - /// - /// - /// - internal void LoadXml(Func callback, bool saveAfterCallbackIfChanges) - { - lock (Locker) - { - var doc = File.Exists(TreeConfigFilePath) - ? XDocument.Load(TreeConfigFilePath) - : XDocument.Parse(""); - - if (doc.Root != null) - { - var hasChanges = callback.Invoke(doc); - - if (saveAfterCallbackIfChanges && hasChanges - //Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process - // in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and - // more about the IO trying to save the XML doc, but it doesn't hurt to check. - && doc.Root != null && doc.Root.Elements().Any()) - { - //ensures the folder exists - Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath)); - - //saves it - doc.Save(TreeConfigFilePath); - - //remove the cache now that it has changed SD: I'm leaving this here even though it - // is taken care of by events as well, I think unit tests may rely on it being cleared here. - _cache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); - } - } - } - } - - private List ReadFromXmlAndSort() - { - var list = new List(); - - //read in the xml file containing trees and convert them all to ApplicationTree instances - LoadXml(doc => - { - foreach (var addElement in doc.Root.Elements("add").OrderBy(x => - { - var sortOrderAttr = x.Attribute("sortOrder"); - return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; - })) - { - var applicationAlias = (string)addElement.Attribute("application"); - var type = (string)addElement.Attribute("type"); - var assembly = (string)addElement.Attribute("assembly"); - - var clrType = Type.GetType(type); - if (clrType == null) - { - _logger.Warn("The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type"); - continue; - } - - //check if the tree definition (applicationAlias + type + assembly) is already in the list - - if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false) - { - list.Add(new ApplicationTree( - addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value), - addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0, - addElement.Attribute("application").Value, - addElement.Attribute("alias").Value, - addElement.Attribute("title").Value, - addElement.Attribute("iconClosed").Value, - addElement.Attribute("iconOpen").Value, - addElement.Attribute("type").Value)); - } - } - - return false; - - }, false); - - return list; - } - - - internal static event TypedEventHandler Deleted; - private static void OnDeleted(ApplicationTree app, EventArgs args) - { - if (Deleted != null) - { - Deleted(app, args); - } - } - - internal static event TypedEventHandler New; - private static void OnNew(ApplicationTree app, EventArgs args) - { - if (New != null) - { - New(app, args); - } - } - - internal static event TypedEventHandler Updated; - private static void OnUpdated(ApplicationTree app, EventArgs args) - { - if (Updated != null) - { - Updated(app, args); - } - } - - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Services +{ + internal class ApplicationTreeService : IApplicationTreeService + { + private readonly ILogger _logger; + private readonly CacheHelper _cache; + private Lazy> _allAvailableTrees; + internal const string TreeConfigFileName = "trees.config"; + private static string _treeConfig; + private static readonly object Locker = new object(); + + public ApplicationTreeService(ILogger logger, CacheHelper cache) + { + _logger = logger; + _cache = cache; + } + + /// + /// gets/sets the trees.config file path + /// + /// + /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath + /// + internal static string TreeConfigFilePath + { + get + { + if (string.IsNullOrWhiteSpace(_treeConfig)) + { + _treeConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + TreeConfigFileName); + } + return _treeConfig; + } + set { _treeConfig = value; } + } + + /// + /// The main entry point to get application trees + /// + /// + /// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins + /// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned. + /// + private List GetAppTrees() + { + return _cache.RuntimeCache.GetCacheItem>( + CacheKeys.ApplicationTreeCacheKey, + () => + { + var list = ReadFromXmlAndSort(); + + //now we can check the non-volatile flag + if (_allAvailableTrees != null) + { + var hasChanges = false; + + LoadXml(doc => + { + //Now, load in the xml structure and update it with anything that is not declared there and save the file. + + //NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy + // based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this + // currently because of the legacy assemblies and types not in the Core. + + //Get all the trees not registered in the config + var unregistered = _allAvailableTrees.Value + .Where(x => list.Any(l => l.Alias == x.Alias) == false) + .ToArray(); + + hasChanges = unregistered.Any(); + + if (hasChanges == false) return false; + + //add the unregistered ones to the list and re-save the file if any changes were found + var count = 0; + foreach (var tree in unregistered) + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", tree.Initialize), + new XAttribute("sortOrder", tree.SortOrder), + new XAttribute("alias", tree.Alias), + new XAttribute("application", tree.ApplicationAlias), + new XAttribute("title", tree.Title), + new XAttribute("iconClosed", tree.IconClosed), + new XAttribute("iconOpen", tree.IconOpened), + new XAttribute("type", tree.Type))); + count++; + } + + //don't save if there's no changes + return count > 0; + }, true); + + if (hasChanges) + { + //If there were changes, we need to re-read the structures from the XML + list = ReadFromXmlAndSort(); + } + } + + + return list; + + + }, new TimeSpan(0, 10, 0)); + } + + /// + /// 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) + { + LoadXml(doc => + { + var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == alias && x.Attribute("application").Value == applicationAlias); + + if (el == null) + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", initialize), + new XAttribute("sortOrder", sortOrder), + new XAttribute("alias", alias), + new XAttribute("application", applicationAlias), + new XAttribute("title", title), + new XAttribute("iconClosed", iconClosed), + new XAttribute("iconOpen", iconOpened), + new XAttribute("type", type))); + } + + return true; + + }, true); + + OnNew(new ApplicationTree(initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, type), new EventArgs()); + } + + /// + /// Saves this instance. + /// + public void SaveTree(ApplicationTree tree) + { + LoadXml(doc => + { + var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == tree.Alias && x.Attribute("application").Value == tree.ApplicationAlias); + + if (el != null) + { + el.RemoveAttributes(); + + el.Add(new XAttribute("initialize", tree.Initialize)); + el.Add(new XAttribute("sortOrder", tree.SortOrder)); + el.Add(new XAttribute("alias", tree.Alias)); + el.Add(new XAttribute("application", tree.ApplicationAlias)); + el.Add(new XAttribute("title", tree.Title)); + el.Add(new XAttribute("iconClosed", tree.IconClosed)); + el.Add(new XAttribute("iconOpen", tree.IconOpened)); + el.Add(new XAttribute("type", tree.Type)); + } + + return true; + + }, true); + + OnUpdated(tree, new EventArgs()); + } + + /// + /// Deletes this instance. + /// + public void DeleteTree(ApplicationTree tree) + { + LoadXml(doc => + { + doc.Root.Elements("add") + .Where(x => x.Attribute("application") != null + && x.Attribute("application").Value == tree.ApplicationAlias + && x.Attribute("alias") != null && x.Attribute("alias").Value == tree.Alias).Remove(); + + return true; + + }, true); + + OnDeleted(tree, new EventArgs()); + } + + /// + /// Gets an ApplicationTree by it's tree alias. + /// + /// The tree alias. + /// An ApplicationTree instance + public ApplicationTree GetByAlias(string treeAlias) + { + return GetAppTrees().Find(t => (t.Alias == treeAlias)); + + } + + /// + /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. + /// + /// Returns a ApplicationTree Array + public IEnumerable GetAll() + { + return GetAppTrees().OrderBy(x => x.SortOrder); + } + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// Returns a ApplicationTree Array + public IEnumerable GetApplicationTrees(string applicationAlias) + { + return GetApplicationTrees(applicationAlias, false); + } + + /// + /// 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) + { + var list = GetAppTrees().FindAll( + t => + { + if (onlyInitialized) + return (t.ApplicationAlias == applicationAlias && t.Initialize); + return (t.ApplicationAlias == applicationAlias); + } + ); + + return list.OrderBy(x => x.SortOrder).ToArray(); + } + + /// + /// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the + /// callback with the xml document and saves the structure back to disk if saveAfterCallback is true. + /// + /// + /// + internal void LoadXml(Func callback, bool saveAfterCallbackIfChanges) + { + lock (Locker) + { + var doc = System.IO.File.Exists(TreeConfigFilePath) + ? XDocument.Load(TreeConfigFilePath) + : XDocument.Parse(""); + + if (doc.Root != null) + { + var hasChanges = callback.Invoke(doc); + + if (saveAfterCallbackIfChanges && hasChanges + //Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process + // in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and + // more about the IO trying to save the XML doc, but it doesn't hurt to check. + && doc.Root != null && doc.Root.Elements().Any()) + { + //ensures the folder exists + Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath)); + + //saves it + doc.Save(TreeConfigFilePath); + + //remove the cache now that it has changed SD: I'm leaving this here even though it + // is taken care of by events as well, I think unit tests may rely on it being cleared here. + _cache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + } + } + } + } + + private List ReadFromXmlAndSort() + { + var list = new List(); + + //read in the xml file containing trees and convert them all to ApplicationTree instances + LoadXml(doc => + { + foreach (var addElement in doc.Root.Elements("add").OrderBy(x => + { + var sortOrderAttr = x.Attribute("sortOrder"); + return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; + })) + { + var applicationAlias = (string)addElement.Attribute("application"); + var type = (string)addElement.Attribute("type"); + var assembly = (string)addElement.Attribute("assembly"); + + var clrType = Type.GetType(type); + if (clrType == null) + { + _logger.Warn("The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type"); + continue; + } + + //check if the tree definition (applicationAlias + type + assembly) is already in the list + + if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false) + { + list.Add(new ApplicationTree( + addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value), + addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0, + addElement.Attribute("application").Value, + addElement.Attribute("alias").Value, + addElement.Attribute("title").Value, + addElement.Attribute("iconClosed").Value, + addElement.Attribute("iconOpen").Value, + addElement.Attribute("type").Value)); + } + } + + return false; + + }, false); + + return list; + } + + + internal static event TypedEventHandler Deleted; + private static void OnDeleted(ApplicationTree app, EventArgs args) + { + if (Deleted != null) + { + Deleted(app, args); + } + } + + internal static event TypedEventHandler New; + private static void OnNew(ApplicationTree app, EventArgs args) + { + if (New != null) + { + New(app, args); + } + } + + internal static event TypedEventHandler Updated; + private static void OnUpdated(ApplicationTree app, EventArgs args) + { + if (Updated != null) + { + Updated(app, args); + } + } + + /// + /// This class is here so that we can provide lazy access to tree scanning for when it is needed + /// + private class LazyEnumerableTrees : IEnumerable + { + public LazyEnumerableTrees() + { + _lazyTrees = new Lazy>(() => + { + var added = new List(); + + // Load all Controller Trees by attribute + var types = PluginManager.Current.ResolveTypesWithAttribute(); + //convert them to ApplicationTree instances + var items = types + .Select(x => + new Tuple(x, x.GetCustomAttributes(false).Single())) + .Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly())) + .ToArray(); + + added.AddRange(items.Select(x => x.Alias)); + + return items.ToArray(); + }); + } + + private readonly Lazy> _lazyTrees; + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator() + { + return _lazyTrees.Value.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Web/Services/SectionService.cs similarity index 95% rename from src/Umbraco.Core/Services/SectionService.cs rename to src/Umbraco.Web/Services/SectionService.cs index 376c41471e..07da567636 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Web/Services/SectionService.cs @@ -1,329 +1,330 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Xml.Linq; -using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; -using File = System.IO.File; - -namespace Umbraco.Core.Services -{ - internal class SectionService : ISectionService - { - private readonly IUserService _userService; - private readonly Lazy> _allAvailableSections; - private readonly IApplicationTreeService _applicationTreeService; - private readonly IDatabaseUnitOfWorkProvider _uowProvider; - private readonly CacheHelper _cache; - internal const string AppConfigFileName = "applications.config"; - private static string _appConfig; - private static readonly object Locker = new object(); - - public SectionService( - IUserService userService, - IApplicationTreeService applicationTreeService, - IDatabaseUnitOfWorkProvider uowProvider, - CacheHelper cache) - { - if (applicationTreeService == null) throw new ArgumentNullException("applicationTreeService"); - if (cache == null) throw new ArgumentNullException("cache"); - - _userService = userService; - _applicationTreeService = applicationTreeService; - _uowProvider = uowProvider; - _cache = cache; - _allAvailableSections = new Lazy>(() => new LazyEnumerableSections()); - } - - - /// - /// gets/sets the application.config file path - /// - /// - /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath - /// - internal static string AppConfigFilePath - { - get - { - if (string.IsNullOrWhiteSpace(_appConfig)) - { - _appConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + AppConfigFileName); - } - return _appConfig; - } - set { _appConfig = value; } - } - - /// - /// The cache storage for all applications - /// - public IEnumerable
GetSections() - { - return _cache.RuntimeCache.GetCacheItem>( - CacheKeys.ApplicationsCacheKey, - () => - { - var list = ReadFromXmlAndSort(); - var hasChanges = false; - var localCopyList = list; - - LoadXml(doc => - { - //Now, load in the xml structure and update it with anything that is not declared there and save the file. - //NOTE: On the first iteration here, it will lazily scan all apps, etc... this is because this ienumerable is lazy - //Get all the trees not registered in the config - - var unregistered = _allAvailableSections.Value - .Where(x => localCopyList.Any(l => l.Alias == x.Alias) == false) - .ToArray(); - - hasChanges = unregistered.Any(); - - var count = 0; - foreach (var attr in unregistered) - { - doc.Root.Add(new XElement("add", - new XAttribute("alias", attr.Alias), - new XAttribute("name", attr.Name), - new XAttribute("icon", attr.Icon), - new XAttribute("sortOrder", attr.SortOrder))); - count++; - } - - //don't save if there's no changes - return count > 0; - }, true); - - if (hasChanges) - { - //If there were changes, we need to re-read the structures from the XML - list = ReadFromXmlAndSort(); - } - - return list; - - }, new TimeSpan(0, 10, 0)); - } - - internal void LoadXml(Func callback, bool saveAfterCallbackIfChanged) - { - lock (Locker) - { - var doc = File.Exists(AppConfigFilePath) - ? XDocument.Load(AppConfigFilePath) - : XDocument.Parse(""); - - if (doc.Root != null) - { - var changed = callback.Invoke(doc); - - if (saveAfterCallbackIfChanged && changed) - { - //ensure the folder is created! - Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath)); - - doc.Save(AppConfigFilePath); - - //remove the cache so it gets re-read ... SD: I'm leaving this here even though it - // is taken care of by events as well, I think unit tests may rely on it being cleared here. - _cache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); - } - } - } - } - - /// - /// Get the user's allowed sections - /// - /// - /// - public IEnumerable
GetAllowedSections(int userId) - { - - var user = _userService.GetUserById(userId); - if (user == null) - { - throw new InvalidOperationException("No user found with id " + userId); - } - - return GetSections().Where(x => user.AllowedSections.Contains(x.Alias)); - } - - /// - /// Gets the application by its alias. - /// - /// The application alias. - /// - public Section GetByAlias(string appAlias) - { - return GetSections().FirstOrDefault(t => t.Alias == 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. - public void MakeNew(string name, string alias, string icon) - { - MakeNew(name, alias, icon, GetSections().Max(x => x.SortOrder) + 1); - } - - /// - /// Makes the new. - /// - /// The name. - /// The alias. - /// The icon. - /// The sort order. - public void MakeNew(string name, string alias, string icon, int sortOrder) - { - if (GetSections().All(x => x.Alias != alias)) - { - LoadXml(doc => - { - doc.Root.Add(new XElement("add", - new XAttribute("alias", alias), - new XAttribute("name", name), - new XAttribute("icon", icon), - new XAttribute("sortOrder", sortOrder))); - return true; - }, true); - - //raise event - OnNew(new Section(name, alias, icon, sortOrder), new EventArgs()); - } - } - - /// - /// Deletes the section - /// - public void DeleteSection(Section section) - { - lock (Locker) - { - //delete the assigned applications - _uowProvider.GetUnitOfWork().Database.Execute( - "delete from umbracoUser2App where app = @appAlias", - new { appAlias = section.Alias }); - - //delete the assigned trees - var trees = _applicationTreeService.GetApplicationTrees(section.Alias); - foreach (var t in trees) - { - _applicationTreeService.DeleteTree(t); - } - - LoadXml(doc => - { - doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == section.Alias) - .Remove(); - - return true; - }, true); - - //raise event - OnDeleted(section, new EventArgs()); - } - } - - private List
ReadFromXmlAndSort() - { - var tmp = new List
(); - - LoadXml(doc => - { - foreach (var addElement in doc.Root.Elements("add").OrderBy(x => - { - var sortOrderAttr = x.Attribute("sortOrder"); - return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; - })) - { - var sortOrderAttr = addElement.Attribute("sortOrder"); - tmp.Add(new Section(addElement.Attribute("name").Value, - addElement.Attribute("alias").Value, - addElement.Attribute("icon").Value, - sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0)); - } - return false; - }, false); - - return tmp; - } - - internal static event TypedEventHandler Deleted; - private static void OnDeleted(Section app, EventArgs args) - { - if (Deleted != null) - { - Deleted(app, args); - } - } - - internal static event TypedEventHandler New; - private static void OnNew(Section app, EventArgs args) - { - if (New != null) - { - New(app, args); - } - } - - /// - /// This class is here so that we can provide lazy access to tree scanning for when it is needed - /// - private class LazyEnumerableSections : IEnumerable
- { - public LazyEnumerableSections() - { - _lazySections = new Lazy>(() => - { - // Load all Applications by attribute and add them to the XML config - - //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 - var types = PluginManager.Current.ResolveTypesWithAttribute(cacheResult: false); - - //since applications don't populate their metadata from the attribute and because it is an interface, - //we need to interrogate the attributes for the data. Would be better to have a base class that contains - //metadata populated by the attribute. Oh well i guess. - var attrs = types.Select(x => x.GetCustomAttributes(false).Single()); - return attrs.Select(x => new Section(x.Name, x.Alias, x.Icon, x.SortOrder)).ToArray(); - }); - } - - private readonly Lazy> _lazySections; - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - public IEnumerator
GetEnumerator() - { - return _lazySections.Value.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using File = System.IO.File; + +namespace Umbraco.Web.Services +{ + internal class SectionService : ISectionService + { + private readonly IUserService _userService; + private readonly Lazy> _allAvailableSections; + private readonly IApplicationTreeService _applicationTreeService; + private readonly IDatabaseUnitOfWorkProvider _uowProvider; + private readonly CacheHelper _cache; + internal const string AppConfigFileName = "applications.config"; + private static string _appConfig; + private static readonly object Locker = new object(); + + public SectionService( + IUserService userService, + IApplicationTreeService applicationTreeService, + IDatabaseUnitOfWorkProvider uowProvider, + CacheHelper cache) + { + if (applicationTreeService == null) throw new ArgumentNullException("applicationTreeService"); + if (cache == null) throw new ArgumentNullException("cache"); + + _userService = userService; + _applicationTreeService = applicationTreeService; + _uowProvider = uowProvider; + _cache = cache; + _allAvailableSections = new Lazy>(() => new LazyEnumerableSections()); + } + + + /// + /// gets/sets the application.config file path + /// + /// + /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath + /// + internal static string AppConfigFilePath + { + get + { + if (string.IsNullOrWhiteSpace(_appConfig)) + { + _appConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + AppConfigFileName); + } + return _appConfig; + } + set { _appConfig = value; } + } + + /// + /// The cache storage for all applications + /// + public IEnumerable
GetSections() + { + return _cache.RuntimeCache.GetCacheItem>( + CacheKeys.ApplicationsCacheKey, + () => + { + var list = ReadFromXmlAndSort(); + var hasChanges = false; + var localCopyList = list; + + LoadXml(doc => + { + //Now, load in the xml structure and update it with anything that is not declared there and save the file. + //NOTE: On the first iteration here, it will lazily scan all apps, etc... this is because this ienumerable is lazy + //Get all the trees not registered in the config + + var unregistered = _allAvailableSections.Value + .Where(x => localCopyList.Any(l => l.Alias == x.Alias) == false) + .ToArray(); + + hasChanges = unregistered.Any(); + + var count = 0; + foreach (var attr in unregistered) + { + doc.Root.Add(new XElement("add", + new XAttribute("alias", attr.Alias), + new XAttribute("name", attr.Name), + new XAttribute("icon", attr.Icon), + new XAttribute("sortOrder", attr.SortOrder))); + count++; + } + + //don't save if there's no changes + return count > 0; + }, true); + + if (hasChanges) + { + //If there were changes, we need to re-read the structures from the XML + list = ReadFromXmlAndSort(); + } + + return list; + + }, new TimeSpan(0, 10, 0)); + } + + internal void LoadXml(Func callback, bool saveAfterCallbackIfChanged) + { + lock (Locker) + { + var doc = File.Exists(AppConfigFilePath) + ? XDocument.Load(AppConfigFilePath) + : XDocument.Parse(""); + + if (doc.Root != null) + { + var changed = callback.Invoke(doc); + + if (saveAfterCallbackIfChanged && changed) + { + //ensure the folder is created! + Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath)); + + doc.Save(AppConfigFilePath); + + //remove the cache so it gets re-read ... SD: I'm leaving this here even though it + // is taken care of by events as well, I think unit tests may rely on it being cleared here. + _cache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + } + } + } + } + + /// + /// Get the user's allowed sections + /// + /// + /// + public IEnumerable
GetAllowedSections(int userId) + { + + var user = _userService.GetUserById(userId); + if (user == null) + { + throw new InvalidOperationException("No user found with id " + userId); + } + + return GetSections().Where(x => user.AllowedSections.Contains(x.Alias)); + } + + /// + /// Gets the application by its alias. + /// + /// The application alias. + /// + public Section GetByAlias(string appAlias) + { + return GetSections().FirstOrDefault(t => t.Alias == 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. + public void MakeNew(string name, string alias, string icon) + { + MakeNew(name, alias, icon, GetSections().Max(x => x.SortOrder) + 1); + } + + /// + /// Makes the new. + /// + /// The name. + /// The alias. + /// The icon. + /// The sort order. + public void MakeNew(string name, string alias, string icon, int sortOrder) + { + if (GetSections().All(x => x.Alias != alias)) + { + LoadXml(doc => + { + doc.Root.Add(new XElement("add", + new XAttribute("alias", alias), + new XAttribute("name", name), + new XAttribute("icon", icon), + new XAttribute("sortOrder", sortOrder))); + return true; + }, true); + + //raise event + OnNew(new Section(name, alias, icon, sortOrder), new EventArgs()); + } + } + + /// + /// Deletes the section + /// + public void DeleteSection(Section section) + { + lock (Locker) + { + //delete the assigned applications + _uowProvider.GetUnitOfWork().Database.Execute( + "delete from umbracoUser2App where app = @appAlias", + new { appAlias = section.Alias }); + + //delete the assigned trees + var trees = _applicationTreeService.GetApplicationTrees(section.Alias); + foreach (var t in trees) + { + _applicationTreeService.DeleteTree(t); + } + + LoadXml(doc => + { + doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == section.Alias) + .Remove(); + + return true; + }, true); + + //raise event + OnDeleted(section, new EventArgs()); + } + } + + private List
ReadFromXmlAndSort() + { + var tmp = new List
(); + + LoadXml(doc => + { + foreach (var addElement in doc.Root.Elements("add").OrderBy(x => + { + var sortOrderAttr = x.Attribute("sortOrder"); + return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; + })) + { + var sortOrderAttr = addElement.Attribute("sortOrder"); + tmp.Add(new Section(addElement.Attribute("name").Value, + addElement.Attribute("alias").Value, + addElement.Attribute("icon").Value, + sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0)); + } + return false; + }, false); + + return tmp; + } + + internal static event TypedEventHandler Deleted; + private static void OnDeleted(Section app, EventArgs args) + { + if (Deleted != null) + { + Deleted(app, args); + } + } + + internal static event TypedEventHandler New; + private static void OnNew(Section app, EventArgs args) + { + if (New != null) + { + New(app, args); + } + } + + /// + /// This class is here so that we can provide lazy access to tree scanning for when it is needed + /// + private class LazyEnumerableSections : IEnumerable
+ { + public LazyEnumerableSections() + { + _lazySections = new Lazy>(() => + { + // Load all Applications by attribute and add them to the XML config + + //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 + var types = PluginManager.Current.ResolveTypesWithAttribute(cacheResult: false); + + //since applications don't populate their metadata from the attribute and because it is an interface, + //we need to interrogate the attributes for the data. Would be better to have a base class that contains + //metadata populated by the attribute. Oh well i guess. + var attrs = types.Select(x => x.GetCustomAttributes(false).Single()); + return Enumerable.ToArray
(attrs.Select(x => new Section(x.Name, x.Alias, x.Icon, x.SortOrder))); + }); + } + + private readonly Lazy> _lazySections; + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + public IEnumerator
GetEnumerator() + { + return _lazySections.Value.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + } +} diff --git a/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs b/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs deleted file mode 100644 index 314cc9142e..0000000000 --- a/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using umbraco.businesslogic; -using Umbraco.Core.Services; - -namespace Umbraco.Web.Trees -{ - /// - /// A startup handler for putting the tree config in the config file based on attributes found - /// - /// - /// TODO: This is really not a very ideal process but the code is found here because tree plugins are in the Web project or the legacy business logic project. - /// Moving forward we can put the base tree plugin classes in the core and then this can all just be taken care of normally within the service. - /// - public sealed class ApplicationTreeRegistrar : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //Call initialize on the tree service with the lazy enumerable class below, when the tree service needs to resolve, - // it will lazily do the scanning and comparing so it's not actually done on app start. - applicationContext.Services.ApplicationTreeService.Intitialize(new LazyEnumerableTrees()); - } - - /// - /// This class is here so that we can provide lazy access to tree scanning for when it is needed - /// - private class LazyEnumerableTrees : IEnumerable - { - public LazyEnumerableTrees() - { - _lazyTrees = new Lazy>(() => - { - var added = new List(); - - // Load all Controller Trees by attribute - var types = PluginManager.Current.ResolveAttributedTreeControllers(); - //convert them to ApplicationTree instances - var items = types - .Select(x => - new Tuple(x, x.GetCustomAttributes(false).Single())) - .Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly())) - .ToArray(); - - added.AddRange(items.Select(x => x.Alias)); - - return items.ToArray(); - }); - } - - private readonly Lazy> _lazyTrees; - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return _lazyTrees.Value.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f581747494..c2bad0a5ca 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -348,6 +348,13 @@ + + + + + + + @@ -378,6 +385,7 @@ + @@ -590,7 +598,6 @@ - @@ -777,7 +784,6 @@ - @@ -791,8 +797,6 @@ - -