Adding the ability to select whether a Template (content item) will be rendered on the frontend, admin, or both. Fixes #5579.

This commit is contained in:
Lombiq
2016-11-07 21:41:43 +01:00
committed by Zoltán Lehóczky
parent 68acf2694d
commit 8f2411dbf9
9 changed files with 95 additions and 65 deletions

View File

@@ -11,35 +11,45 @@ using Orchard.Templates.Models;
using Orchard.Templates.Services; using Orchard.Templates.Services;
using Orchard.Templates.ViewModels; using Orchard.Templates.ViewModels;
using Orchard.Utility.Extensions; using Orchard.Utility.Extensions;
using Orchard.Core.Title.Models;
namespace Orchard.Templates.Drivers { namespace Orchard.Templates.Drivers {
public class ShapePartDriver : ContentPartDriver<ShapePart> { public class ShapePartDriver : ContentPartDriver<ShapePart> {
private readonly IEnumerable<ITemplateProcessor> _processors; private readonly IEnumerable<ITemplateProcessor> _processors;
private readonly ITransactionManager _transactions; private readonly ITransactionManager _transactions;
private readonly IContentManager _contentManager;
public ShapePartDriver( public ShapePartDriver(
IEnumerable<ITemplateProcessor> processors, IEnumerable<ITemplateProcessor> processors,
ITransactionManager transactions) { ITransactionManager transactions,
IContentManager contentManager) {
_processors = processors; _processors = processors;
_transactions = transactions; _transactions = transactions;
_contentManager = contentManager;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
} }
Localizer T { get; set; } Localizer T { get; set; }
protected override DriverResult Display(ShapePart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Shape_SummaryAdmin", () => shapeHelper.Parts_Shape_SummaryAdmin());
}
protected override DriverResult Editor(ShapePart part, dynamic shapeHelper) { protected override DriverResult Editor(ShapePart part, dynamic shapeHelper) {
return Editor(part, null, shapeHelper); return Editor(part, null, shapeHelper);
} }
protected override DriverResult Editor(ShapePart part, IUpdateModel updater, dynamic shapeHelper) { protected override DriverResult Editor(ShapePart part, IUpdateModel updater, dynamic shapeHelper) {
var viewModel = new ShapePartViewModel { var viewModel = new ShapePartViewModel {
Template = part.Template Template = part.Template,
RenderingMode = part.RenderingMode
}; };
if (updater != null if (updater != null
&& updater.TryUpdateModel(viewModel, Prefix, null, new[] { "AvailableLanguages" }) && updater.TryUpdateModel(viewModel, Prefix, null, new[] { "AvailableLanguages" })
&& ValidateShapeName(part, updater)) { && ValidateShapeName(part, updater)) {
part.Template = viewModel.Template; part.Template = viewModel.Template;
part.RenderingMode = viewModel.RenderingMode;
try { try {
var processor = _processors.FirstOrDefault(x => String.Equals(x.Type, part.ProcessorName, StringComparison.OrdinalIgnoreCase)) ?? _processors.First(); var processor = _processors.FirstOrDefault(x => String.Equals(x.Type, part.ProcessorName, StringComparison.OrdinalIgnoreCase)) ?? _processors.First();
@@ -49,6 +59,24 @@ namespace Orchard.Templates.Drivers {
updater.AddModelError("", T("Template processing error: {0}", ex.Message)); updater.AddModelError("", T("Template processing error: {0}", ex.Message));
_transactions.Cancel(); _transactions.Cancel();
} }
// We need to query for the content type names because querying for content parts has no effect on the database side.
var contentTypesWithShapePart = _contentManager
.GetContentTypeDefinitions()
.Where(typeDefinition => typeDefinition.Parts.Any(partDefinition => partDefinition.PartDefinition.Name == "ShapePart"))
.Select(typeDefinition => typeDefinition.Name);
// If ShapePart is only dynamically added to this content type or even this content item then we won't find
// a corresponding content type definition, so using the current content type too.
contentTypesWithShapePart = contentTypesWithShapePart.Union(new[] { part.ContentItem.ContentType });
var existingShapes = _contentManager
.Query(VersionOptions.Latest, contentTypesWithShapePart.ToArray())
.Where<TitlePartRecord>(record => record.Title == part.As< TitlePart>().Title && record.ContentItemRecord.Id != part.ContentItem.Id);
if (existingShapes.List().Any(x => x.As<ShapePart>().RenderingMode == part.RenderingMode)) {
updater.AddModelError("ShapeNameAlreadyExists", T("A template with the given name and rendering mode already exists."));
}
} }
return ContentShape("Parts_Shape_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts.Shape", Model: viewModel, Prefix: Prefix)); return ContentShape("Parts_Shape_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts.Shape", Model: viewModel, Prefix: Prefix));
} }

View File

@@ -1,49 +0,0 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Title.Models;
using Orchard.Localization;
using Orchard.Templates.Models;
using System.Linq;
namespace Orchard.Templates.Drivers {
public class TitlePartDriver : ContentPartDriver<TitlePart> {
private readonly IContentManager _contentManager;
public Localizer T { get; set; }
public TitlePartDriver(IContentManager contentManager) {
_contentManager = contentManager;
T = NullLocalizer.Instance;
}
protected override DriverResult Editor(TitlePart part, IUpdateModel updater, dynamic shapeHelper) {
if (!part.ContentItem.Has<ShapePart>()) {
return null;
}
updater.TryUpdateModel(part, Prefix, null, null);
// We need to query for the content type names because querying for content parts has no effect on the database side.
var contentTypesWithShapePart = _contentManager
.GetContentTypeDefinitions()
.Where(typeDefinition => typeDefinition.Parts.Any(partDefinition => partDefinition.PartDefinition.Name == "ShapePart"))
.Select(typeDefinition => typeDefinition.Name);
// If ShapePart is only dynamically added to this content type or even this content item then we won't find
// a corresponding content type definition, so using the current content type too.
contentTypesWithShapePart = contentTypesWithShapePart.Union(new[] { part.ContentItem.ContentType });
var existingShapeCount = _contentManager
.Query(VersionOptions.Latest, contentTypesWithShapePart.ToArray())
.Where<TitlePartRecord>(record => record.Title == part.Title && record.ContentItemRecord.Id != part.ContentItem.Id)
.Count();
if (existingShapeCount > 0) {
updater.AddModelError("ShapeNameAlreadyExists", T("A template with the given name already exists."));
}
return null;
}
}
}

View File

@@ -16,5 +16,16 @@ namespace Orchard.Templates.Models {
get { return this.Retrieve(x => x.Template); } get { return this.Retrieve(x => x.Template); }
set { this.Store(x => x.Template, value); } set { this.Store(x => x.Template, value); }
} }
public RenderingMode RenderingMode {
get { return this.Retrieve(x => x.RenderingMode); }
set { this.Store(x => x.RenderingMode, value); }
}
}
public enum RenderingMode {
FrontEndAndAdmin,
FrontEnd,
Admin
} }
} }

View File

@@ -180,7 +180,6 @@
<ItemGroup> <ItemGroup>
<Compile Include="AdminMenu.cs" /> <Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminController.cs" /> <Compile Include="Controllers\AdminController.cs" />
<Compile Include="Drivers\TitlePartDriver.cs" />
<Compile Include="Helpers\StringExtensions.cs" /> <Compile Include="Helpers\StringExtensions.cs" />
<Compile Include="Handlers\ShapePartHandler.cs" /> <Compile Include="Handlers\ShapePartHandler.cs" />
<Compile Include="Drivers\ShapePartDriver.cs" /> <Compile Include="Drivers\ShapePartDriver.cs" />
@@ -227,6 +226,9 @@
<ItemGroup> <ItemGroup>
<Content Include="packages.config" /> <Content Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Views\Parts.Shape.SummaryAdmin.cshtml" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -1,3 +1,7 @@
<Placement> <Placement>
<Place Parts_Shape_Edit="Content:0" /> <Place Parts_Shape_Edit="Content:0" />
<Match DisplayType="SummaryAdmin">
<Place Parts_Shape_SummaryAdmin="Content:5" />
</Match>
</Placement> </Placement>

View File

@@ -10,6 +10,8 @@ using System.Web;
using System.Web.Mvc; using System.Web.Mvc;
using Orchard.DisplayManagement; using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Descriptors; using Orchard.DisplayManagement.Descriptors;
using System.Web.Routing;
using Orchard.UI.Admin;
namespace Orchard.Templates.Services { namespace Orchard.Templates.Services {
public class TemplateShapeBindingResolver : IShapeBindingResolver { public class TemplateShapeBindingResolver : IShapeBindingResolver {
@@ -18,30 +20,50 @@ namespace Orchard.Templates.Services {
private IContentManager _contentManager; private IContentManager _contentManager;
private IContentDefinitionManager _contentDefinitionManager; private IContentDefinitionManager _contentDefinitionManager;
private ITemplateService _templateService; private ITemplateService _templateService;
private readonly RequestContext _requestContext;
public TemplateShapeBindingResolver( public TemplateShapeBindingResolver(
ICacheManager cacheManager, ICacheManager cacheManager,
ISignals signals, ISignals signals,
IContentManager contentManager, IContentManager contentManager,
IContentDefinitionManager contentDefinitionManager, IContentDefinitionManager contentDefinitionManager,
ITemplateService templateService ITemplateService templateService,
) { RequestContext requestContext) {
_cacheManager = cacheManager; _cacheManager = cacheManager;
_signals = signals; _signals = signals;
_contentManager = contentManager; _contentManager = contentManager;
_contentDefinitionManager = contentDefinitionManager; _contentDefinitionManager = contentDefinitionManager;
_templateService = templateService; _templateService = templateService;
_requestContext = requestContext;
} }
public bool TryGetDescriptorBinding(string shapeType, out ShapeBinding shapeBinding) { public bool TryGetDescriptorBinding(string shapeType, out ShapeBinding shapeBinding) {
var processors = BuildShapeProcessors(); var processors = BuildShapeProcessors();
var acceptableRenderingModes = new List<RenderingMode>() { RenderingMode.FrontEndAndAdmin };
if (AdminFilter.IsApplied(_requestContext)) {
acceptableRenderingModes.Add(RenderingMode.Admin);
}
else {
acceptableRenderingModes.Add(RenderingMode.FrontEnd);
}
var templateResults = processors[shapeType].Where(template => acceptableRenderingModes.Contains(template.RenderingMode));
TemplateResult templateResult = null; TemplateResult templateResult = null;
if (processors.TryGetValue(shapeType, out templateResult)) { var templateResultsCount = templateResults.Count();
if (templateResultsCount == 1) {
templateResult = templateResults.FirstOrDefault();
}
else if (templateResultsCount > 1) {
// Templates with the same name but specified rendering mode are prioritized.
templateResult = templateResults.FirstOrDefault(template => template.RenderingMode != RenderingMode.FrontEndAndAdmin);
}
if (templateResult != null) {
shapeBinding = new ShapeBinding { shapeBinding = new ShapeBinding {
BindingName = "Templates", BindingName = "Templates",
Binding = ctx => CoerceHtmlString(_templateService.Execute( Binding = ctx => CoerceHtmlString(_templateService.Execute(
templateResult.Template, templateResult.Template,
templateResult.Name, templateResult.Name,
templateResult.Processor, ctx.Value)), templateResult.Processor, ctx.Value)),
ShapeDescriptor = new ShapeDescriptor { ShapeType = shapeType } ShapeDescriptor = new ShapeDescriptor { ShapeType = shapeType }
@@ -54,7 +76,7 @@ namespace Orchard.Templates.Services {
return false; return false;
} }
private IDictionary<string, TemplateResult> BuildShapeProcessors() { private ILookup<string, TemplateResult> BuildShapeProcessors() {
return _cacheManager.Get("Template.ShapeProcessors", true, ctx => { return _cacheManager.Get("Template.ShapeProcessors", true, ctx => {
ctx.Monitor(_signals.When(DefaultTemplateService.TemplatesSignal)); ctx.Monitor(_signals.When(DefaultTemplateService.TemplatesSignal));
@@ -67,11 +89,12 @@ namespace Orchard.Templates.Services {
var allTemplates = _contentManager.Query<ShapePart>(typesWithShapePart).List(); var allTemplates = _contentManager.Query<ShapePart>(typesWithShapePart).List();
return allTemplates.Select(x => new TemplateResult { return allTemplates.Select(shapePart => new TemplateResult {
Name = x.Name, Name = shapePart.Name,
Template = x.Template, Template = shapePart.Template,
Processor = x.ProcessorName Processor = shapePart.ProcessorName,
}).ToDictionary(x => x.Name, x => x); RenderingMode = shapePart.RenderingMode
}).ToLookup(template => template.Name);
}); });
} }
@@ -83,6 +106,7 @@ namespace Orchard.Templates.Services {
public string Name { get; set; } public string Name { get; set; }
public string Processor { get; set; } public string Processor { get; set; }
public string Template { get; set; } public string Template { get; set; }
public RenderingMode RenderingMode { get; set; }
} }
} }
} }

View File

@@ -1,5 +1,9 @@
namespace Orchard.Templates.ViewModels { using Orchard.Templates.Models;
namespace Orchard.Templates.ViewModels {
public class ShapePartViewModel { public class ShapePartViewModel {
public string Template { get; set; } public string Template { get; set; }
public RenderingMode RenderingMode { get; set; }
} }
} }

View File

@@ -1,4 +1,5 @@
@model Orchard.Templates.ViewModels.ShapePartViewModel @using Orchard.Templates.Models
@model Orchard.Templates.ViewModels.ShapePartViewModel
@{ @{
Style.Include("~/modules/orchard.templates/scripts/codemirror/lib/codemirror.css"); Style.Include("~/modules/orchard.templates/scripts/codemirror/lib/codemirror.css");
Style.Include("template-editor.css"); Style.Include("template-editor.css");
@@ -15,4 +16,8 @@
<div> <div>
@Html.TextAreaFor(m => m.Template, new { @class = "text large code-editor" }) @Html.TextAreaFor(m => m.Template, new { @class = "text large code-editor" })
</div> </div>
<div>
@Html.LabelFor(m => m.RenderingMode, T("Rendering Mode"))
@Html.DropDownListFor(m => m.RenderingMode, Enum.GetValues(typeof(RenderingMode)).Cast<RenderingMode>().Select(renderingMode => new SelectListItem { Text = renderingMode.ToString(), Value = renderingMode.ToString() }))
</div>
</fieldset> </fieldset>

View File

@@ -0,0 +1 @@
@T("Rendering mode: {0}", Model.ContentPart.RenderingMode)