manifest dashboard code

This commit is contained in:
Shannon
2018-02-23 11:00:03 +10:00
parent accf8ba5f2
commit 0aa930d1c9
6 changed files with 235 additions and 40 deletions

View File

@@ -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
/// </summary>
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";
/// <summary>
/// Returns all grid editors found in the manfifests
/// </summary>
@@ -33,7 +36,7 @@ namespace Umbraco.Core.Manifest
{
get
{
return _cache.GetCacheItem<IEnumerable<GridEditor>>(
return _runtimeCache.GetCacheItem<IEnumerable<GridEditor>>(
typeof (ManifestBuilder) + GridEditorsKey,
() =>
{
@@ -58,7 +61,7 @@ namespace Umbraco.Core.Manifest
{
get
{
return _cache.GetCacheItem<IEnumerable<PropertyEditor>>(
return _runtimeCache.GetCacheItem<IEnumerable<PropertyEditor>>(
typeof(ManifestBuilder) + PropertyEditorsKey,
() =>
{
@@ -83,7 +86,7 @@ namespace Umbraco.Core.Manifest
{
get
{
return _cache.GetCacheItem<IEnumerable<ParameterEditor>>(
return _runtimeCache.GetCacheItem<IEnumerable<ParameterEditor>>(
typeof (ManifestBuilder) + ParameterEditorsKey,
() =>
{
@@ -98,7 +101,104 @@ namespace Umbraco.Core.Manifest
return editors;
}, new TimeSpan(0, 10, 0));
}
}
/// <summary>
/// Returns all dashboards found in the manfifests
/// </summary>
internal IDictionary<string, Section> Dashboards
{
get
{
//TODO: Need to integrate the security with the manifest dashboards
return _runtimeCache.GetCacheItem<IDictionary<string, Section>>(
typeof(ManifestBuilder) + DashboardsKey,
() =>
{
var dashboards = new Dictionary<string, Section>();
foreach (var manifest in _parser.GetManifests())
{
if (manifest.Dashboards != null)
{
var converted = manifest.Dashboards.ToDictionary(x => x.Key, x => x.Value.ToObject<Section>());
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<string>();
Tabs = new Dictionary<string, Tab>();
}
[JsonProperty("areas")]
public List<string> Areas { get; set; }
[JsonProperty("tabs")]
public IDictionary<string, Tab> Tabs { get; set; }
}
internal class Tab
{
public Tab()
{
Controls = new List<Control>();
Index = int.MaxValue; //default so we can check if this value has been explicitly set
}
[JsonProperty("controls")]
public List<Control> 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
}
}
}

View File

@@ -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<IDictionary<string, JObject>>() : new Dictionary<string, JObject>()
};
result.Add(manifest);
}
@@ -353,4 +361,4 @@ namespace Umbraco.Core.Manifest
}
}
}

View File

@@ -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
/// </summary>
public JArray ParameterEditors { get; set; }
/// <summary>
/// The json array of grid editors
/// </summary>
public JArray GridEditors { get; set; }
/// <summary>
/// The dictionary of dashboards
/// </summary>
public IDictionary<string, JObject> Dashboards { get; set; }
}
}
}

View File

@@ -123,8 +123,8 @@ namespace Umbraco.Web.Editors
[ValidateAngularAntiForgeryToken]
public IEnumerable<Tab<DashboardControl>> 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);
}
}
}

View File

@@ -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;
}
/// <summary>
@@ -28,7 +31,7 @@ namespace Umbraco.Web.Editors
public IDictionary<string, IEnumerable<Tab<DashboardControl>>> GetDashboards(IUser currentUser)
{
var result = new Dictionary<string, IEnumerable<Tab<DashboardControl>>>();
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
/// <param name="currentUser"></param>
/// <returns></returns>
public IEnumerable<Tab<DashboardControl>> 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<Tab<DashboardControl>>(); //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<Tab<DashboardControl>> GetDashboardsFromConfig(int startTabId, string section, IUser currentUser)
{
var tabs = new List<Tab<DashboardControl>>();
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<DashboardControl>();
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<Tab<DashboardControl>> 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<Tab<DashboardControl>>();
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<DashboardControl>();
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<DashboardControl>
{
//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;
}
}
}
}

View File

@@ -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
}
}
}
}