Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Placement match providers #2864

Merged
merged 27 commits into from
Dec 20, 2018
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8896aa5
corrected typo
BuzzKeith Jan 24, 2018
8cea6ac
ported placement match providers from O1
BuzzKeith Jan 24, 2018
1d1c270
renamed matches to match as not an array
BuzzKeith Jan 24, 2018
98f1041
switch match providers to accept JToken rather than string expression
BuzzKeith Jan 31, 2018
841d85f
tidied up match providers
BuzzKeith Feb 27, 2018
257c581
updated read me
BuzzKeith Feb 27, 2018
2c0bf4c
Merge branch 'placement_matches_dynamic' into placement_matches_dev
BuzzKeith Feb 27, 2018
3b47d2c
Merge branch 'dev' into placement_matches_dev
BuzzKeith Feb 28, 2018
8ce7a00
update path property due to dev changes. Appears to return incorrect…
BuzzKeith Mar 6, 2018
6edc550
Addition of placement match providers
BuzzKeith Dec 12, 2018
ee80832
removed unnecessary debug code
BuzzKeith Dec 13, 2018
133548b
removed match node and elevated match providers
BuzzKeith Dec 18, 2018
62976f7
updated filter providers to accept string or array of strings
BuzzKeith Dec 18, 2018
371885c
readme update
BuzzKeith Dec 18, 2018
02f9196
renamed matchproviders to be consistent with oc
BuzzKeith Dec 18, 2018
18f77f9
contentItem already null checked
BuzzKeith Dec 18, 2018
33e79a5
rename of interface in docs
BuzzKeith Dec 18, 2018
5df1d0c
updated naming
BuzzKeith Dec 18, 2018
3caab4d
renamed file
BuzzKeith Dec 18, 2018
5f40364
added path filet provider
BuzzKeith Dec 18, 2018
a0f23e7
added path filter to readme
BuzzKeith Dec 18, 2018
d9b41ea
removed commented code from O1
BuzzKeith Dec 18, 2018
d5677a0
Merge branch 'placement_matches_dec_dev' into Placement_Match_Providers
BuzzKeith Dec 18, 2018
9a67d52
renamed file
BuzzKeith Dec 18, 2018
124cab2
tidied path normalisation
BuzzKeith Dec 19, 2018
294eb40
moved content filters form OrchardCore.Contents to OrchardCore.Conten…
BuzzKeith Dec 19, 2018
bba33a2
updated PlacementFile to switch DisplayType from using kebab to camel…
BuzzKeith Dec 19, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OrchardCore.ContentManagement;
using OrchardCore.DisplayManagement.Descriptors;
using OrchardCore.DisplayManagement.Descriptors.ShapePlacementStrategy;
using OrchardCore.DisplayManagement.Shapes;

namespace OrchardCore.Contents.Placement
{
public enum MatchType
{
Any,
All
}

public class ContentPartPlacementParseMatchOptions
{
[JsonProperty(PropertyName = "parts")]
public IEnumerable<string> Parts { get; set; }

[JsonProperty(PropertyName = "matchType")]
public MatchType MatchType { get; set; }
}

public class ContentPartPlacementParseMatchProvider : ContentPlacementParseMatchProviderBase, IPlacementParseMatchProvider
{
public string Key { get { return "contentPart"; } }

public bool Match(ShapePlacementContext context, JToken expression)
{
var contentItem = GetContent(context);
if (contentItem == null)
{
return false;
}

var options = expression.ToObject<ContentPartPlacementParseMatchOptions>();

return options.MatchType == MatchType.All ? options.Parts.All(p => contentItem.Has(p)) : options.Parts.Any(p => contentItem.Has(p));
}
}

public class ContentTypePlacementParseMatchProvider : ContentPlacementParseMatchProviderBase, IPlacementParseMatchProvider
{
public string Key { get { return "contentType"; } }

public bool Match(ShapePlacementContext context, JToken expression)
{
var contentItem = GetContent(context);
if (contentItem == null)
{
return false;
}

var contentTypes = expression.ToObject<IEnumerable<string>>();

return contentTypes.Any(ct =>
{
if (ct.EndsWith("*"))
{
var prefix = ct.Substring(0, ct.Length - 1);

return (contentItem?.ContentType ?? "").StartsWith(prefix);// || (context.Stereotype ?? "").StartsWith(prefix);
}

return contentItem.ContentType == ct;// || context.Stereotype == expression;
});
}
}

public class ContentPlacementParseMatchProviderBase
{
protected bool HasContent(ShapePlacementContext context)
{
var shape = context.ZoneShape as Shape;
return shape != null && shape.Properties["ContentItem"] != null;
}

protected ContentItem GetContent(ShapePlacementContext context)
{
if (!HasContent(context))
{
return null;
}

var shape = context.ZoneShape as Shape;
return shape.Properties["ContentItem"] as ContentItem;
}
}
}
6 changes: 6 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Contents/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using OrchardCore.Contents.Indexing;
using OrchardCore.Contents.Liquid;
using OrchardCore.Contents.Models;
using OrchardCore.Contents.Placement;
using OrchardCore.Contents.Recipes;
using OrchardCore.Contents.Services;
using OrchardCore.Contents.TagHelpers;
Expand All @@ -21,6 +22,7 @@
using OrchardCore.Deployment;
using OrchardCore.DisplayManagement.Descriptors;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Descriptors.ShapePlacementStrategy;
using OrchardCore.Entities;
using OrchardCore.Navigation;
using OrchardCore.Feeds;
Expand Down Expand Up @@ -66,7 +68,11 @@ public override void ConfigureServices(IServiceCollection services)
// TODO: Move to feature
services.AddScoped<IFeedItemBuilder, CommonFeedItemBuilder>();


services.AddTagHelpers<ContentLinkTagHelper>();

services.AddScoped<IPlacementParseMatchProvider, ContentTypePlacementParseMatchProvider>();
services.AddScoped<IPlacementParseMatchProvider, ContentPartPlacementParseMatchProvider>();
}

public override void Configure(IApplicationBuilder builder, IRouteBuilder routes, IServiceProvider serviceProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public ShapePlacementContext(string shapeType, string displayType, string differ
ShapeType = shapeType;
DisplayType = displayType;
Differentiator = differentiator;
ZoneShape = ZoneShape;
ZoneShape = zoneShape;
}

public IShape ZoneShape { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json.Linq;

namespace OrchardCore.DisplayManagement.Descriptors.ShapePlacementStrategy
{
public interface IPlacementParseMatchProvider
{
string Key { get; }
bool Match(ShapePlacementContext context, JToken expression);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace OrchardCore.DisplayManagement.Descriptors.ShapePlacementStrategy
{
Expand Down Expand Up @@ -27,5 +28,8 @@ public class PlacementNode
[JsonProperty(PropertyName = "shape")]
public string ShapeType { get; set; }

[JsonProperty(PropertyName = "match")]
public IDictionary<string, JToken> Match { get; set; } = new Dictionary<string, JToken>();

}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OrchardCore.DisplayManagement.Shapes;
using OrchardCore.Environment.Extensions;
using OrchardCore.Environment.Extensions.Features;
Expand All @@ -19,15 +21,18 @@ public class ShapePlacementParsingStrategy : IShapeTableHarvester
private readonly IHostingEnvironment _hostingEnviroment;
private readonly IShellFeaturesManager _shellFeaturesManager;
private readonly ILogger _logger;
private readonly IEnumerable<IPlacementParseMatchProvider> _placementParseMatchProviders;

public ShapePlacementParsingStrategy(
IHostingEnvironment hostingEnviroment,
IShellFeaturesManager shellFeaturesManager,
ILogger<ShapePlacementParsingStrategy> logger)
ILogger<ShapePlacementParsingStrategy> logger,
IEnumerable<IPlacementParseMatchProvider> placementParseMatchProviders)
{
_logger = logger;
_hostingEnviroment = hostingEnviroment;
_shellFeaturesManager = shellFeaturesManager;
_placementParseMatchProviders = placementParseMatchProviders;
}

public void Discover(ShapeTableBuilder builder)
Expand Down Expand Up @@ -70,12 +75,20 @@ private void ProcessPlacementFile(ShapeTableBuilder builder, IFeatureInfo featur
foreach (var entry in placementFile)
{
var shapeType = entry.Key;
var matches = entry.Value;


foreach (var filter in entry.Value)
{
var matches = filter.Match.ToList();

Func<ShapePlacementContext, bool> predicate = ctx => CheckFilter(ctx, filter);

if (matches.Any())
{
predicate = matches.Aggregate(predicate, BuildPredicate);
}

var placement = new PlacementInfo();

placement.Location = filter.Location;
if (filter.Alternates?.Length > 0)
{
Expand All @@ -91,7 +104,7 @@ private void ProcessPlacementFile(ShapeTableBuilder builder, IFeatureInfo featur

builder.Describe(shapeType)
.From(featureDescriptor)
.Placement(ctx => CheckFilter(ctx, filter), placement);
.Placement(ctx => predicate(ctx), placement);
}
}
}
Expand Down Expand Up @@ -145,5 +158,27 @@ public static bool CheckFilter(ShapePlacementContext ctx, PlacementNode filter)
//return ctx => (ctx.Path.Equals(normalizedPath, StringComparison.OrdinalIgnoreCase)) && predicate(ctx);
//}
}

private Func<ShapePlacementContext, bool> BuildPredicate(Func<ShapePlacementContext, bool> predicate,
KeyValuePair<string, JToken> term)
{
return BuildPredicate(predicate, term, _placementParseMatchProviders);
}

public static Func<ShapePlacementContext, bool> BuildPredicate(Func<ShapePlacementContext, bool> predicate,
KeyValuePair<string, JToken> term, IEnumerable<IPlacementParseMatchProvider> placementMatchProviders)
{

if (placementMatchProviders != null)
{
var providersForTerm = placementMatchProviders.Where(x => x.Key.Equals(term.Key));
if (providersForTerm.Any())
{
var expression = term.Value;
return ctx => providersForTerm.Any(x => x.Match(ctx, expression)) && predicate(ctx);
}
}
return predicate;
}
}
}
12 changes: 12 additions & 0 deletions src/OrchardCore/OrchardCore.DisplayManagement/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ Currently you can filter shapes by:
- Their original type, which is the property name of the placement rule, like `TextField`.
- `display-type` (Optional): The display type, like `Summary` and `Detail` for the most common ones.
- `differentiator` (Optional): The differentiator which is used to distinguish shape types that are reused for multiple elements, like field names.
- `match` (Optional): Custom match providers implementing `IPlacementParseMatchProvider`.

For shapes that are built from a content item, you can filter by the following built in match providers:

- `contentType` (Optional): An array of ContentTypes that content item from which the shape was built should match.
- `contentPart` (Optional): An array of ContentParts that content item from which the shape was built should contain (With the option for matching one or all).

Placement information consists of:

Expand All @@ -44,6 +50,12 @@ Placement information consists of:
"display-type": "Detail",
"differentiator": "Article-MyTextField",

"match": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think match is necessary. Every filter can be a the root of the node, like display-type or differentiator.

I would also accept a string, and array of string, of a complex object (for matchType). You could even simplify it by not taking matchType at all, as i assume we'll always want 'any'. I can't find an example that someone would want to match on having multiple parts on the content item.

ContentType can also accept multiple values in this case, right?

Copy link
Contributor Author

@KeithRaven KeithRaven Dec 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of loosing the "match" node in that the matches are doing the same kind of thing (i.e. filtering) as display-type and differentiator as so they would be better together.

That said I personally find it a little confusing having the filters and alterations (place,wrappers, alternates, shape) alongside each other with nothing to distinguish them but I guess it keeps it simpler.

Yes allowing string, array string or object would be a good idea as well.

I can't find an example that someone would want to match on having multiple parts on the content item.

I haven't got a use case at the minute but it feels like there one out there somewhere :) It's not necessary to supply it as it defaults to "Any".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe confusing, but only because you are coming from O1, and as long as it's document it should not be an issue.

"contentType": [ "Page" ],
"contentPart": {
"parts": ["BodyPart","TitlePart"],
"matchType": "Any"
}
"place": "Content",
"alternates": [ "TextField_Title" ],
"wrappers": [ "TextField_Title" ],
Expand Down