--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-10-12 17:46:57 -07:00
34 changed files with 734 additions and 101 deletions

View File

@@ -153,7 +153,68 @@ namespace Orchard.Tests.DisplayManagement.Descriptors {
Assert.That(foo.Displaying.Single(), Is.SameAs(cb3));
Assert.That(foo.Displayed.Single(), Is.SameAs(cb4));
}
[Test]
public void DefaultPlacementIsReturnedByDefault() {
var manager = _container.Resolve<IShapeTableManager>();
var hello = manager.GetShapeTable(null).Descriptors["Hello"];
hello.DefaultPlacement = "Header:5";
var result = hello.Placement(null);
Assert.That(result, Is.EqualTo("Header:5"));
}
[Test]
public void DescribedPlacementIsReturnedIfNotNull() {
_container.Resolve<TestShapeProvider>().Discover =
builder => builder.Describe("Hello")
.Placement(ctx => ctx.DisplayType == "Detail" ? "Main" : null)
.Placement(ctx => ctx.DisplayType == "Summary" ? "" : null);
var manager = _container.Resolve<IShapeTableManager>();
var hello = manager.GetShapeTable(null).Descriptors["Hello"];
var result1 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result2 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result3 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
hello.DefaultPlacement = "Header:5";
var result4 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result5 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result6 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
Assert.That(result1, Is.EqualTo("Main"));
Assert.That(result2, Is.EqualTo(""));
Assert.That(result3, Is.Null);
Assert.That(result4, Is.EqualTo("Main"));
Assert.That(result5, Is.EqualTo(""));
Assert.That(result6, Is.EqualTo("Header:5"));
}
[Test]
public void TwoArgumentVariationDoesSameThing() {
_container.Resolve<TestShapeProvider>().Discover =
builder => builder.Describe("Hello")
.Placement(ctx => ctx.DisplayType == "Detail", "Main")
.Placement(ctx => ctx.DisplayType == "Summary", "");
var manager = _container.Resolve<IShapeTableManager>();
var hello = manager.GetShapeTable(null).Descriptors["Hello"];
var result1 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result2 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result3 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
hello.DefaultPlacement = "Header:5";
var result4 = hello.Placement(new ShapePlacementContext { DisplayType = "Detail" });
var result5 = hello.Placement(new ShapePlacementContext { DisplayType = "Summary" });
var result6 = hello.Placement(new ShapePlacementContext { DisplayType = "Tile" });
Assert.That(result1, Is.EqualTo("Main"));
Assert.That(result2, Is.EqualTo(""));
Assert.That(result3, Is.Null);
Assert.That(result4, Is.EqualTo("Main"));
Assert.That(result5, Is.EqualTo(""));
Assert.That(result6, Is.EqualTo("Header:5"));
}
[Test]
public void OnlyShapesFromTheGivenThemeAreProvided() {
_container.Resolve<TestShapeProvider>();
@@ -182,5 +243,7 @@ namespace Orchard.Tests.DisplayManagement.Descriptors {
Assert.IsTrue(table.Bindings.ContainsKey("OverriddenShape"));
Assert.AreEqual("DerivedTheme", table.Descriptors["OverriddenShape"].BindingSource);
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.IO;
using Orchard.Caching;
using Orchard.FileSystems.WebSite;
namespace Orchard.Tests.DisplayManagement.Descriptors {
public class InMemoryWebSiteFolder : IWebSiteFolder {
public InMemoryWebSiteFolder() {
Contents = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public IDictionary<string, string> Contents { get; set; }
public IEnumerable<string> ListDirectories(string virtualPath) {
throw new NotImplementedException();
}
public IEnumerable<string> ListFiles(string virtualPath, bool recursive) {
throw new NotImplementedException();
}
public bool FileExists(string virtualPath) {
throw new NotImplementedException();
}
public string ReadFile(string virtualPath) {
string result;
return Contents.TryGetValue(virtualPath, out result) ? result : null;
}
public string ReadFile(string virtualPath, bool actualContent) {
throw new NotImplementedException();
}
public void CopyFileTo(string virtualPath, Stream destination) {
throw new NotImplementedException();
}
public void CopyFileTo(string virtualPath, Stream destination, bool actualContent) {
throw new NotImplementedException();
}
public IVolatileToken WhenPathChanges(string virtualPath) {
return new Token { IsCurrent = true };
}
public class Token : IVolatileToken {
public bool IsCurrent { get; set; }
}
}
}

View File

@@ -0,0 +1,92 @@
using System.Linq;
using System.Text;
using Autofac;
using NUnit.Framework;
using Orchard.Caching;
using Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy;
using Orchard.FileSystems.WebSite;
using Orchard.Tests.Stubs;
namespace Orchard.Tests.DisplayManagement.Descriptors {
[TestFixture]
public class PlacementFileParserTests : ContainerTestBase {
private IPlacementFileParser _parser;
private InMemoryWebSiteFolder _folder;
protected override void Register(Autofac.ContainerBuilder builder) {
builder.RegisterType<PlacementFileParser>().As<IPlacementFileParser>();
builder.RegisterType<StubCacheManager>().As<ICacheManager>();
builder.RegisterType<InMemoryWebSiteFolder>().As<IWebSiteFolder>()
.As<InMemoryWebSiteFolder>().InstancePerLifetimeScope();
}
protected override void Resolve(IContainer container) {
_parser = container.Resolve<IPlacementFileParser>();
_folder = container.Resolve<InMemoryWebSiteFolder>();
}
[Test]
public void ParsingMissingFileIsNull() {
var result = _parser.Parse("~/hello.xml");
Assert.That(result, Is.Null);
}
[Test]
public void ParsingEmptyFileAsNothing() {
_folder.Contents["~/hello.xml"] = "<Placement/>";
var result = _parser.Parse("~/hello.xml");
Assert.That(result, Is.Not.Null);
Assert.That(result.Nodes, Is.Not.Null);
Assert.That(result.Nodes.Count(), Is.EqualTo(0));
}
[Test]
public void ItemsComeBackAsPlacementNodes() {
_folder.Contents["~/hello.xml"] = @"
<Placement>
<Match ContentType=""BlogPost""/>
<Match ContentType=""Page""/>
</Placement>
";
var result = _parser.Parse("~/hello.xml");
Assert.That(result, Is.Not.Null);
Assert.That(result.Nodes, Is.Not.Null);
Assert.That(result.Nodes.Count(), Is.EqualTo(2));
}
[Test]
public void NestedItemsComeBackAsNestedNodes() {
_folder.Contents["~/hello.xml"] = @"
<Placement>
<Match ContentType=""BlogPost"">
<Match DisplayType=""Detail""/>
</Match>
<Match ContentType=""Page""/>
</Placement>
";
var result = _parser.Parse("~/hello.xml");
Assert.That(result, Is.Not.Null);
Assert.That(result.Nodes, Is.Not.Null);
Assert.That(result.Nodes.Count(), Is.EqualTo(2));
Assert.That(result.Nodes.First().Nodes.Count(), Is.EqualTo(1));
Assert.That(result.Nodes.Last().Nodes.Count(), Is.EqualTo(0));
}
[Test]
public void EachPlaceAttributeIsShapeLocation() {
_folder.Contents["~/hello.xml"] = @"
<Place Foo=""Header"" Bar=""Content:after""/>
";
var result = _parser.Parse("~/hello.xml");
Assert.That(result, Is.Not.Null);
Assert.That(result.Nodes, Is.Not.Null);
Assert.That(result.Nodes.Count(), Is.EqualTo(2));
var foo = result.Nodes.OfType<PlacementShapeLocation>().Single(x=>x.ShapeType == "Foo");
var bar = result.Nodes.OfType<PlacementShapeLocation>().Single(x=>x.ShapeType == "Bar");
Assert.That(foo.Location, Is.EqualTo("Header"));
Assert.That(bar.Location, Is.EqualTo("Content:after"));
}
}
}

View File

@@ -216,6 +216,8 @@
<Compile Include="ContainerTestBase.cs" />
<Compile Include="DisplayManagement\Descriptors\BasicShapeTemplateHarvesterTests.cs" />
<Compile Include="DisplayManagement\Descriptors\DefaultShapeTableManagerTests.cs" />
<Compile Include="DisplayManagement\Descriptors\InMemoryWebSiteFolder.cs" />
<Compile Include="DisplayManagement\Descriptors\PlacementFileParserTests.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeAttributeBindingStrategyTests.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeTemplateBindingStrategyTests.cs" />
<Compile Include="DisplayManagement\ShapeFactoryTests.cs" />

View File

@@ -34,19 +34,17 @@ namespace Orchard.Core.Common.Drivers {
}
protected override DriverResult Display(BodyPart part, string displayType, dynamic shapeHelper) {
var bodyText = _htmlFilters.Aggregate(part.Text, (text, filter) => filter.ProcessContent(text));
var body = shapeHelper.Parts_Common_Body(ContentPart: part, Html: new HtmlString(bodyText));
if (!string.IsNullOrWhiteSpace(displayType))
body.Metadata.Type = string.Format("{0}.{1}", body.Metadata.Type, displayType);
var location = part.GetLocation(displayType);
//return Combined(
// Services.Authorizer.Authorize(Permissions.ChangeOwner) ? ContentPartTemplate(model, "Parts/Common.Body.ManageWrapperPre").LongestMatch(displayType, "SummaryAdmin").Location(location) : null,
// Services.Authorizer.Authorize(Permissions.ChangeOwner) ? ContentPartTemplate(model, "Parts/Common.Body.Manage").LongestMatch(displayType, "SummaryAdmin").Location(location) : null,
// ContentPartTemplate(model, TemplateName, Prefix).LongestMatch(displayType, "Summary", "SummaryAdmin").Location(location),
// Services.Authorizer.Authorize(Permissions.ChangeOwner) ? ContentPartTemplate(model, "Parts/Common.Body.ManageWrapperPost").LongestMatch(displayType, "SummaryAdmin").Location(location) : null);
return ContentShape(body).Location(location);
return Combined(
ContentShape("Parts_Common_Body", displayType == "Detail" ? "Content" : null, () => {
var bodyText = _htmlFilters.Aggregate(part.Text, (text, filter) => filter.ProcessContent(text));
return shapeHelper.Parts_Common_Body(ContentPart: part, Html: new HtmlString(bodyText));
}),
ContentShape("Parts_Common_Body_Summary", displayType == "Summary" ? "Content" : null, () => {
var bodyText = _htmlFilters.Aggregate(part.Text, (text, filter) => filter.ProcessContent(text));
return shapeHelper.Parts_Common_Body_Summary(ContentPart: part, Html: new HtmlString(bodyText));
})
);
}
protected override DriverResult Editor(BodyPart part, dynamic shapeHelper) {

View File

@@ -1,15 +1,27 @@
using Orchard.DisplayManagement.Implementation;
using System;
using Orchard.ContentManagement;
using Orchard.DisplayManagement.Descriptors;
using Orchard.DisplayManagement.Implementation;
using Orchard.UI.Zones;
namespace Orchard.Core.Contents {
public class Shapes : IShapeFactoryEvents {
public void Creating(ShapeCreatingContext creating) {
if (creating.ShapeType.StartsWith("Items_Content"))
creating.Behaviors.Add(new ZoneHoldingBehavior(name => creating.New.ContentZone()));
public class Shapes : IShapeTableProvider {
public void Discover(ShapeTableBuilder builder) {
builder.Describe("Items_Content")
.OnCreating(creating => creating.Behaviors.Add(new ZoneHoldingBehavior(name => ContentZone(creating, name))))
.OnDisplaying(displaying => {
ContentItem contentItem = displaying.Shape.ContentItem;
if (contentItem != null) {
displaying.ShapeMetadata.Alternates.Add("Items_Content__" + contentItem.ContentType);
displaying.ShapeMetadata.Alternates.Add("Items_Content__" + contentItem.Id);
}
});
}
public void Created(ShapeCreatedContext created) {
private static object ContentZone(ShapeCreatingContext creating, string name) {
var zone = creating.New.ContentZone();
zone.ZoneName = name;
return zone;
}
}
}

View File

@@ -3,6 +3,6 @@
@Display(Model.Header)
</header>
<section>
@Display(Model.Primary)
@Display(Model.Content)
</section>
</article>

View File

@@ -392,7 +392,7 @@
<Content Include="Shapes\Views\MenuItem.cshtml" />
<Content Include="Shapes\Views\Web.config" />
<Content Include="Dashboard\Views\Helper\Index.cshtml" />
<Content Include="Routable\Views\Parts\Routable.RoutePart.cshtml" />
<None Include="Routable\Views\Parts\RoutableTitle.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

View File

@@ -45,11 +45,7 @@ namespace Orchard.Core.Routable.Drivers {
}
protected override DriverResult Display(RoutePart part, string displayType, dynamic shapeHelper) {
var routePart = shapeHelper.Parts_Routable_RoutePart(ContentPart: part, Title: part.Title);
if (!string.IsNullOrWhiteSpace(displayType))
routePart.Metadata.Type = string.Format("{0}.{1}", routePart.Metadata.Type, displayType);
var location = part.GetLocation(displayType, "Header", "5");
return ContentShape(routePart).Location(location);
return ContentShape("Parts_RoutableTitle", "Header:5", () => shapeHelper.Parts_RoutableTitle(ContentPart: part, Title: part.Title));
}
protected override DriverResult Editor(RoutePart part, dynamic shapeHelper) {
@@ -80,7 +76,7 @@ namespace Orchard.Core.Routable.Drivers {
}
protected override DriverResult Editor(RoutePart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new RoutableEditorViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
part.Title = model.Title;

View File

@@ -215,6 +215,7 @@ namespace Orchard.Setup.Services {
contentDefinitionManager.AlterPartDefinition("BodyPart", cfg => cfg
.WithSetting("BodyPartSettings.FlavorDefault", BodyPartSettings.FlavorDefaultDefault));
// add a layer for the homepage
var layer = contentManager.Create("Layer");
layer.As<LayerPart>().Name = "TheHomepage";

View File

@@ -115,6 +115,7 @@
<None Include="Views\EditorTemplates\Parts\Widgets.WidgetPart.cshtml" />
<None Include="Views\EditorTemplates\Parts\Widgets.LayerPart.cshtml" />
<None Include="Views\EditorTemplates\Parts\Widgets.WidgetBagPart.cshtml" />
<Content Include="Views\Items_Widget.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

View File

@@ -0,0 +1,4 @@
TheWidget
@Display(Model.Content)
@Display(Model.Primary)
@Display(Model.Body)

View File

@@ -125,6 +125,7 @@
<Content Include="Default.aspx" />
<Content Include="Global.asax" />
<Content Include="Refresh.html" />
<None Include="Themes\Classic\Placement.info" />
<Content Include="Themes\Contoso\Styles\search.css" />
<Content Include="Themes\Contoso\Zones\Footer.html" />
<Content Include="Themes\Corporate\Views\DisplayTemplates\Parts\Blogs.BlogPost.Metadata.ascx" />

View File

@@ -0,0 +1,21 @@
<Placement>
<!--
note - commas, and wildcards, do NOT work, but would be very nice
<Match ContentType="Page">
<Match DisplayType="Detail,Summary*" >
<Place
Parts_RoutableTitle="Header"
Parts_BodyText="Content:8"
/>
</Match>
</Match>
-->
<Match ContentType="Page" DisplayType="Detail">
<Place
Parts_RoutableTitle="Content:5.b"
Parts_Common_Body="Header:after" />
</Match>
</Placement>

View File

@@ -141,6 +141,21 @@ p {
margin-top:12px;
}
/*Two Sidebars on */
.has-sidebars #content {
background-color:#e8e8e8;
width: 600px;
}
.has-sidebars .sidebar {
background-color:#f6f6f6;
width: 180px;
}
.has-sidebars .primary, .has-sidebars .secondary, .has-sidebars #content {
float:left;
}
/* Navigation */

View File

@@ -85,7 +85,6 @@
<aside class="aside first">
@Zone(Model.AsideFirst)
</aside>
}
@* Create a zone and only show it on the home page. *@

View File

@@ -5,6 +5,8 @@
<add path="*.aspx" verb="*" type="System.Web.HttpNotFoundHandler"/>
<add path="*.ascx" verb="*" type="System.Web.HttpNotFoundHandler"/>
<add path="*.master" verb="*" type="System.Web.HttpNotFoundHandler"/>
<add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
<add path="*.info" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<!--
@@ -29,7 +31,7 @@
<validation validateIntegratedModeConfiguration="false"/>
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*.aspx,*.ascx,*.master" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
<add name="BlockViewHandler" path="*.aspx,*.ascx,*.master,*.cshtml,*.info" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
</handlers>
</system.webServer>
<runtime>

View File

@@ -130,7 +130,7 @@
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
<bindingRedirect oldVersion="2.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Routing;
using Microsoft.CSharp.RuntimeBinder;
using Orchard.ContentManagement.Handlers;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Descriptors;
using Orchard.Logging;
using Orchard.Mvc;
using Orchard.Themes;
namespace Orchard.ContentManagement {
public class DefaultContentDisplay : IContentDisplay {
private readonly Lazy<IEnumerable<IContentHandler>> _handlers;
private readonly IShapeHelperFactory _shapeHelperFactory;
private readonly IShapeTableManager _shapeTableManager;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Lazy<IThemeService> _themeService;
private readonly RequestContext _requestContext;
public DefaultContentDisplay(
Lazy<IEnumerable<IContentHandler>> handlers,
IShapeHelperFactory shapeHelperFactory,
IShapeTableManager shapeTableManager,
IWorkContextAccessor workContextAccessor,
IHttpContextAccessor httpContextAccessor,
Lazy<IThemeService> themeService,
RequestContext requestContext) {
_handlers = handlers;
_shapeHelperFactory = shapeHelperFactory;
_shapeTableManager = shapeTableManager;
_workContextAccessor = workContextAccessor;
_httpContextAccessor = httpContextAccessor;
_themeService = themeService;
_requestContext = requestContext;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
static readonly CallSiteCollection _shapeHelperCalls = new CallSiteCollection(shapeTypeName => Binder.InvokeMember(
CSharpBinderFlags.None,
shapeTypeName,
Enumerable.Empty<Type>(),
null,
new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
public dynamic BuildDisplay(IContent content, string displayType) {
var contentTypeDefinition = content.ContentItem.TypeDefinition;
string stereotype;
if (!contentTypeDefinition.Settings.TryGetValue("Stereotype", out stereotype))
stereotype = "Content";
var shapeTypeName = "Items_" + stereotype;
var shapeDisplayType = string.IsNullOrWhiteSpace(displayType) ? "Detail" : displayType;
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = _shapeHelperCalls.Invoke(shapeHelper, shapeTypeName);
itemShape.ContentItem = content.ContentItem;
itemShape.Metadata.DisplayType = shapeDisplayType;
var context = new BuildDisplayContext(itemShape, content, shapeDisplayType, _shapeHelperFactory);
BindPlacement(context, displayType);
_handlers.Value.Invoke(handler => handler.BuildDisplay(context), Logger);
return context.Shape;
}
public dynamic BuildEditor(IContent content) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new BuildEditorContext(itemShape, content, _shapeHelperFactory);
BindPlacement(context, null);
_handlers.Value.Invoke(handler => handler.BuildEditor(context), Logger);
return context.Shape;
}
public dynamic UpdateEditor(IContent content, IUpdateModel updater) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new UpdateEditorContext(itemShape, content, updater, _shapeHelperFactory);
BindPlacement(context, null);
_handlers.Value.Invoke(handler => handler.UpdateEditor(context), Logger);
return context.Shape;
}
private void BindPlacement(BuildShapeContext context, string displayType) {
context.FindPlacement = (partShapeType, defaultLocation) => {
//var workContext = _workContextAccessor.GetContext();
//var theme = workContext.CurrentTheme;
var theme = _themeService.Value.GetRequestTheme(_requestContext);
var shapeTable = _shapeTableManager.GetShapeTable(theme.ThemeName);
ShapeDescriptor descriptor;
if (shapeTable.Descriptors.TryGetValue(partShapeType, out descriptor)) {
var placementContext = new ShapePlacementContext {
ContentType = context.ContentItem.ContentType,
DisplayType = displayType
};
var location = descriptor.Placement(placementContext);
return location ?? defaultLocation;
}
return defaultLocation;
};
}
}
}

View File

@@ -4,14 +4,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Autofac;
using Microsoft.CSharp.RuntimeBinder;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.Records;
using Orchard.Data;
using Orchard.DisplayManagement;
using Orchard.Indexing;
using Orchard.Logging;
@@ -23,7 +21,7 @@ namespace Orchard.ContentManagement {
private readonly IRepository<ContentItemVersionRecord> _contentItemVersionRepository;
private readonly IContentDefinitionManager _contentDefinitionManager;
private readonly Func<IContentManagerSession> _contentManagerSession;
private readonly IShapeHelperFactory _shapeHelperFactory;
private readonly Lazy<IContentDisplay> _contentDisplay;
public DefaultContentManager(
IComponentContext context,
@@ -32,14 +30,14 @@ namespace Orchard.ContentManagement {
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
Func<IContentManagerSession> contentManagerSession,
IShapeHelperFactory shapeHelperFactory) {
Lazy<IContentDisplay> contentDisplay) {
_context = context;
_contentTypeRepository = contentTypeRepository;
_contentItemRepository = contentItemRepository;
_contentItemVersionRepository = contentItemVersionRepository;
_contentDefinitionManager = contentDefinitionManager;
_contentManagerSession = contentManagerSession;
_shapeHelperFactory = shapeHelperFactory;
_contentDisplay = contentDisplay;
Logger = NullLogger.Instance;
}
@@ -370,54 +368,17 @@ namespace Orchard.ContentManagement {
return context.Metadata;
}
static readonly CallSiteCollection _shapeHelperCalls = new CallSiteCollection(shapeTypeName => Binder.InvokeMember(
CSharpBinderFlags.None,
shapeTypeName,
Enumerable.Empty<Type>(),
null,
new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
public dynamic BuildDisplay(dynamic content, string displayType = "") {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var shapeTypeName = string.IsNullOrEmpty(displayType) ? "Items_Content" : ("Items_Content_" + displayType);
var itemShape = _shapeHelperCalls.Invoke(shapeHelper, shapeTypeName);
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new BuildDisplayContext(itemShape, content, displayType, _shapeHelperFactory);
Handlers.Invoke(handler => handler.BuildDisplay(context), Logger);
return context.Shape;
public dynamic BuildDisplay(IContent content, string displayType = "") {
return _contentDisplay.Value.BuildDisplay(content, displayType);
}
public dynamic BuildEditor(dynamic content) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new BuildEditorContext(itemShape, content, _shapeHelperFactory);
Handlers.Invoke(handler => handler.BuildEditor(context), Logger);
return context.Shape;
public dynamic BuildEditor(IContent content) {
return _contentDisplay.Value.BuildEditor(content);
}
public dynamic UpdateEditor(dynamic content, IUpdateModel updater) {
var shapeHelper = _shapeHelperFactory.CreateHelper();
var itemShape = shapeHelper.Items_Content_Edit();
IContent iContent = content;
if (iContent != null)
itemShape.ContentItem = iContent.ContentItem;
var context = new UpdateEditorContext(itemShape, content, updater, _shapeHelperFactory);
Handlers.Invoke(handler => handler.UpdateEditor(context), Logger);
return context.Shape;
public dynamic UpdateEditor(IContent content, IUpdateModel updater) {
return _contentDisplay.Value.UpdateEditor(content, updater);
}
public IContentQuery<ContentItem> Query() {

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData;
using Orchard.DisplayManagement;
namespace Orchard.ContentManagement.Drivers {
public abstract class ContentPartDriver<TContent> : IContentPartDriver where TContent : ContentPart, new() {
@@ -27,8 +28,21 @@ namespace Orchard.ContentManagement.Drivers {
protected virtual DriverResult Editor(TContent part, dynamic shapeHelper) { return null; }
protected virtual DriverResult Editor(TContent part, IUpdateModel updater, dynamic shapeHelper) { return null; }
public ContentShapeResult ContentShape(dynamic shape) {
return new ContentShapeResult(shape, Prefix).Location(Zone);
[Obsolete("Provided while transitioning to factory variations")]
public ContentShapeResult ContentShape(IShape shape) {
return ContentShapeImplementation(shape.Metadata.Type, Zone, () => shape);
}
public ContentShapeResult ContentShape(string shapeType, Func<dynamic> factory) {
return ContentShapeImplementation(shapeType, null, factory);
}
public ContentShapeResult ContentShape(string shapeType, string defaultLocation, Func<dynamic> factory) {
return ContentShapeImplementation(shapeType, defaultLocation, factory);
}
private ContentShapeResult ContentShapeImplementation(string shapeType, string defaultLocation, Func<object> factory) {
return new ContentShapeResult(shapeType, Prefix, factory).Location(defaultLocation);
}
[Obsolete]

View File

@@ -1,37 +1,57 @@
using Orchard.ContentManagement.Handlers;
using System;
using Orchard.ContentManagement.Handlers;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Shapes;
namespace Orchard.ContentManagement.Drivers {
public class ContentShapeResult : DriverResult {
public dynamic Shape { get; set; }
public string Prefix { get; set; }
public string Zone { get; set; }
public string Position { get; set; }
private string _defaultLocation;
private readonly string _shapeType;
private readonly string _prefix;
private readonly Func<dynamic> _shapeBuilder;
public ContentShapeResult(dynamic shape, string prefix) {
Shape = shape;
Prefix = prefix;
public ContentShapeResult(string shapeType, string prefix, Func<dynamic> shapeBuilder) {
_shapeType = shapeType;
_prefix = prefix;
_shapeBuilder = shapeBuilder;
}
public override void Apply(BuildDisplayContext context) {
context.Shape.Zones[Zone].Add(Shape, Position);
ApplyImplementation(context, context.DisplayType);
}
public override void Apply(BuildEditorContext context) {
IShape iShape = Shape;
if (iShape != null )
Shape.Metadata.Prefix = Prefix;
context.Shape.Zones[Zone].Add(Shape, Position);
ApplyImplementation(context, null);
}
private void ApplyImplementation(BuildShapeContext context, string displayType) {
var location = context.FindPlacement(_shapeType, _defaultLocation);
if (string.IsNullOrEmpty(location) || location == "-")
return;
dynamic parentShape = context.Shape;
IShape contentShape = _shapeBuilder();
contentShape.Metadata.Prefix = _prefix;
contentShape.Metadata.DisplayType = displayType;
var delimiterIndex = location.IndexOf(':');
if (delimiterIndex < 0) {
parentShape.Zones[location].Add(contentShape);
}
else {
var zoneName = location.Substring(0, delimiterIndex);
var position = location.Substring(delimiterIndex + 1);
parentShape.Zones[zoneName].Add(contentShape, position);
}
}
public ContentShapeResult Location(string zone) {
Zone = zone;
_defaultLocation = zone;
return this;
}
public ContentShapeResult Location(string zone, string position) {
Zone = zone;
Position = position;
_defaultLocation = zone + ":" + position;
return this;
}

View File

@@ -1,3 +1,4 @@
using System;
using Orchard.DisplayManagement;
namespace Orchard.ContentManagement.Handlers {
@@ -6,10 +7,13 @@ namespace Orchard.ContentManagement.Handlers {
Shape = shape;
ContentItem = content.ContentItem;
New = shapeHelperFactory.CreateHelper();
FindPlacement = (partType, defaultLocation) => defaultLocation;
}
public dynamic Shape { get; private set; }
public ContentItem ContentItem { get; private set; }
public dynamic New { get; private set; }
public Func<string, string, string> FindPlacement { get; set; }
}
}

View File

@@ -1,5 +1,10 @@
namespace Orchard.ContentManagement {
public interface IContent {
ContentItem ContentItem { get; }
/// <summary>
/// The ContentItem's identifier.
/// </summary>
int Id { get; }
}
}

View File

@@ -26,9 +26,15 @@ namespace Orchard.ContentManagement {
ContentItemMetadata GetItemMetadata(IContent contentItem);
dynamic BuildDisplay(dynamic content, string displayType = "");
dynamic BuildEditor(dynamic content);
dynamic UpdateEditor(dynamic content, IUpdateModel updater);
dynamic BuildDisplay(IContent content, string displayType = "");
dynamic BuildEditor(IContent content);
dynamic UpdateEditor(IContent content, IUpdateModel updater);
}
public interface IContentDisplay : IDependency {
dynamic BuildDisplay(IContent content, string displayType = "");
dynamic BuildEditor(IContent content);
dynamic UpdateEditor(IContent content, IUpdateModel updater);
}
public class VersionOptions {

View File

@@ -73,7 +73,7 @@ namespace Orchard.DisplayManagement.Descriptors {
descriptor.Created = existing.Concat(new[] { action });
});
}
public ShapeAlterationBuilder OnDisplaying(Action<ShapeDisplayingContext> action) {
return Configure(descriptor => {
var existing = descriptor.Displaying ?? Enumerable.Empty<Action<ShapeDisplayingContext>>();
@@ -88,8 +88,28 @@ namespace Orchard.DisplayManagement.Descriptors {
});
}
public ShapeAlterationBuilder Placement(Func<ShapePlacementContext, string> action) {
return Configure(descriptor => {
var next = descriptor.Placement;
descriptor.Placement = ctx => action(ctx) ?? next(ctx);
});
}
public ShapeAlterationBuilder Placement(Func<ShapePlacementContext, bool> predicate, string location) {
return Configure(descriptor => {
var next = descriptor.Placement;
descriptor.Placement = ctx => predicate(ctx) ? location : next(ctx);
});
}
public ShapeAlteration Build() {
return new ShapeAlteration(_shapeType, _feature, _configurations.ToArray());
}
}
}
public class ShapePlacementContext {
public string ContentType { get; set; }
public string DisplayType { get; set; }
}
}

View File

@@ -13,6 +13,7 @@ namespace Orchard.DisplayManagement.Descriptors {
Displayed = Enumerable.Empty<Action<ShapeDisplayedContext>>();
Wrappers = new List<string>();
Bindings = new Dictionary<string, ShapeBinding>();
Placement = ctx => DefaultPlacement;
}
public string ShapeType { get; set; }
@@ -41,6 +42,9 @@ namespace Orchard.DisplayManagement.Descriptors {
public IEnumerable<Action<ShapeDisplayingContext>> Displaying { get; set; }
public IEnumerable<Action<ShapeDisplayedContext>> Displayed { get; set; }
public Func<ShapePlacementContext, string> Placement { get; set; }
public string DefaultPlacement { get; set; }
public IList<string> Wrappers { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
public class PlacementFile : PlacementNode {
}
public class PlacementNode {
public IEnumerable<PlacementNode> Nodes { get; set; }
}
public class PlacementMatch : PlacementNode {
public IDictionary<string, string> Terms { get; set; }
}
public class PlacementShapeLocation : PlacementNode {
public string ShapeType { get; set; }
public string Location { get; set; }
}
}

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Orchard.Caching;
using Orchard.FileSystems.WebSite;
namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
/// <summary>
/// Parses and caches the Placement.info file contents for a given IWebSiteFolder vdir
/// </summary>
public interface IPlacementFileParser : IDependency {
PlacementFile Parse(string virtualPath);
}
public class PlacementFileParser : IPlacementFileParser {
private readonly ICacheManager _cacheManager;
private readonly IWebSiteFolder _webSiteFolder;
public PlacementFileParser(ICacheManager cacheManager, IWebSiteFolder webSiteFolder) {
_cacheManager = cacheManager;
_webSiteFolder = webSiteFolder;
}
public PlacementFile Parse(string virtualPath) {
return _cacheManager.Get(virtualPath, context => {
context.Monitor(_webSiteFolder.WhenPathChanges(virtualPath));
var placementText = _webSiteFolder.ReadFile(virtualPath);
return ParseImplementation(virtualPath, placementText);
});
}
private PlacementFile ParseImplementation(string virtualPath, string placementText) {
if (placementText == null)
return null;
var element = XElement.Parse(placementText);
return new PlacementFile {
Nodes = Accept(element).ToList()
};
}
private IEnumerable<PlacementNode> Accept(XElement element) {
switch (element.Name.LocalName) {
case "Placement":
return AcceptMatch(element);
case "Match":
return AcceptMatch(element);
case "Place":
return AcceptPlace(element);
}
return Enumerable.Empty<PlacementNode>();
}
private IEnumerable<PlacementNode> AcceptMatch(XElement element) {
if (element.HasAttributes == false) {
// Match with no attributes will collapse child results upward
// rather than return an unconditional node
return element.Elements().SelectMany(Accept);
}
// return match node that carries back key/value dictionary of condition,
// and has child rules nested as Nodes
return new[]{new PlacementMatch{
Terms = element.Attributes().ToDictionary(attr=>attr.Name.LocalName, attr=>attr.Value),
Nodes=element.Elements().SelectMany(Accept).ToArray(),
}};
}
private IEnumerable<PlacementShapeLocation> AcceptPlace(XElement element) {
// return attributes as part locations
return element.Attributes().Select(attr => new PlacementShapeLocation {
ShapeType = attr.Name.LocalName,
Location = attr.Value
});
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Descriptor.Models;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
namespace Orchard.DisplayManagement.Descriptors.ShapePlacementStrategy {
/// <summary>
/// This component discovers and announces the shape alterations implied by the contents of the Placement.info files
/// </summary>
public class ShapePlacementParsingStrategy : IShapeTableProvider {
private readonly IExtensionManager _extensionManager;
private readonly ShellDescriptor _shellDescriptor;
private readonly IPlacementFileParser _placementFileParser;
public ShapePlacementParsingStrategy(
IExtensionManager extensionManager,
ShellDescriptor shellDescriptor,
IPlacementFileParser placementFileParser) {
_extensionManager = extensionManager;
_shellDescriptor = shellDescriptor;
_placementFileParser = placementFileParser;
}
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.Name == fd.Extension.Name)) {
ProcessFeatureDescriptor(builder, featureDescriptor);
}
}
}
private void ProcessFeatureDescriptor(ShapeTableBuilder builder, FeatureDescriptor featureDescriptor) {
// The App_Data subfolder is used because it is not served.
// But it's still considered a site-deployable content file, which is why the IWebSiteFolder (not the IAppDataFolder) is appropriate
var virtualPath = featureDescriptor.Extension.Location + "/" + featureDescriptor.Extension.Name + "/App_Data/Placement.xml";
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;
Func<ShapePlacementContext, bool> predicate = ctx => true;
predicate = matches.SelectMany(match=>match.Terms).Aggregate(predicate, BuildPredicate);
builder.Describe(shapeLocation.ShapeType)
.From(feature)
.Placement(predicate, shapeLocation.Location);
}
}
private Func<ShapePlacementContext, bool> BuildPredicate(Func<ShapePlacementContext, bool> predicate, KeyValuePair<string, string> term) {
var expression = term.Value;
switch(term.Key) {
case "ContentType":
return ctx=>ctx.ContentType == expression ? true : predicate(ctx);
case "DisplayType":
return ctx=>ctx.DisplayType == expression ? true : 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 fd.Extension.ExtensionType == "Theme";
}
private bool FeatureIsEnabled(FeatureDescriptor fd) {
return _shellDescriptor.Features.Any(sf => sf.Name == fd.Name);
}
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.Name, null)).ToList();
}
}
}

View File

@@ -9,6 +9,7 @@ namespace Orchard.DisplayManagement.Shapes {
}
public string Type { get; set; }
public string DisplayType { get; set; }
public string Position { get; set; }
public string Prefix { get; set; }
public IList<string> Wrappers { get; set; }

View File

@@ -159,11 +159,14 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ContentManagement\DefaultContentDisplay.cs" />
<Compile Include="ContentManagement\Drivers\ContentShapeResult.cs" />
<Compile Include="ContentManagement\Handlers\BuildShapeContext.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeDescriptor.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeAlteration.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeAlterationBuilder.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapePlacementStrategy\PlacementFileParser.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapePlacementStrategy\ShapePlacementParsingStrategy.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeTable.cs" />
<Compile Include="DisplayManagement\Descriptors\ShapeTableBuilder.cs" />
<Compile Include="DisplayManagement\Implementation\IShapeDisplayEvents.cs" />

View File

@@ -24,6 +24,7 @@ MenuItem
-RouteValues
-Item (clr object)
List: ul|ol + li*
.Items (meta-property, bound to shape children or passed in)
.Id