Files
Orchard/src/Orchard.Web/Modules/Orchard.Layouts/Drivers/ProjectionElementDriver.cs

325 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.Core.Title.Models;
using Orchard.Data;
using Orchard.DisplayManagement;
using Orchard.Environment.Extensions;
using Orchard.Forms.Services;
using Orchard.Layouts.Elements;
using Orchard.Layouts.Framework.Display;
using Orchard.Layouts.Framework.Drivers;
using Orchard.Projections.Descriptors.Layout;
using Orchard.Projections.Descriptors.Property;
using Orchard.Projections.Models;
using Orchard.Projections.Services;
using Orchard.Projections.ViewModels;
using Orchard.Tokens;
using Orchard.UI.Navigation;
using Orchard.Layouts.Helpers;
using DescribeContext = Orchard.Forms.Services.DescribeContext;
namespace Orchard.Layouts.Drivers {
[OrchardFeature("Orchard.Layouts.Projections")]
public class ProjectionElementDriver : FormsElementDriver<Projection> {
private readonly IProjectionManager _projectionManager;
private readonly IContentManager _contentManager;
private readonly IOrchardServices _services;
private readonly IRepository<LayoutRecord> _layoutRepository;
private readonly ITokenizer _tokenizer;
private readonly IDisplayHelperFactory _displayHelperFactory;
public ProjectionElementDriver(
IFormManager formManager,
IProjectionManager projectionManager,
IOrchardServices services,
IRepository<LayoutRecord> layoutRepository,
ITokenizer tokenizer,
IDisplayHelperFactory displayHelperFactory)
: base(formManager) {
_projectionManager = projectionManager;
_contentManager = services.ContentManager;
_services = services;
_layoutRepository = layoutRepository;
_tokenizer = tokenizer;
_displayHelperFactory = displayHelperFactory;
}
protected override IEnumerable<string> FormNames {
get {
yield return "ProjectionForm";
}
}
protected override void OnDisplaying(Projection element, ElementDisplayContext context) {
var queryId = element.QueryId;
var layoutId = element.LayoutId;
var query = queryId != null ? _contentManager.Get<QueryPart>(queryId.Value) : default(QueryPart);
var emptyContentItemsList = Enumerable.Empty<ContentManagement.ContentItem>();
context.ElementShape.ContentItems = emptyContentItemsList;
context.ElementShape.BuildShapes = (Func<string, IEnumerable<dynamic>>)(displayType => emptyContentItemsList.Select(x => _contentManager.BuildDisplay(x, displayType)));
if (query == null || layoutId == null) {
return;
}
// Retrieving paging parameters.
var queryString = _services.WorkContext.HttpContext.Request.QueryString;
var pageKey = String.IsNullOrWhiteSpace(element.PagerSuffix) ? "page" : "page-" + element.PagerSuffix;
var page = 0;
// Default page size.
var pageSize = element.ItemsToDisplay;
// Don't try to page if not necessary.
if (element.DisplayPager && queryString.AllKeys.Contains(pageKey)) {
Int32.TryParse(queryString[pageKey], out page);
}
// If 0, then assume "All", limit to 128 by default.
if (pageSize == 128) {
pageSize = Int32.MaxValue;
}
// If pageSize is provided on the query string, ensure it is compatible within allowed limits.
var pageSizeKey = "pageSize" + element.PagerSuffix;
if (queryString.AllKeys.Contains(pageSizeKey)) {
int qsPageSize;
if (Int32.TryParse(queryString[pageSizeKey], out qsPageSize)) {
if (element.MaxItems == 0 || qsPageSize <= element.MaxItems) {
pageSize = qsPageSize;
}
}
}
var pager = new Pager(_services.WorkContext.CurrentSite, page, pageSize);
// TODO: Investigate this further and see if it makes sense to implement for a Projection Element.
//// Generates a link to the RSS feed for this term.
//var metaData = _services.ContentManager.GetItemMetadata(part.ContentItem);
//_feedManager.Register(metaData.DisplayText, "rss", new RouteValueDictionary { { "projection", part.Id } });
// Execute the query.
var contentItems = _projectionManager.GetContentItems(query.Id, pager.GetStartIndex() + element.Skip, pager.PageSize).ToList();
context.ElementShape.ContentItems = contentItems;
context.ElementShape.BuildShapes = (Func<string, IEnumerable<dynamic>>)(displayType => contentItems.Select(x => _contentManager.BuildDisplay(x, displayType)));
// TODO: Figure out if we need this for a Projection Element, and if so, how.
//// Sanity check so that content items with ProjectionPart can't be added here, or it will result in an infinite loop.
//contentItems = contentItems.Where(x => !x.Has<ProjectionPart>()).ToList();
// Applying layout.
var layout = layoutId != null ? _layoutRepository.Get(layoutId.Value) : default(LayoutRecord);
var layoutDescriptor = layout == null ? null : _projectionManager.DescribeLayouts().SelectMany(x => x.Descriptors).FirstOrDefault(x => x.Category == layout.Category && x.Type == layout.Type);
// Create pager shape.
if (element.DisplayPager) {
var contentItemsCount = Math.Max(0, _projectionManager.GetCount(query.Id) - element.Skip);
var pagerShape = _services.New.Pager(pager)
.Element(element)
.PagerId(pageKey)
.TotalItemCount(contentItemsCount);
context.ElementShape.Pager = pagerShape;
}
// Renders in a standard List shape if no specific layout could be found.
if (layoutDescriptor == null) {
var contentShapes = contentItems.Select(item => _contentManager.BuildDisplay(item, "Summary"));
var list = context.ElementShape.List = _services.New.List();
list.AddRange(contentShapes);
return;
}
var allFielDescriptors = _projectionManager.DescribeProperties().ToList();
var fieldDescriptors = layout.Properties.OrderBy(p => p.Position).Select(p => allFielDescriptors.SelectMany(x => x.Descriptors).Select(d => new { Descriptor = d, Property = p }).FirstOrDefault(x => x.Descriptor.Category == p.Category && x.Descriptor.Type == p.Type)).ToList();
var layoutComponents = contentItems.Select(contentItem => {
var contentItemMetadata = _contentManager.GetItemMetadata(contentItem);
var propertyDescriptors = fieldDescriptors.Select(d => {
var fieldContext = new PropertyContext {
State = FormParametersHelper.ToDynamic(d.Property.State),
Tokens = new Dictionary<string, object> { { "Content", contentItem } }
};
return new { d.Property, Shape = d.Descriptor.Property(fieldContext, contentItem) };
});
// Apply all settings to the field content, wrapping it in a FieldWrapper shape.
var properties = _services.New.Properties(
Items: propertyDescriptors.Select(
pd => _services.New.PropertyWrapper(
Item: pd.Shape,
Property: pd.Property,
ContentItem: contentItem,
ContentItemMetadata: contentItemMetadata
)
)
);
return new LayoutComponentResult {
ContentItem = contentItem,
ContentItemMetadata = contentItemMetadata,
Properties = properties
};
}).ToList();
var tokenizedState = _tokenizer.Replace(layout.State, new Dictionary<string, object> { { "Content", context.Content.ContentItem } });
var renderLayoutContext = new LayoutContext {
State = FormParametersHelper.ToDynamic(tokenizedState),
LayoutRecord = layout,
};
if (layout.GroupProperty != null) {
var groupPropertyId = layout.GroupProperty.Id;
var display = _displayHelperFactory.CreateHelper(new ViewContext { HttpContext = _services.WorkContext.HttpContext }, new ViewDataContainer());
// Index by PropertyWrapper shape.
var groups = layoutComponents.GroupBy(x => {
var propertyShape = ((IEnumerable<dynamic>)x.Properties.Items).First(p => ((PropertyRecord)p.Property).Id == groupPropertyId);
// clear the wrappers, as they shouldn't be needed to generate the grouping key itself
// otherwise the DisplayContext.View is null, and throws an exception if a wrapper is rendered (#18558)
((IShape)propertyShape).Metadata.Wrappers.Clear();
string key = Convert.ToString(display(propertyShape));
return key;
}).Select(x => new { Key = x.Key, Components = x });
var list = context.ElementShape.List = _services.New.List();
foreach (var group in groups) {
var localResult = layoutDescriptor.Render(renderLayoutContext, group.Components);
// Add the Context to the shape.
localResult.Context(renderLayoutContext);
list.Add(_services.New.LayoutGroup(Key: new MvcHtmlString(group.Key), List: localResult));
}
return;
}
var layoutResult = layoutDescriptor.Render(renderLayoutContext, layoutComponents);
// Add the Context to the shape.
layoutResult.Context(renderLayoutContext);
// Set the List shape to be the layout result.
context.ElementShape.List = layoutResult;
}
protected override void DescribeForm(DescribeContext context) {
context.Form("ProjectionForm", factory => {
var shape = (dynamic)factory;
var form = shape.Fieldset(
Id: "ProjectionForm",
_QueryLayoutId: shape.SelectList(
Id: "QueryLayoutId",
Name: "QueryLayoutId",
Title: T("For Query"),
Description: T("The query to display.")),
_ItemsToDisplay: shape.Textbox(
Id: "ItemsToDisplay",
Name: "ItemsToDisplay",
Title: T("Items to display"),
Value: "0",
Description: T("The number of items to display. Enter 0 for no limit. When using pagination, this is the number of items per page."),
Classes: new[] { "text", "medium", "tokenized" }),
_Skip: shape.Textbox(
Id: "Skip",
Name: "Skip",
Title: T("Offset"),
Value: "0",
Description: T("The number of items to skip (e.g., if 2 is entered, the first 2 items won't be diplayed)."),
Classes: new[] { "text", "medium", "tokenized" }),
_MaxItems: shape.Textbox(
Id: "MaxItems",
Name: "MaxItems",
Title: T("MaxItems items"),
Value: "20",
Description: T("Maximum number of items which can be queried at once. Use 0 for unlimited. This is only used as a failsafe when the number of items comes from a user-provided source such as the query string."),
Classes: new[] { "text", "medium", "tokenized" }),
_PagerSuffix: shape.Textbox(
Id: "PagerSuffix",
Name: "PagerSuffix",
Title: T("Suffix"),
Description: T("Optional. Provide a suffix to use when multiple pagers are displayed on the same page, e.g., when using multiple Projection Widgets, or to define alternates."),
Classes: new[] { "text", "medium", "tokenized" }),
_DisplayPager: shape.Checkbox(
Id: "DisplayPager",
Name: "DisplayPager",
Title: T("Show Pager"),
Value: "true",
Description: T("Check to add a pager to the list.")));
// Populate the list of queries and layouts.
var layouts = _projectionManager.DescribeLayouts().SelectMany(x => x.Descriptors).ToList();
var queries = _contentManager.Query<QueryPart, QueryPartRecord>().Join<TitlePartRecord>().OrderBy(x => x.Title).List()
.Select(x => new QueryRecordEntry {
Id = x.Id,
Name = x.Name,
LayoutRecordEntries = x.Layouts.Select(l => new LayoutRecordEntry {
Id = l.Id,
Description = GetLayoutDescription(layouts, l)
})
}).ToArray();
foreach (var queryRecord in queries.OrderBy(x => x.Name)) {
form._QueryLayoutId.Add(new SelectListGroup { Name = queryRecord.Name });
form._QueryLayoutId.Add(new SelectListItem { Text = queryRecord.Name + " " + T("(Default Layout)").Text, Value = queryRecord.Id + ";-1" });
foreach (var layoutRecord in queryRecord.LayoutRecordEntries.OrderBy(x => x.Description)) {
form._QueryLayoutId.Add(new SelectListItem { Text = queryRecord.Name + " " + T("({0})", layoutRecord.Description).Text, Value = queryRecord.Id + ";" + layoutRecord.Id });
}
}
return form;
});
}
protected override void OnExporting(Projection element, ExportElementContext context) {
var query = element.QueryId != null ? _contentManager.Get<QueryPart>(element.QueryId.Value) : default(QueryPart);
var layout = query != null && element.LayoutId != null ? _layoutRepository.Get(element.LayoutId.Value) : default(LayoutRecord);
var queryIdentity = query != null ? _contentManager.GetItemMetadata(query).Identity.ToString() : default(string);
var layoutIndex = layout != null ? query.Layouts.IndexOf(layout) : default(int?);
if (queryIdentity != null && layoutIndex != null) {
context.ExportableData["QueryId"] = queryIdentity;
context.ExportableData["LayoutIndex"] = layoutIndex.Value.ToString();
}
}
protected override void OnImporting(Projection element, ImportElementContext context) {
var queryIdentity = context.ExportableData.Get("QueryId");
var query = queryIdentity != null ? context.Session.GetItemFromSession(queryIdentity) : default(ContentManagement.ContentItem);
if (query == null)
return;
var queryPart = query.As<QueryPart>();
var layoutIndex = XmlHelper.Parse<int>(context.ExportableData.Get("LayoutIndex"));
var layout = queryPart.Layouts[layoutIndex];
element.QueryId = queryPart.Id;
element.LayoutId = layout.Id;
}
private static string GetLayoutDescription(IEnumerable<LayoutDescriptor> layouts, LayoutRecord l) {
var descriptor = layouts.First(x => l.Category == x.Category && l.Type == x.Type);
return String.IsNullOrWhiteSpace(l.Description) ? descriptor.Display(new LayoutContext { State = FormParametersHelper.ToDynamic(l.State) }).Text : l.Description;
}
private class ViewDataContainer : IViewDataContainer {
public ViewDataDictionary ViewData { get; set; }
}
}
}