Skip to content

Instantly share code, notes, and snippets.

@carlwoodhouse
Last active August 30, 2017 15:49
Show Gist options
  • Save carlwoodhouse/5b636a85fa8f240034f27073a5cafbb3 to your computer and use it in GitHub Desktop.
Save carlwoodhouse/5b636a85fa8f240034f27073a5cafbb3 to your computer and use it in GitHub Desktop.
using Orchard.ContentManagement;
using Orchard.DisplayManagement.Descriptors;
using Patient.Framework.DisplayManagement;
namespace Patient.ContentTypes.Clinical.Providers {
public class HubPlacementParseStrategyMatchProvider : IPlacementParseStategyMatchProvider {
public string Key { get { return "Hub"; } }
public bool IsMatch(ShapePlacementContext context, string expression) {
var contentItem = context.Content.ContentItem;
return contentItem.Has<IClincialArticlePart>() && contentItem.As<IClincialArticlePart>().IsTopicHub;
}
}
}
using System;
using Orchard;
using Orchard.DisplayManagement.Descriptors;
namespace Patient.Framework.DisplayManagement {
public interface IPlacementParseStategyMatchProvider : IDependency {
string Key { get; }
bool IsMatch(ShapePlacementContext context, string expression);
}
}
<Placement>
<Place Parts_PageToolsShare="-" />
<Place Parts_Common_Metadata="-" />
<Match DisplayType="Detail">
<Place Parts_PageTools="/ContentMenu:1" />
<Place Parts_PageTools_Readspeaker="/BodyFoot:1" />
<Place Parts_Tabs="-" />
<Place Parts_Title="/ContentHead:0" />
<Place Parts_ClinicalArticle_MetaData="/ContentHead:1" />
<Place Parts_PageTools_PageOptions="-" />
<Place Parts_ClinicalArticle_Introduction="Content:0" />
<Place Parts_ClinicalArticle_SectionNavigation="Content:1" />
<Place Parts_ClinicalArticle_InlineNavigation="Content:1" />
<Place Parts_ClinicalArticle="Content:2" />
<Place Parts_ClinicalArticle_SectionNavigation_Footer="Content:3" />
<Place Parts_ClinicalArticle_References="Content:4" />
<Place Parts_ClinicalArticle_RelatedContent="/ContentFoot:1" />
<Place Parts_ClinicalArticle_Footer="/ContentFoot:2" />
<Place Parts_ClinicalArticle_Related="-" />
<Place Parts_ClinicalArticle_Discussions="-" />
<Place Parts_ClinicalArticle_RelatedArticle_PilNudge="-" />
<Place Parts_ClinicalArticle_RelatedArticle_ProNudge="-" />
<Place Parts_FeedbackForm="-" />
<Place Parts_Taboola="/AfterContentAds:1" />
<Place Parts_ClickBait_Medianet="-" />
<Match Hub="true">
<Place Parts_Title="/ContentHead:0;Alternate=Parts_Title__Hub" />
<Place Parts_ClinicalArticle_MetaData="-" />
<Place Parts_ClinicalArticle_Introduction="Content:1.5" />
<Place Parts_ClinicalArticle_InlineNavigation="-" />
<Place Parts_ClinicalArticle="Content:2" />
<Place Parts_ClinicalArticle_SectionNavigation_Footer="-" />
<Place Parts_ClinicalArticle_References="-" />
<Place Parts_ClinicalArticle_RelatedContent="-" />
<Place Parts_ClinicalArticle_Footer="-" />
</Match>
</Match>
</Placement>
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard.DisplayManagement.Descriptors;
using Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Patient.Framework.DisplayManagement;
namespace Patient.Framework.DisplayManangement {
[OrchardSuppressDependency("Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy.ShapePlacementParsingStrategy")]
public class ShapePlacementParsingStrategy : IShapeTableProvider {
private readonly IExtensionManager _extensionManager;
private readonly ShellDescriptor _shellDescriptor;
private readonly IPlacementFileParser _placementFileParser;
private readonly IEnumerable<IPlacementParseStategyMatchProvider> _parseMatchProviders;
public ShapePlacementParsingStrategy(
IExtensionManager extensionManager,
ShellDescriptor shellDescriptor,
IPlacementFileParser placementFileParser,
IEnumerable<IPlacementParseStategyMatchProvider> parseMatchProviders) {
_extensionManager = extensionManager;
_shellDescriptor = shellDescriptor;
_placementFileParser = placementFileParser;
_parseMatchProviders = parseMatchProviders;
}
public void Discover(ShapeTableBuilder builder) {
var availableFeatures = _extensionManager.AvailableFeatures();
var activeFeatures = availableFeatures.Where(fd => FeatureIsTheme(fd) || FeatureIsEnabled(fd));
var activeExtensions = Once(activeFeatures);
foreach (var extensionDescriptor in activeExtensions) {
foreach (var featureDescriptor in extensionDescriptor.Features.Where(fd => fd.Id == fd.Extension.Id)) {
ProcessFeatureDescriptor(builder, featureDescriptor);
}
}
}
private void ProcessFeatureDescriptor(ShapeTableBuilder builder, FeatureDescriptor featureDescriptor) {
var virtualPath = featureDescriptor.Extension.Location + "/" + featureDescriptor.Extension.Id + "/Placement.info";
var placementFile = _placementFileParser.Parse(virtualPath);
if (placementFile != null) {
ProcessPlacementFile(builder, featureDescriptor, placementFile);
}
}
private void ProcessPlacementFile(ShapeTableBuilder builder, FeatureDescriptor featureDescriptor, PlacementFile placementFile) {
var feature = new Feature { Descriptor = featureDescriptor };
// invert the tree into a list of leaves and the stack
var entries = DrillDownShapeLocations(placementFile.Nodes, Enumerable.Empty<PlacementMatch>());
foreach (var entry in entries) {
var shapeLocation = entry.Item1;
var matches = entry.Item2;
string shapeType;
string differentiator;
GetShapeType(shapeLocation, out shapeType, out differentiator);
Func<ShapePlacementContext, bool> predicate = ctx => true;
if (differentiator != "") {
predicate = ctx => (ctx.Differentiator ?? "") == differentiator;
}
if (matches.Any()) {
predicate = matches.SelectMany(match => match.Terms).Aggregate(predicate, BuildPredicate);
}
var placement = new PlacementInfo();
var segments = shapeLocation.Location.Split(';').Select(s => s.Trim());
foreach (var segment in segments) {
if (!segment.Contains('=')) {
placement.Location = segment;
}
else {
var index = segment.IndexOf('=');
var property = segment.Substring(0, index).ToLower();
var value = segment.Substring(index + 1);
switch (property) {
case "shape":
placement.ShapeType = value;
break;
case "alternate":
placement.Alternates = new[] { value };
break;
case "wrapper":
placement.Wrappers = new[] { value };
break;
}
}
}
builder.Describe(shapeType)
.From(feature)
.Placement(ctx => {
var hit = predicate(ctx);
// generate 'debugging' information to trace which file originated the actual location
if (hit) {
var virtualPath = featureDescriptor.Extension.Location + "/" + featureDescriptor.Extension.Id + "/Placement.info";
ctx.Source = virtualPath;
}
return hit;
}, placement);
}
}
private void GetShapeType(PlacementShapeLocation shapeLocation, out string shapeType, out string differentiator) {
differentiator = "";
shapeType = shapeLocation.ShapeType;
var separatorLengh = 2;
var separatorIndex = shapeType.LastIndexOf("__");
var dashIndex = shapeType.LastIndexOf('-');
if (dashIndex > separatorIndex) {
separatorIndex = dashIndex;
separatorLengh = 1;
}
if (separatorIndex > 0 && separatorIndex < shapeType.Length - separatorLengh) {
differentiator = shapeType.Substring(separatorIndex + separatorLengh);
shapeType = shapeType.Substring(0, separatorIndex);
}
}
private Func<ShapePlacementContext, bool> BuildPredicate(Func<ShapePlacementContext, bool> predicate, KeyValuePair<string, string> term) {
var expression = term.Value;
switch (term.Key) {
case "ContentPart":
return ctx => ctx.Content != null
&& ctx.Content.ContentItem.Parts.Any(part => part.PartDefinition.Name == expression)
&& predicate(ctx);
case "ContentType":
if (expression.EndsWith("*")) {
var prefix = expression.Substring(0, expression.Length - 1);
return ctx => ((ctx.ContentType ?? "").StartsWith(prefix) || (ctx.Stereotype ?? "").StartsWith(prefix)) && predicate(ctx);
}
return ctx => ((ctx.ContentType == expression) || (ctx.Stereotype == expression)) && predicate(ctx);
case "DisplayType":
if (expression.EndsWith("*")) {
var prefix = expression.Substring(0, expression.Length - 1);
return ctx => (ctx.DisplayType ?? "").StartsWith(prefix) && predicate(ctx);
}
return ctx => (ctx.DisplayType == expression) && predicate(ctx);
case "Path":
var normalizedPath = VirtualPathUtility.IsAbsolute(expression)
? VirtualPathUtility.ToAppRelative(expression)
: VirtualPathUtility.Combine("~/", expression);
if (normalizedPath.EndsWith("*")) {
var prefix = normalizedPath.Substring(0, normalizedPath.Length - 1);
return ctx => VirtualPathUtility.ToAppRelative(String.IsNullOrEmpty(ctx.Path) ? "/" : ctx.Path).StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && predicate(ctx);
}
normalizedPath = VirtualPathUtility.AppendTrailingSlash(normalizedPath);
return ctx => (ctx.Path.Equals(normalizedPath, StringComparison.OrdinalIgnoreCase)) && predicate(ctx);
}
if (_parseMatchProviders != null) {
var providersForTerm = _parseMatchProviders.Where(x => x.Key.Equals(term.Key));
if (providersForTerm.Any()) {
return ctx => providersForTerm.Any(x => x.IsMatch(ctx, expression)) && predicate(ctx);
}
}
return predicate;
}
private static IEnumerable<Tuple<PlacementShapeLocation, IEnumerable<PlacementMatch>>> DrillDownShapeLocations(
IEnumerable<PlacementNode> nodes,
IEnumerable<PlacementMatch> path) {
// return shape locations nodes in this place
foreach (var placementShapeLocation in nodes.OfType<PlacementShapeLocation>()) {
yield return new Tuple<PlacementShapeLocation, IEnumerable<PlacementMatch>>(placementShapeLocation, path);
}
// recurse down into match nodes
foreach (var placementMatch in nodes.OfType<PlacementMatch>()) {
foreach (var findShapeLocation in DrillDownShapeLocations(placementMatch.Nodes, path.Concat(new[] { placementMatch }))) {
yield return findShapeLocation;
}
}
}
private bool FeatureIsTheme(FeatureDescriptor fd) {
return DefaultExtensionTypes.IsTheme(fd.Extension.ExtensionType);
}
private bool FeatureIsEnabled(FeatureDescriptor fd) {
return _shellDescriptor.Features.Any(sf => sf.Name == fd.Id);
}
private static IEnumerable<ExtensionDescriptor> Once(IEnumerable<FeatureDescriptor> featureDescriptors) {
var once = new ConcurrentDictionary<string, object>();
return featureDescriptors.Select(fd => fd.Extension).Where(ed => once.TryAdd(ed.Id, null)).ToList();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment