Refactored the ManifestContentAppDefinition to have a factory and a seperate data class.

This commit is contained in:
Bjarke Berg
2018-12-06 10:02:23 +01:00
parent f26353a34e
commit aee6156a09
18 changed files with 212 additions and 158 deletions
@@ -6,11 +6,11 @@ using Umbraco.Core.Serialization;
namespace Umbraco.Core.Manifest
{
/// <summary>
/// Implements a json read converter for <see cref="IContentAppDefinition"/>.
/// Implements a json read converter for <see cref="ManifestContentAppDefinition"/>.
/// </summary>
internal class ContentAppDefinitionConverter : JsonReadConverter<IContentAppDefinition>
internal class ContentAppDefinitionConverter : JsonReadConverter<ManifestContentAppDefinition>
{
protected override IContentAppDefinition Create(Type objectType, string path, JObject jObject)
protected override ManifestContentAppDefinition Create(Type objectType, string path, JObject jObject)
=> new ManifestContentAppDefinition();
}
}
@@ -31,11 +31,9 @@ namespace Umbraco.Core.Manifest
/// Represents a content app definition, parsed from a manifest.
/// </summary>
[DataContract(Name = "appdef", Namespace = "")]
public class ManifestContentAppDefinition : IContentAppDefinition
public class ManifestContentAppDefinition
{
private string _view;
private ContentApp _app;
private ShowRule[] _showRules;
/// <summary>
/// Gets or sets the name of the content app.
@@ -83,132 +81,5 @@ namespace Umbraco.Core.Manifest
[DataMember(Name = "show")]
public string[] Show { get; set; } = Array.Empty<string>();
/// <inheritdoc />
public ContentApp GetContentAppFor(object o, IEnumerable<IReadOnlyUserGroup> userGroups)
{
string partA, partB;
switch (o)
{
case IContent content:
partA = "content";
partB = content.ContentType.Alias;
break;
case IMedia media:
partA = "media";
partB = media.ContentType.Alias;
break;
default:
return null;
}
var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray());
var userGroupsList = userGroups.ToList();
var okRole = false;
var hasRole = false;
var okType = false;
var hasType = false;
foreach (var rule in rules)
{
if (rule.PartA.InvariantEquals("role"))
{
// if roles have been ok-ed already, skip the rule
if (okRole)
continue;
// remember we have role rules
hasRole = true;
foreach (var group in userGroupsList)
{
// if the entry does not apply, skip
if (!rule.Matches("role", group.Alias))
continue;
// if the entry applies,
// if it's an exclude entry, exit, do not display the content app
if (!rule.Show)
return null;
// else ok to display, remember roles are ok, break from userGroupsList
okRole = rule.Show;
break;
}
}
else // it is a type rule
{
// if type has been ok-ed already, skip the rule
if (okType)
continue;
// remember we have type rules
hasType = true;
// if the entry does not apply, skip it
if (!rule.Matches(partA, partB))
continue;
// if the entry applies,
// if it's an exclude entry, exit, do not display the content app
if (!rule.Show)
return null;
// else ok to display, remember type rules are ok
okType = true;
}
}
// if roles rules are specified but not ok,
// or if type roles are specified but not ok,
// cannot display the content app
if ((hasRole && !okRole) || (hasType && !okType))
return null;
// else
// content app can be displayed
return _app ?? (_app = new ContentApp
{
Alias = Alias,
Name = Name,
Icon = Icon,
View = View,
Weight = Weight
});
}
private class ShowRule
{
private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public bool Show { get; private set; }
public string PartA { get; private set; }
public string PartB { get; private set; }
public bool Matches(string partA, string partB)
{
return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB));
}
public static IEnumerable<ShowRule> Parse(string[] rules)
{
foreach (var rule in rules)
{
var match = ShowRegex.Match(rule);
if (!match.Success)
throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest.");
yield return new ShowRule
{
Show = match.Groups[1].Value != "-",
PartA = match.Groups[2].Value,
PartB = match.Groups[3].Value
};
}
}
}
}
}
@@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Manifest
{
// contentApps: [
// {
// name: 'App Name', // required
// alias: 'appAlias', // required
// weight: 0, // optional, default is 0, use values between -99 and +99
// icon: 'icon.app', // required
// view: 'path/view.htm', // required
// show: [ // optional, default is always show
// '-content/foo', // hide for content type 'foo'
// '+content/*', // show for all other content types
// '+media/*', // show for all media types
// '+role/admin' // show for admin users. Role based permissions will override others.
// ]
// },
// ...
// ]
/// <summary>
/// Represents a content app definition, parsed from a manifest.
/// </summary>
public class ManifestContentAppFactory : IContentAppFactory
{
private readonly ManifestContentAppDefinition _definition;
public ManifestContentAppFactory(ManifestContentAppDefinition definition)
{
_definition = definition;
}
private ContentApp _app;
private ShowRule[] _showRules;
/// <inheritdoc />
public ContentApp GetContentAppFor(object o,IEnumerable<IReadOnlyUserGroup> userGroups)
{
string partA, partB;
switch (o)
{
case IContent content:
partA = "content";
partB = content.ContentType.Alias;
break;
case IMedia media:
partA = "media";
partB = media.ContentType.Alias;
break;
default:
return null;
}
var rules = _showRules ?? (_showRules = ShowRule.Parse(_definition.Show).ToArray());
var userGroupsList = userGroups.ToList();
var okRole = false;
var hasRole = false;
var okType = false;
var hasType = false;
foreach (var rule in rules)
{
if (rule.PartA.InvariantEquals("role"))
{
// if roles have been ok-ed already, skip the rule
if (okRole)
continue;
// remember we have role rules
hasRole = true;
foreach (var group in userGroupsList)
{
// if the entry does not apply, skip
if (!rule.Matches("role", group.Alias))
continue;
// if the entry applies,
// if it's an exclude entry, exit, do not display the content app
if (!rule.Show)
return null;
// else ok to display, remember roles are ok, break from userGroupsList
okRole = rule.Show;
break;
}
}
else // it is a type rule
{
// if type has been ok-ed already, skip the rule
if (okType)
continue;
// remember we have type rules
hasType = true;
// if the entry does not apply, skip it
if (!rule.Matches(partA, partB))
continue;
// if the entry applies,
// if it's an exclude entry, exit, do not display the content app
if (!rule.Show)
return null;
// else ok to display, remember type rules are ok
okType = true;
}
}
// if roles rules are specified but not ok,
// or if type roles are specified but not ok,
// cannot display the content app
if ((hasRole && !okRole) || (hasType && !okType))
return null;
// else
// content app can be displayed
return _app ?? (_app = new ContentApp
{
Alias = _definition.Alias,
Name = _definition.Name,
Icon = _definition.Icon,
View = _definition.View,
Weight = _definition.Weight
});
}
private class ShowRule
{
private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public bool Show { get; private set; }
public string PartA { get; private set; }
public string PartB { get; private set; }
public bool Matches(string partA, string partB)
{
return (PartA == "*" || PartA.InvariantEquals(partA)) && (PartB == "*" || PartB.InvariantEquals(partB));
}
public static IEnumerable<ShowRule> Parse(string[] rules)
{
foreach (var rule in rules)
{
var match = ShowRegex.Match(rule);
if (!match.Success)
throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest.");
yield return new ShowRule
{
Show = match.Groups[1].Value != "-",
PartA = match.Groups[2].Value,
PartB = match.Groups[3].Value
};
}
}
}
}
}
+1 -1
View File
@@ -99,7 +99,7 @@ namespace Umbraco.Core.Manifest
var propertyEditors = new List<IDataEditor>();
var parameterEditors = new List<IDataEditor>();
var gridEditors = new List<GridEditor>();
var contentApps = new List<IContentAppDefinition>();
var contentApps = new List<ManifestContentAppDefinition>();
var dashboards = new List<ManifestDashboardDefinition>();
foreach (var manifest in manifests)
+1 -1
View File
@@ -26,7 +26,7 @@ namespace Umbraco.Core.Manifest
public GridEditor[] GridEditors { get; set; } = Array.Empty<GridEditor>();
[JsonProperty("contentApps")]
public IContentAppDefinition[] ContentApps { get; set; } = Array.Empty<IContentAppDefinition>();
public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty<ManifestContentAppDefinition>();
[JsonProperty("dashboards")]
public ManifestDashboardDefinition[] Dashboards { get; set; } = Array.Empty<ManifestDashboardDefinition>();
@@ -1,13 +1,15 @@
using System.Collections.Generic;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Models.ContentEditing
{
/// <summary>
/// Represents a content app definition.
/// </summary>
public interface IContentAppDefinition
public interface IContentAppFactory
{
/// <summary>
/// Gets the content app for an object.
+2 -1
View File
@@ -336,6 +336,7 @@
<Compile Include="Manifest\ContentAppDefinitionConverter.cs" />
<Compile Include="Manifest\DashboardAccessRuleConverter.cs" />
<Compile Include="Manifest\ManifestContentAppDefinition.cs" />
<Compile Include="Manifest\ManifestContentAppFactory.cs" />
<Compile Include="Manifest\ManifestDashboardDefinition.cs" />
<Compile Include="Migrations\IncompleteMigrationExpressionException.cs" />
<Compile Include="Migrations\MigrationBase_Extra.cs" />
@@ -380,7 +381,7 @@
<Compile Include="Models\ConsentExtensions.cs" />
<Compile Include="Models\ConsentState.cs" />
<Compile Include="Models\ContentEditing\ContentApp.cs" />
<Compile Include="Models\ContentEditing\IContentAppDefinition.cs" />
<Compile Include="Models\ContentEditing\IContentAppFactory.cs" />
<Compile Include="Models\ContentSchedule.cs" />
<Compile Include="Models\ContentScheduleAction.cs" />
<Compile Include="Models\ContentScheduleCollection.cs" />
@@ -67,7 +67,8 @@ namespace Umbraco.Tests.Manifest
private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups)
{
var definition = JsonConvert.DeserializeObject<ManifestContentAppDefinition>("{" + (show.Length == 0 ? "" : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}");
var app = definition.GetContentAppFor(source, groups);
var factory = new ManifestContentAppFactory(definition);
var app = factory.GetContentAppFor(source, groups);
if (expected)
Assert.IsNotNull(app);
else
@@ -5,15 +5,17 @@ using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.ContentApps
{
public class ContentAppDefinitionCollection : BuilderCollectionBase<IContentAppDefinition>
public class ContentAppDefinitionCollection : BuilderCollectionBase<IContentAppFactory>
{
private readonly ILogger _logger;
private readonly IContentAppFactory _factory;
public ContentAppDefinitionCollection(IEnumerable<IContentAppDefinition> items, ILogger logger)
public ContentAppDefinitionCollection(IEnumerable<IContentAppFactory> items, ILogger logger)
: base(items)
{
_logger = logger;
@@ -32,7 +34,10 @@ namespace Umbraco.Web.ContentApps
public IEnumerable<ContentApp> GetContentAppsFor(object o, IEnumerable<IReadOnlyUserGroup> userGroups=null)
{
var roles = GetCurrentUserGroups();
var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList();
var apps = Enumerable.Empty<ContentApp>();// this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList();
var aliases = new HashSet<string>();
List<string> dups = null;
@@ -5,14 +5,16 @@ using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Services;
namespace Umbraco.Web.ContentApps
{
public class ContentAppDefinitionCollectionBuilder : OrderedCollectionBuilderBase<ContentAppDefinitionCollectionBuilder, ContentAppDefinitionCollection, IContentAppDefinition>
public class ContentAppDefinitionCollectionBuilder : OrderedCollectionBuilderBase<ContentAppDefinitionCollectionBuilder, ContentAppDefinitionCollection, IContentAppFactory>
{
public ContentAppDefinitionCollectionBuilder(IServiceContainer container)
: base(container)
{ }
{
}
protected override ContentAppDefinitionCollectionBuilder This => this;
@@ -25,14 +27,14 @@ namespace Umbraco.Web.ContentApps
return new ContentAppDefinitionCollection(CreateItems(), logger);
}
protected override IEnumerable<IContentAppDefinition> CreateItems(params object[] args)
protected override IEnumerable<IContentAppFactory> CreateItems(params object[] args)
{
// get the manifest parser just-in-time - injecting it in the ctor would mean that
// simply getting the builder in order to configure the collection, would require
// its dependencies too, and that can create cycles or other oddities
var manifestParser = Container.GetInstance<ManifestParser>();
return base.CreateItems(args).Concat(manifestParser.Manifest.ContentApps);
return base.CreateItems(args).Concat(manifestParser.Manifest.ContentApps.Select(x=>new ManifestContentAppFactory(x)));
}
}
}
@@ -6,7 +6,7 @@ using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.ContentApps
{
internal class ContentEditorContentAppDefinition : IContentAppDefinition
internal class ContentEditorContentAppFactory : IContentAppFactory
{
// see note on ContentApp
private const int Weight = -100;
@@ -6,7 +6,7 @@ using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.ContentApps
{
public class ContentInfoContentAppDefinition : IContentAppDefinition
public class ContentInfoContentAppFactory : IContentAppFactory
{
// see note on ContentApp
private const int Weight = +100;
@@ -9,7 +9,7 @@ using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.ContentApps
{
internal class ListViewContentAppDefinition : IContentAppDefinition
internal class ListViewContentAppFactory : IContentAppFactory
{
// see note on ContentApp
private const int Weight = -666;
@@ -17,7 +17,7 @@ namespace Umbraco.Web.ContentApps
private readonly IDataTypeService _dataTypeService;
private readonly PropertyEditorCollection _propertyEditors;
public ListViewContentAppDefinition(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors)
public ListViewContentAppFactory(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors)
{
_dataTypeService = dataTypeService;
_propertyEditors = propertyEditors;
+1 -1
View File
@@ -234,7 +234,7 @@ namespace Umbraco.Web.Editors
public ContentItemDisplay GetRecycleBin()
{
var apps = new List<ContentApp>();
apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content", Core.Constants.DataTypes.DefaultMembersListView));
apps.Add(ListViewContentAppFactory.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content", Core.Constants.DataTypes.DefaultMembersListView));
apps[0].Active = true;
var display = new ContentItemDisplay
{
+1 -1
View File
@@ -99,7 +99,7 @@ namespace Umbraco.Web.Editors
public MediaItemDisplay GetRecycleBin()
{
var apps = new List<ContentApp>();
apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media", Core.Constants.DataTypes.DefaultMediaListView));
apps.Add(ListViewContentAppFactory.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media", Core.Constants.DataTypes.DefaultMediaListView));
apps[0].Active = true;
var display = new MediaItemDisplay
{
+1 -1
View File
@@ -139,7 +139,7 @@ namespace Umbraco.Web.Editors
var name = foundType != null ? foundType.Name : listName;
var apps = new List<ContentApp>();
apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView));
apps.Add(ListViewContentAppFactory.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView));
apps[0].Active = true;
var display = new MemberListDisplay
@@ -207,9 +207,9 @@ namespace Umbraco.Web.Runtime
// register known content apps
composition.Container.RegisterCollectionBuilder<ContentAppDefinitionCollectionBuilder>()
.Append<ListViewContentAppDefinition>()
.Append<ContentEditorContentAppDefinition>()
.Append<ContentInfoContentAppDefinition>();
.Append<ListViewContentAppFactory>()
.Append<ContentEditorContentAppFactory>()
.Append<ContentInfoContentAppFactory>();
}
internal void Initialize(
+3 -3
View File
@@ -110,6 +110,7 @@
<Compile Include="Cache\ContentCacheRefresher.cs" />
<Compile Include="Cache\UserGroupCacheRefresher.cs" />
<Compile Include="Components\BackOfficeUserAuditEventsComponent.cs" />
<Compile Include="ContentApps\ListViewContentAppFactory.cs" />
<Compile Include="Editors\BackOfficePreviewModel.cs" />
<Compile Include="Logging\WebProfiler.cs" />
<Compile Include="Logging\WebProfilerComponent.cs" />
@@ -155,9 +156,8 @@
<Compile Include="Models\Mapping\MappingOperationOptionsExtensions.cs" />
<Compile Include="ContentApps\ContentAppDefinitionCollection.cs" />
<Compile Include="ContentApps\ContentAppDefinitionCollectionBuilder.cs" />
<Compile Include="ContentApps\ContentEditorContentAppDefinition.cs" />
<Compile Include="ContentApps\ContentInfoContentAppDefinition.cs" />
<Compile Include="ContentApps\ListViewContentAppDefinition.cs" />
<Compile Include="ContentApps\ContentEditorContentAppFactory.cs" />
<Compile Include="ContentApps\ContentInfoContentAppFactory.cs" />
<Compile Include="Models\Mapping\ScheduledPublishDateResolver.cs" />
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />