From 0aa930d1c9d97bc216f642b0f3c0d54db7c2a613 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 23 Feb 2018 11:00:03 +1000 Subject: [PATCH] manifest dashboard code --- src/Umbraco.Core/Manifest/ManifestBuilder.cs | 122 ++++++++++++++++-- src/Umbraco.Core/Manifest/ManifestParser.cs | 30 +++-- src/Umbraco.Core/Manifest/PackageManifest.cs | 12 +- .../Editors/DashboardController.cs | 4 +- src/Umbraco.Web/Editors/DashboardHelper.cs | 103 +++++++++++++-- src/Umbraco.Web/Editors/SectionController.cs | 4 +- 6 files changed, 235 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Core/Manifest/ManifestBuilder.cs b/src/Umbraco.Core/Manifest/ManifestBuilder.cs index 5f95deea22..6fc803b1f8 100644 --- a/src/Umbraco.Core/Manifest/ManifestBuilder.cs +++ b/src/Umbraco.Core/Manifest/ManifestBuilder.cs @@ -2,6 +2,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; +using Newtonsoft.Json; using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.PropertyEditors; @@ -13,19 +15,20 @@ namespace Umbraco.Core.Manifest /// internal class ManifestBuilder { - private readonly IRuntimeCacheProvider _cache; + private readonly IRuntimeCacheProvider _runtimeCache; private readonly ManifestParser _parser; public ManifestBuilder(IRuntimeCacheProvider cache, ManifestParser parser) { - _cache = cache; + _runtimeCache = cache; _parser = parser; } - private const string GridEditorsKey = "grideditors"; - private const string PropertyEditorsKey = "propertyeditors"; - private const string ParameterEditorsKey = "parametereditors"; - + public const string GridEditorsKey = "gridEditors"; + public const string PropertyEditorsKey = "propertyEditors"; + public const string ParameterEditorsKey = "parameterEditors"; + public const string DashboardsKey = "dashboards"; + /// /// Returns all grid editors found in the manfifests /// @@ -33,7 +36,7 @@ namespace Umbraco.Core.Manifest { get { - return _cache.GetCacheItem>( + return _runtimeCache.GetCacheItem>( typeof (ManifestBuilder) + GridEditorsKey, () => { @@ -58,7 +61,7 @@ namespace Umbraco.Core.Manifest { get { - return _cache.GetCacheItem>( + return _runtimeCache.GetCacheItem>( typeof(ManifestBuilder) + PropertyEditorsKey, () => { @@ -83,7 +86,7 @@ namespace Umbraco.Core.Manifest { get { - return _cache.GetCacheItem>( + return _runtimeCache.GetCacheItem>( typeof (ManifestBuilder) + ParameterEditorsKey, () => { @@ -98,7 +101,104 @@ namespace Umbraco.Core.Manifest return editors; }, new TimeSpan(0, 10, 0)); } + } + + /// + /// Returns all dashboards found in the manfifests + /// + internal IDictionary Dashboards + { + get + { + //TODO: Need to integrate the security with the manifest dashboards + + return _runtimeCache.GetCacheItem>( + typeof(ManifestBuilder) + DashboardsKey, + () => + { + var dashboards = new Dictionary(); + foreach (var manifest in _parser.GetManifests()) + { + if (manifest.Dashboards != null) + { + var converted = manifest.Dashboards.ToDictionary(x => x.Key, x => x.Value.ToObject
()); + foreach (var item in converted) + { + Section existing; + if (dashboards.TryGetValue(item.Key, out existing)) + { + foreach (var area in item.Value.Areas) + { + if (existing.Areas.Contains(area, StringComparer.InvariantCultureIgnoreCase) == false) + existing.Areas.Add(area); + } + + //merge + foreach (var tab in item.Value.Tabs) + { + Tab existingTab; + if (existing.Tabs.TryGetValue(tab.Key, out existingTab)) + { + //merge + foreach (var control in tab.Value.Controls) + { + existingTab.Controls.Add(control); + } + } + else + { + existing.Tabs[tab.Key] = tab.Value; + } + } + ; + } + else + { + dashboards[item.Key] = item.Value; + } + } + } + } + return dashboards; + }, new TimeSpan(0, 10, 0)); + } + } + + #region Internal manifest models + internal class Section + { + public Section() + { + Areas = new List(); + Tabs = new Dictionary(); + } + [JsonProperty("areas")] + public List Areas { get; set; } + [JsonProperty("tabs")] + public IDictionary Tabs { get; set; } + } + + internal class Tab + { + public Tab() + { + Controls = new List(); + Index = int.MaxValue; //default so we can check if this value has been explicitly set + } + [JsonProperty("controls")] + public List Controls { get; set; } + [JsonProperty("index")] + public int Index { get; set; } + } + + internal class Control + { + [JsonProperty("path")] + public string Path { get; set; } + [JsonProperty("caption")] + public string Caption { get; set; } } - + #endregion + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index e30d188339..77f52bac13 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -185,26 +185,33 @@ namespace Umbraco.Core.Manifest } //validate the property editors section - var propEditors = deserialized.Properties().Where(x => x.Name == "propertyEditors").ToArray(); + var propEditors = deserialized.Properties().Where(x => x.Name == ManifestBuilder.PropertyEditorsKey).ToArray(); if (propEditors.Length > 1) { throw new FormatException("The manifest is not formatted correctly contains more than one 'propertyEditors' element"); } //validate the parameterEditors section - var paramEditors = deserialized.Properties().Where(x => x.Name == "parameterEditors").ToArray(); + var paramEditors = deserialized.Properties().Where(x => x.Name == ManifestBuilder.ParameterEditorsKey).ToArray(); if (paramEditors.Length > 1) { throw new FormatException("The manifest is not formatted correctly contains more than one 'parameterEditors' element"); } //validate the gridEditors section - var gridEditors = deserialized.Properties().Where(x => x.Name == "gridEditors").ToArray(); + var gridEditors = deserialized.Properties().Where(x => x.Name == ManifestBuilder.GridEditorsKey).ToArray(); if (gridEditors.Length > 1) { throw new FormatException("The manifest is not formatted correctly contains more than one 'gridEditors' element"); } + //validate the dashboards section + var dashboards = deserialized.Properties().Where(x => x.Name == "dashboards").ToArray(); + if (dashboards.Length > 1) + { + throw new FormatException("The manifest is not formatted correctly contains more than one 'dashboards' element"); + } + var jConfig = init.Any() ? (JArray)deserialized["javascript"] : new JArray(); ReplaceVirtualPaths(jConfig); @@ -212,9 +219,9 @@ namespace Umbraco.Core.Manifest ReplaceVirtualPaths(cssConfig); //replace virtual paths for each property editor - if (deserialized["propertyEditors"] != null) + if (deserialized[ManifestBuilder.PropertyEditorsKey] != null) { - foreach (JObject p in deserialized["propertyEditors"]) + foreach (JObject p in deserialized[ManifestBuilder.PropertyEditorsKey]) { if (p["editor"] != null) { @@ -228,9 +235,9 @@ namespace Umbraco.Core.Manifest } //replace virtual paths for each property editor - if (deserialized["gridEditors"] != null) + if (deserialized[ManifestBuilder.GridEditorsKey] != null) { - foreach (JObject p in deserialized["gridEditors"]) + foreach (JObject p in deserialized[ManifestBuilder.GridEditorsKey]) { if (p["view"] != null) { @@ -247,9 +254,10 @@ namespace Umbraco.Core.Manifest { JavaScriptInitialize = jConfig, StylesheetInitialize = cssConfig, - PropertyEditors = propEditors.Any() ? (JArray)deserialized["propertyEditors"] : new JArray(), - ParameterEditors = paramEditors.Any() ? (JArray)deserialized["parameterEditors"] : new JArray(), - GridEditors = gridEditors.Any() ? (JArray)deserialized["gridEditors"] : new JArray() + PropertyEditors = propEditors.Any() ? (JArray)deserialized[ManifestBuilder.PropertyEditorsKey] : new JArray(), + ParameterEditors = paramEditors.Any() ? (JArray)deserialized[ManifestBuilder.ParameterEditorsKey] : new JArray(), + GridEditors = gridEditors.Any() ? (JArray)deserialized[ManifestBuilder.GridEditorsKey] : new JArray(), + Dashboards = dashboards.Any() ? deserialized[ManifestBuilder.DashboardsKey].ToObject>() : new Dictionary() }; result.Add(manifest); } @@ -353,4 +361,4 @@ namespace Umbraco.Core.Manifest } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index dea1eb9877..0c37591e0b 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace Umbraco.Core.Manifest { @@ -26,10 +27,15 @@ namespace Umbraco.Core.Manifest /// The json array of parameter editors /// public JArray ParameterEditors { get; set; } - + /// /// The json array of grid editors /// public JArray GridEditors { get; set; } + + /// + /// The dictionary of dashboards + /// + public IDictionary Dashboards { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index 55286d99b4..efa0d50f1c 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -123,8 +123,8 @@ namespace Umbraco.Web.Editors [ValidateAngularAntiForgeryToken] public IEnumerable> GetDashboard(string section) { - var dashboardHelper = new DashboardHelper(Services.SectionService); - return dashboardHelper.GetDashboard(section, Security.CurrentUser); + var dashboardHelper = new DashboardHelper(ApplicationContext); + return dashboardHelper.GetDashboard(section, Security.CurrentUser); } } } diff --git a/src/Umbraco.Web/Editors/DashboardHelper.cs b/src/Umbraco.Web/Editors/DashboardHelper.cs index a54df7d795..a5f84b5a68 100644 --- a/src/Umbraco.Web/Editors/DashboardHelper.cs +++ b/src/Umbraco.Web/Editors/DashboardHelper.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Manifest; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -12,12 +15,12 @@ namespace Umbraco.Web.Editors { internal class DashboardHelper { - private readonly ISectionService _sectionService; + private readonly ApplicationContext _appContext; - public DashboardHelper(ISectionService sectionService) + public DashboardHelper(ApplicationContext appContext) { - if (sectionService == null) throw new ArgumentNullException("sectionService"); - _sectionService = sectionService; + if (appContext == null) throw new ArgumentNullException("appContext"); + _appContext = appContext; } /// @@ -28,7 +31,7 @@ namespace Umbraco.Web.Editors public IDictionary>> GetDashboards(IUser currentUser) { var result = new Dictionary>>(); - foreach (var section in _sectionService.GetSections()) + foreach (var section in _appContext.Services.SectionService.GetSections()) { result[section.Alias] = GetDashboard(section.Alias, currentUser); } @@ -42,29 +45,64 @@ namespace Umbraco.Web.Editors /// /// public IEnumerable> GetDashboard(string section, IUser currentUser) + { + var configDashboards = GetDashboardsFromConfig(1, section, currentUser); + var pluginDashboards = GetDashboardsFromPlugins(configDashboards.Count + 1, section, currentUser); + + //now we need to merge them, the plugin ones would replace anything matched in the config one where the tab alias matches + var added = new List>(); //to track the ones we'll add + foreach (var configDashboard in configDashboards) + { + var matched = pluginDashboards.Where(x => string.Equals(x.Alias, configDashboard.Alias, StringComparison.InvariantCultureIgnoreCase)).ToList(); + foreach (var tab in matched) + { + configDashboard.Label = tab.Label; //overwrite + configDashboard.Properties = configDashboard.Properties.Concat(tab.Properties).ToList(); //combine + added.Add(tab); //track this + } + } + + //now add the plugin dashboards to the config dashboards that have not already been added + var toAdd = pluginDashboards.Where(pluginDashboard => added.Contains(pluginDashboard) == false).ToList(); + configDashboards.AddRange(toAdd); + + //last thing is to re-sort and ID the tabs + configDashboards.Sort((tab, tab1) => tab.Id > tab1.Id ? 1 : 0); + for (var index = 0; index < configDashboards.Count; index++) + { + var tab = configDashboards[index]; + tab.Id = (index + 1); + if (tab.Id == 1) + tab.IsActive = true; + } + + return configDashboards; + } + + private List> GetDashboardsFromConfig(int startTabId, string section, IUser currentUser) { var tabs = new List>(); - var i = 1; + var i = startTabId; // The dashboard config can contain more than one area inserted by a package. foreach (var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section))) { //we need to validate access to this section - if (DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService) == false) + if (DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _appContext.Services.SectionService) == false) continue; //User is authorized foreach (var tab in dashboardSection.Tabs) { //we need to validate access to this tab - if (DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService) == false) + if (DashboardSecurity.AuthorizeAccess(tab, currentUser, _appContext.Services.SectionService) == false) continue; var dashboardControls = new List(); foreach (var control in tab.Controls) { - if (DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService) == false) + if (DashboardSecurity.AuthorizeAccess(control, currentUser, _appContext.Services.SectionService) == false) continue; var dashboardControl = new DashboardControl(); @@ -81,7 +119,6 @@ namespace Umbraco.Web.Editors { Id = i, Alias = tab.Caption.ToSafeAlias(), - IsActive = i == 1, Label = tab.Caption, Properties = dashboardControls }); @@ -93,5 +130,49 @@ namespace Umbraco.Web.Editors //In case there are no tabs or a user doesn't have access the empty tabs list is returned return tabs; } + + private List> GetDashboardsFromPlugins(int startTabId, string section, IUser currentUser) + { + //TODO: Need to integrate the security with the manifest dashboards + + var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); + var parser = new ManifestParser(appPlugins, _appContext.ApplicationCache.RuntimeCache); + var builder = new ManifestBuilder(_appContext.ApplicationCache.RuntimeCache, parser); + + var tabs = new List>(); + var i = startTabId; + + foreach (var sectionDashboard in builder.Dashboards.Where(x => x.Value.Areas.InvariantContains(section))) + { + foreach (var tab in sectionDashboard.Value.Tabs) + { + var dashboardControls = new List(); + + foreach (var control in tab.Value.Controls) + { + var dashboardControl = new DashboardControl(); + var controlPath = control.Path.Trim(); + dashboardControl.Caption = control.Caption; + dashboardControl.Path = IOHelper.FindFile(controlPath); + if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant())) + dashboardControl.ServerSide = true; + + dashboardControls.Add(dashboardControl); + } + + tabs.Add(new Tab + { + //assign the Id to the value of the index if one was defined, then we'll use the Id to sort later + Id = tab.Value.Index == int.MaxValue ? i : tab.Value.Index, + Alias = tab.Key.ToSafeAlias(), + Label = tab.Key, + Properties = dashboardControls + }); + + i++; + } + } + return tabs; + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 6c94b410f9..f53cb53b52 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Editors //Check if there are empty dashboards or dashboards that will end up empty based on the current user's access //and add the meta data about them - var dashboardHelper = new DashboardHelper(Services.SectionService); + var dashboardHelper = new DashboardHelper(ApplicationContext); //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 @@ -72,4 +72,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +}