Merge branch '1.10.x' into dev

# Conflicts:
#	src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/HiddenFieldElementDriver.cs
#	src/Orchard.Web/Modules/Orchard.DynamicForms/Drivers/TextFieldElementDriver.cs
#	src/Orchard/ContentManagement/IHqlCriterion.cs
This commit is contained in:
Lombiq
2016-11-24 23:47:27 +01:00
committed by Zoltán Lehóczky
44 changed files with 882 additions and 326 deletions

View File

@@ -1,36 +1,24 @@
using System;
using System.Linq;
using Orchard.Blogs.Models;
using Orchard.Blogs.Services;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Widgets.Models;
using Orchard.Widgets.Services;
namespace Orchard.Blogs.Commands {
public class BlogWidgetCommands : DefaultOrchardCommandHandler {
private readonly IWidgetsService _widgetsService;
private readonly IWidgetCommandsService _widgetCommandsService;
private readonly IBlogService _blogService;
private readonly ISiteService _siteService;
private readonly IMembershipService _membershipService;
private readonly IContentManager _contentManager;
private BlogPart blog;
public BlogWidgetCommands(
IWidgetsService widgetsService,
IWidgetCommandsService widgetCommandsService,
IBlogService blogService,
ISiteService siteService,
IMembershipService membershipService,
IContentManager contentManager) {
_widgetsService = widgetsService;
_widgetCommandsService = widgetCommandsService;
_blogService = blogService;
_siteService = siteService;
_membershipService = membershipService;
_contentManager = contentManager;
RenderTitle = true;
@@ -75,11 +63,21 @@ namespace Orchard.Blogs.Commands {
public void CreateRecentBlogPostsWidget() {
var type = "RecentBlogPosts";
var widget = CreateStandardWidget(type);
// Check any custom parameters that could cause creating the widget to fail.
blog = GetBlog(BlogId, BlogPath);
if (blog == null) {
Context.Output.WriteLine(T("Creating {0} widget failed: blog was not found.", type));
return;
}
// Create the widget using the standard parameters.
var widget = _widgetCommandsService.CreateBaseWidget(
Context, type, Title, Name, Zone, Position, Layer, Identity, RenderTitle, Owner, null, false, null);
if (widget == null) {
return;
}
// Publish the successfully created widget.
widget.As<RecentBlogPostsPart>().BlogId = blog.Id;
// Setting count to 0 means all posts. It's an optional parameter and defaults to 5.
@@ -90,7 +88,8 @@ namespace Orchard.Blogs.Commands {
}
}
_contentManager.Publish(widget.ContentItem);
// Publish the successfully created widget.
_widgetCommandsService.Publish(widget);
Context.Output.WriteLine(T("{0} widget created successfully.", type).Text);
}
@@ -100,64 +99,28 @@ namespace Orchard.Blogs.Commands {
public void CreateBlogArchivesWidget() {
var type = "BlogArchives";
var widget = CreateStandardWidget(type);
// Check any custom parameters that could cause creating the widget to fail.
blog = GetBlog(BlogId, BlogPath);
if (blog == null) {
Context.Output.WriteLine(T("Creating {0} widget failed: blog was not found.", type));
return;
}
// Create the widget using the standard parameters.
var widget = _widgetCommandsService.CreateBaseWidget(
Context, type, Title, Name, Zone, Position, Layer, Identity, RenderTitle, Owner, null, false, null);
if (widget == null) {
return;
}
// Set the custom parameters.
widget.As<BlogArchivesPart>().BlogId = blog.Id;
_contentManager.Publish(widget.ContentItem);
// Publish the successfully created widget.
_widgetCommandsService.Publish(widget);
Context.Output.WriteLine(T("{0} widget created successfully.", type).Text);
}
private WidgetPart CreateStandardWidget(string type) {
var widgetTypeNames = _widgetsService.GetWidgetTypeNames().ToList();
if (!widgetTypeNames.Contains(type)) {
Context.Output.WriteLine(T("Creating widget failed: type {0} was not found. Supported widget types are: {1}.",
type,
string.Join(" ", widgetTypeNames)));
return null;
}
var layer = GetLayer(Layer);
if (layer == null) {
Context.Output.WriteLine(T("Creating {0} widget failed: layer {1} was not found.", type, Layer));
return null;
}
blog = GetBlog(BlogId, BlogPath);
if (blog == null) {
Context.Output.WriteLine(T("Creating {0} widget failed: blog was not found.", type));
return null;
}
var widget = _widgetsService.CreateWidget(layer.ContentItem.Id, type, T(Title).Text, Position, Zone);
if (!String.IsNullOrWhiteSpace(Name)) {
widget.Name = Name.Trim();
}
widget.RenderTitle = RenderTitle;
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}
var owner = _membershipService.GetUser(Owner);
widget.As<ICommonPart>().Owner = owner;
if (widget.Has<IdentityPart>() && !String.IsNullOrEmpty(Identity)) {
widget.As<IdentityPart>().Identifier = Identity;
}
return widget;
}
private LayerPart GetLayer(string layer) {
var layers = _widgetsService.GetLayers();
return layers.FirstOrDefault(layerPart => String.Equals(layerPart.Name, layer, StringComparison.OrdinalIgnoreCase));
}
private BlogPart GetBlog(int blogId, string blogPath) {
return _contentManager.Get<BlogPart>(blogId) ?? _blogService.Get(blogPath);
}

View File

@@ -12,6 +12,7 @@ using Orchard.Core.Contents.Settings;
using Orchard.Core.Contents.ViewModels;
using Orchard.DisplayManagement;
using Orchard.Localization;
using Orchard.Localization.Services;
using Orchard.Mvc;
using Orchard.Settings;
using Orchard.Themes;
@@ -22,16 +23,22 @@ namespace Orchard.ContentPicker.Controllers {
private readonly ISiteService _siteService;
private readonly IContentDefinitionManager _contentDefinitionManager;
private readonly INavigationManager _navigationManager;
private readonly ICultureManager _cultureManager;
private readonly ICultureFilter _cultureFilter;
public AdminController(
IOrchardServices orchardServices,
ISiteService siteService,
IContentDefinitionManager contentDefinitionManager,
INavigationManager navigationManager) {
INavigationManager navigationManager,
ICultureManager cultureManager,
ICultureFilter cultureFilter) {
_siteService = siteService;
_contentDefinitionManager = contentDefinitionManager;
_navigationManager = navigationManager;
Services = orchardServices;
_cultureManager = cultureManager;
_cultureFilter = cultureFilter;
T = NullLocalizer.Instance;
}
@@ -123,10 +130,16 @@ namespace Orchard.ContentPicker.Controllers {
break;
}
if (!String.IsNullOrWhiteSpace(model.Options.SelectedCulture)) {
query = _cultureFilter.FilterCulture(query, model.Options.SelectedCulture);
}
model.Options.FilterOptions = contentTypes
.Select(ctd => new KeyValuePair<string, string>(ctd.Name, ctd.DisplayName))
.ToList().OrderBy(kvp => kvp.Value);
model.Options.Cultures = _cultureManager.ListCultures();
var pagerShape = Services.New.Pager(pager).TotalItemCount(query.Count());
var pageOfContentItems = query.Slice(pager.GetStartIndex(), pager.PageSize).ToList();
var list = Services.New.List();

View File

@@ -0,0 +1,44 @@
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentPicker.Fields;
using Orchard.ContentPicker.ViewModels;
using Orchard.Environment.Extensions;
namespace Orchard.ContentPicker.Drivers {
[OrchardFeature("Orchard.ContentPicker.LocalizationExtensions")]
public class ContentPickerFieldLocalizationDriver : ContentFieldDriver<Fields.ContentPickerField> {
private readonly IContentManager _contentManager;
public ContentPickerFieldLocalizationDriver(IContentManager contentManager) {
_contentManager = contentManager;
}
private static string GetPrefix(Fields.ContentPickerField field, ContentPart part) {
return part.PartDefinition.Name + "." + field.Name;
}
private static string GetDifferentiator(Fields.ContentPickerField field, ContentPart part) {
return field.Name;
}
protected override DriverResult Editor(ContentPart part, Fields.ContentPickerField field, dynamic shapeHelper) {
return ContentShape("Fields_ContentPickerLocalization_Edit", GetDifferentiator(field, part),
() => {
var model = new ContentPickerFieldViewModel {
Field = field,
Part = part,
ContentItems = _contentManager.GetMany<ContentItem>(field.Ids, VersionOptions.Latest, QueryHints.Empty).ToList()
};
model.SelectedIds = string.Join(",", field.Ids);
return shapeHelper.EditorTemplate(TemplateName: "Fields/ContentPickerLocalization.Edit", Model: model, Prefix: GetPrefix(field, part));
});
}
protected override DriverResult Editor(ContentPart part, ContentPickerField field, IUpdateModel updater, dynamic shapeHelper) {
return Editor(part, field, shapeHelper);
}
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentPicker.Fields;
using Orchard.ContentPicker.Settings;
using Orchard.Environment.Extensions;
using Orchard.Localization;
using Orchard.Localization.Models;
using Orchard.Localization.Services;
using Orchard.UI.Notify;
namespace Orchard.ContentPicker.Handlers {
[OrchardFeature("Orchard.ContentPicker.LocalizationExtensions")]
public class ContentPickerFieldLocalizationExtensionHandler : ContentHandler {
private readonly IOrchardServices _orchardServices;
private readonly IContentManager _contentManager;
private readonly ILocalizationService _localizationService;
public Localizer T { get; set; }
public ContentPickerFieldLocalizationExtensionHandler(
IOrchardServices orchardServices,
IContentManager contentManager,
ILocalizationService localizationService) {
_orchardServices = orchardServices;
_contentManager = contentManager;
_localizationService = localizationService;
T = NullLocalizer.Instance;
}
protected override void UpdateEditorShape(UpdateEditorContext context) {
base.UpdateEditorShape(context);
//Here we implement the logic based on the settings introduced in ContentPickerFieldLocalizationSettings
//These settings should only be active if the ContentItem that is being updated has a LocalizationPart
if (context.ContentItem.Parts.Any(part => part is LocalizationPart)) {
var lPart = (LocalizationPart)context.ContentItem.Parts.Single(part => part is LocalizationPart);
var fields = context.ContentItem.Parts.SelectMany(x => x.Fields.Where(f => f.FieldDefinition.Name == typeof(ContentPickerField).Name)).Cast<ContentPickerField>();
foreach (var field in fields) {
var settings = field.PartFieldDefinition.Settings.GetModel<ContentPickerFieldLocalizationSettings>();
if (settings.TryToLocalizeItems) {
//try to replace items in the field with their translation
var itemsInField = _contentManager.GetMany<ContentItem>(field.Ids, VersionOptions.Published, QueryHints.Empty);
if (settings.RemoveItemsWithNoLocalizationPart && itemsInField.Where(ci => !ci.Parts.Any(part => part is LocalizationPart)).Any()) {
//keep only items that have a LocalizationPart
_orchardServices.Notifier.Warning(T(
"{0}: The following items could have no localization, so they were removed: {1}",
field.DisplayName,
string.Join(", ", itemsInField.Where(ci => !ci.Parts.Any(part => part is LocalizationPart)).Select(ci => _contentManager.GetItemMetadata(ci).DisplayText))
));
itemsInField = itemsInField.Where(ci => ci.Parts.Any(part => part is LocalizationPart));
}
//use an (int, int) tuple to track translations
var newIds = itemsInField.Select(ci => {
if (ci.Parts.Any(part => part is LocalizationPart)) {
if (_localizationService.GetContentCulture(ci) == lPart.Culture.Culture)
return new Tuple<int, int>(ci.Id, ci.Id); //this item is fine
var localized = _localizationService.GetLocalizations(ci).FirstOrDefault(lp => lp.Culture == lPart.Culture);
return localized == null ? new Tuple<int, int>(ci.Id, -ci.Id) : new Tuple<int, int>(ci.Id, localized.Id); //return negative id where we found no translation
}
else {
//we only go here if RemoveItemsWithNoLocalizationPart == false
return new Tuple<int, int>(ci.Id, ci.Id);
}
});
if (newIds.Any(tup => tup.Item2 < 0)) {
if (settings.RemoveItemsWithoutLocalization) {
//remove the items for which we could not find a localization
_orchardServices.Notifier.Warning(T(
"{0}: We could not find a localization for the following items, so they were removed: {1}",
field.DisplayName,
string.Join(", ", newIds.Where(tup => tup.Item2 < 0).Select(tup => _contentManager.GetItemMetadata(_contentManager.GetLatest(tup.Item1)).DisplayText))
));
newIds = newIds.Where(tup => tup.Item2 > 0);
}
else {
//negative Ids are made positive again
newIds = newIds.Select(tup => tup = new Tuple<int, int>(tup.Item1, Math.Abs(tup.Item2)));
}
}
if (newIds.Where(tup => tup.Item1 != tup.Item2).Any()) {
_orchardServices.Notifier.Warning(T(
"{0}: The following items were replaced by their correct localization: {1}",
field.DisplayName,
string.Join(", ", newIds.Where(tup => tup.Item1 != tup.Item2).Select(tup => _contentManager.GetItemMetadata(_contentManager.GetLatest(tup.Item1)).DisplayText))
));
}
field.Ids = newIds.Select(tup => tup.Item2).Distinct().ToArray();
}
if (settings.AssertItemsHaveSameCulture) {
//verify that the items in the ContentPickerField are all in the culture of the ContentItem whose editor we are updating
var itemsInField = _contentManager.GetMany<ContentItem>(field.Ids, VersionOptions.Published, QueryHints.Empty);
var itemsWithoutLocalizationPart = itemsInField.Where(ci => !ci.Parts.Any(part => part is LocalizationPart));
List<int> badItemIds = itemsInField.Where(ci => ci.Parts.Any(part => part is LocalizationPart && ((LocalizationPart)part).Culture != lPart.Culture)).Select(ci => ci.Id).ToList();
if (itemsWithoutLocalizationPart.Count() > 0) {
//Verify items from the ContentPickerField that cannot be localized
_orchardServices.Notifier.Warning(T("{0}: Some of the selected items cannot be localized: {1}",
field.DisplayName,
string.Join(", ", itemsWithoutLocalizationPart.Select(ci => _contentManager.GetItemMetadata(ci).DisplayText))
));
if (settings.BlockForItemsWithNoLocalizationPart) {
badItemIds.AddRange(itemsWithoutLocalizationPart.Select(ci => ci.Id));
}
}
if (badItemIds.Count > 0) {
context.Updater.AddModelError(field.DisplayName, T("Some of the items selected have the wrong localization: {0}",
string.Join(", ", badItemIds.Select(id => _contentManager.GetItemMetadata(_contentManager.GetLatest(id)).DisplayText))
));
}
}
}
}
}
}
}

View File

@@ -10,3 +10,8 @@ Features:
Description: UI for selecting Content Items.
Dependencies: Contents, Navigation
Category: Input Editor
Orchard.ContentPicker.LocalizationExtensions:
Name: Orchard.ContentPicker.LocalizationExtensions
Description: Provides settings to enable advanced localization behaviours for ContentPickerFields.
Dependencies: Orchard.ContentPicker, Orchard.Localization, Orchard.Resources
Category: Input Editor

View File

@@ -98,6 +98,10 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Drivers\ContentPickerFieldLocalizationDriver.cs" />
<Compile Include="Handlers\ContentPickerFieldLocalizationExtensionHandler.cs" />
<Compile Include="Settings\ContentPickerFieldLocalizationEditorEvents.cs" />
<Compile Include="Settings\ContentPickerFieldLocalizationSettings.cs" />
<Compile Include="ViewModels\NavigationPartViewModel.cs" />
<Content Include="Scripts\ContentPicker.js" />
<Content Include="Scripts\SelectableContentTab.js" />
@@ -180,6 +184,10 @@
<Name>Orchard.Core</Name>
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\Orchard.Localization\Orchard.Localization.csproj">
<Project>{fbc8b571-ed50-49d8-8d9d-64ab7454a0d6}</Project>
<Name>Orchard.Localization</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Views\ContentPicker.Edit.cshtml" />
@@ -187,6 +195,12 @@
<ItemGroup>
<Content Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\DefinitionTemplates\ContentPickerFieldLocalizationSettings.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Fields\ContentPickerLocalization.Edit.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -1,5 +1,6 @@
<Placement>
<Place Fields_ContentPicker_Edit="Content:2.3"/>
<Place Fields_ContentPickerLocalization_Edit="Content:2.3"/>
<Place Parts_Navigation_Edit="Content:10"/>
<Place Parts_ContentMenuItem_Edit="Content:10"/>

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Globalization;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.ViewModels;
using Orchard.Environment.Extensions;
namespace Orchard.ContentPicker.Settings {
[OrchardFeature("Orchard.ContentPicker.LocalizationExtensions")]
public class ContentPickerFieldLocalizationEditorEvents : ContentDefinitionEditorEventsBase {
public override IEnumerable<TemplateViewModel> PartFieldEditor(ContentPartFieldDefinition definition) {
if (definition.FieldDefinition.Name == "ContentPickerField") {
var model = definition.Settings.GetModel<ContentPickerFieldLocalizationSettings>();
yield return DefinitionTemplate(model);
}
}
public override IEnumerable<TemplateViewModel> PartFieldEditorUpdate(ContentPartFieldDefinitionBuilder builder, IUpdateModel updateModel) {
if (builder.FieldType != "ContentPickerField") {
yield break;
}
var model = new ContentPickerFieldLocalizationSettings();
if (updateModel.TryUpdateModel(model, "ContentPickerFieldLocalizationSettings", null, null)) {
builder.WithSetting("ContentPickerFieldLocalizationSettings.TryToLocalizeItems", model.TryToLocalizeItems.ToString(CultureInfo.InvariantCulture));
builder.WithSetting("ContentPickerFieldLocalizationSettings.RemoveItemsWithoutLocalization", model.RemoveItemsWithoutLocalization.ToString(CultureInfo.InvariantCulture));
builder.WithSetting("ContentPickerFieldLocalizationSettings.RemoveItemsWithNoLocalizationPart", model.RemoveItemsWithNoLocalizationPart.ToString(CultureInfo.InvariantCulture));
builder.WithSetting("ContentPickerFieldLocalizationSettings.AssertItemsHaveSameCulture", model.AssertItemsHaveSameCulture.ToString(CultureInfo.InvariantCulture));
builder.WithSetting("ContentPickerFieldLocalizationSettings.BlockForItemsWithNoLocalizationPart", model.BlockForItemsWithNoLocalizationPart.ToString(CultureInfo.InvariantCulture));
}
yield return DefinitionTemplate(model);
}
}
}

View File

@@ -0,0 +1,16 @@
using Orchard.Environment.Extensions;
namespace Orchard.ContentPicker.Settings {
[OrchardFeature("Orchard.ContentPicker.LocalizationExtensions")]
public class ContentPickerFieldLocalizationSettings {
public ContentPickerFieldLocalizationSettings() {
TryToLocalizeItems = true;
}
public bool TryToLocalizeItems { get; set; }
public bool RemoveItemsWithoutLocalization { get; set; }
public bool RemoveItemsWithNoLocalizationPart { get; set; }
public bool AssertItemsHaveSameCulture { get; set; }
public bool BlockForItemsWithNoLocalizationPart { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
@model Orchard.ContentPicker.Settings.ContentPickerFieldLocalizationSettings
<fieldset>
@Html.CheckBoxFor(m => m.TryToLocalizeItems)
<label for="@Html.FieldIdFor(m => m.TryToLocalizeItems)" class="forcheckbox">@T("Try to replace selected items with their correct localization.")</label>
<span class="hint">@T("Check to attempt to replace items selected in this field with their translation in the main ContentItem's culture. This only applies if the main ContentItem has a LocalizationPart.")</span>
<div data-controllerid="@Html.FieldIdFor(m => m.TryToLocalizeItems)">
@Html.CheckBoxFor(m => m.RemoveItemsWithoutLocalization)
<label for="@Html.FieldIdFor(m => m.RemoveItemsWithoutLocalization)" class="forcheckbox">@T("Remove items that do not have the correct translation.")</label>
<span class="hint">@T("Check to remove items from the ContentPickerField when the items selected do not have a version in the correct culture (they have a LocalizationPart, but not a translation in the main ContentItem's culture').")</span>
@Html.CheckBoxFor(m => m.RemoveItemsWithNoLocalizationPart)
<label for="@Html.FieldIdFor(m => m.RemoveItemsWithNoLocalizationPart)" class="forcheckbox">@T("Remove items that cannot be localized.")</label>
<span class="hint">@T("Check to remove items from the ContentPickerField when the items selected cannot be localized (do not have a LocalizationPart).")</span>
</div>
@Html.CheckBoxFor(m => m.AssertItemsHaveSameCulture)
<label for="@Html.FieldIdFor(m => m.AssertItemsHaveSameCulture)" class="forcheckbox">@T("Verify culture of selected items.")</label>
<span class="hint">@T("Check to prevent publication of contents when the items selected have a different culture. This only applies if the main ContentItem has a LocalizationPart.")</span>
<div data-controllerid="@Html.FieldIdFor(m => m.AssertItemsHaveSameCulture)">
@Html.CheckBoxFor(m => m.BlockForItemsWithNoLocalizationPart)
<label for="@Html.FieldIdFor(m => m.BlockForItemsWithNoLocalizationPart)" class="forcheckbox">@T("Do not admit items that cannot be localized.")</label>
<span class="hint">@T("Check to stop publication of contents when the items selected cannot be localized (do not have a LocalizationPart).")</span>
</div>
</fieldset>

View File

@@ -0,0 +1,39 @@
@model Orchard.ContentPicker.ViewModels.ContentPickerFieldViewModel
@using Orchard.ContentPicker.Settings;
@using Orchard.Localization.Models;
@using Orchard.ContentManagement;
@{
Script.Require("jQuery").AtFoot();
var settings = Model.Field.PartFieldDefinition.Settings.GetModel<ContentPickerFieldLocalizationSettings>();
string tryTranslateMsg = T("Selected items with a localization different than the current one will be localized.").Text;
string removeMissingMsg = T("Selected items for which there is no correct localization will be removed.").Text;
string removeUnlocalizableMsg = T("Selected items that cannot have localizations will be removed.").Text;
if (settings.RemoveItemsWithoutLocalization) { tryTranslateMsg += " " + removeMissingMsg; }
if (settings.RemoveItemsWithNoLocalizationPart) { tryTranslateMsg += " " + removeUnlocalizableMsg; }
//We will use a script to find the fieldset for the field we are currently processing.
//The fieldset contains a span of class "hint". We will add tryTranslateMsg to it.
string dataPartName = HttpUtility.JavaScriptStringEncode(Model.Part.PartDefinition.Name);
string dataFieldName = HttpUtility.JavaScriptStringEncode(Model.Field.PartFieldDefinition.Name);
}
@using (Script.Foot()) {
<script type="text/javascript">
$(function () {
$("fieldset[data-part-name='@dataPartName'][data-field-name='@dataFieldName']").find("span.hint")[0].innerText += "@tryTranslateMsg";
});
@foreach (var contentItem in Model.ContentItems) {
var loc = contentItem.As<LocalizationPart>();
if (loc != null && loc.Culture!=null && !string.IsNullOrWhiteSpace(loc.Culture.Culture)) {
<text>
$(function () {
$("span[data-id='@contentItem.Id'][data-fieldid='@Html.FieldIdFor(m => m.Field.Ids)'].content-picker-item")[0].append(" (@loc.Culture.Culture)");
})
</text>
}
}
</script>
}

View File

@@ -1,7 +1,7 @@
@using Orchard.Core.Contents.ViewModels;
@{
Script.Require("SelectableContentTab");
var typeDisplayName = Model.TypeDisplayName;
var pageTitle = T("Recent Content");
@@ -9,8 +9,10 @@
pageTitle = T("Manage {0} Content", typeDisplayName);
}
IEnumerable<string> cultures = Model.Options.Cultures;
Layout.Title = pageTitle;
}
<div class="manage">
@@ -25,6 +27,17 @@
@Html.SelectOption((string)Model.Options.SelectedFilter, (string)filterOption.Key, (string)filterOption.Value)
}
</select>
@if (cultures.Count() > 1) {
<label for="filterCultures" class="bulk-culture">@T("Culture")</label>
<select id="filterCultures" name="Options.SelectedCulture">
@Html.SelectOption((string)Model.Options.SelectedCulture, "", T("any (show all)").ToString())
@foreach (string culture in cultures) {
@Html.SelectOption((string)Model.Options.SelectedCulture, culture, System.Globalization.CultureInfo.GetCultureInfo(culture).DisplayName)
}
</select>
}
<label for="orderResults" class="bulk-order">@T("Ordered by")</label>
<select id="orderResults" name="Options.OrderBy">
@Html.SelectOption((ContentsOrder)Model.Options.OrderBy, ContentsOrder.Created, T("recently created").ToString())

View File

@@ -27,7 +27,7 @@ namespace Orchard.DynamicForms.Drivers {
Id: "Value",
Name: "Value",
Title: "Value",
Classes: new[] { "text", "medium" },
Classes: new[] { "text", "medium", "tokenized" },
Description: T("The value of this hidden field.")));
return form;

View File

@@ -91,7 +91,7 @@ namespace Orchard.MultiTenancy.Commands {
public void Create(string tenantName) {
Context.Output.WriteLine(T("Creating tenant '{0}'...", tenantName));
if (String.IsNullOrWhiteSpace(tenantName) || !Regex.IsMatch(tenantName, @"^\w+$")) {
if (String.IsNullOrWhiteSpace(tenantName) || !Regex.IsMatch(tenantName, @"^[a-zA-Z]\w*$")) {
Context.Output.WriteLine(T("Invalid tenant name. Must contain characters only and no spaces."));
return;
}

View File

@@ -66,7 +66,7 @@ namespace Orchard.MultiTenancy.Controllers {
}
// Ensure tenants name are valid.
if (!String.IsNullOrEmpty(viewModel.Name) && !Regex.IsMatch(viewModel.Name, @"^\w+$")) {
if (!String.IsNullOrEmpty(viewModel.Name) && !Regex.IsMatch(viewModel.Name, @"^[a-zA-Z]\w*$")) {
ModelState.AddModelError("Name", T("Invalid tenant name. Must contain characters only and no spaces.").Text);
}
@@ -270,4 +270,4 @@ namespace Orchard.MultiTenancy.Controllers {
return _thisShellSettings.Name == ShellSettings.DefaultName;
}
}
}
}

View File

@@ -2,19 +2,27 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Orchard.OutputCache.Models;
using Orchard.Utility.Extensions;
using Orchard.Environment.Configuration;
using System.Web.Caching;
namespace Orchard.OutputCache.Services {
/// <summary>
/// Tenant wide case insensitive reverse index for <see cref="CacheItem"/> tags.
/// </summary>
public class DefaultTagCache : ITagCache {
private readonly ConcurrentDictionary<string, HashSet<string>> _dictionary;
public DefaultTagCache() {
_dictionary = new ConcurrentDictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
public DefaultTagCache(IWorkContextAccessor workContextAccessor, ShellSettings shellSettings) {
var key = shellSettings.Name + ":TagCache";
var workContext = workContextAccessor.GetContext();
_dictionary = workContext.HttpContext.Cache.Get(key) as ConcurrentDictionary<string, HashSet<string>>;
if (_dictionary == null) {
_dictionary = new ConcurrentDictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
workContext.HttpContext.Cache.Add(key, _dictionary, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
}
}
public void Tag(string tag, params string[] keys) {
@@ -26,7 +34,7 @@ namespace Orchard.OutputCache.Services {
}
}
}
public IEnumerable<string> GetTaggedItems(string tag) {
HashSet<string> set;
if (_dictionary.TryGetValue(tag, out set)) {

View File

@@ -15,6 +15,17 @@ namespace Orchard.PublishLater.Handlers {
OnLoading<PublishLaterPart>((context, part) => LazyLoadHandlers(part));
OnVersioning<PublishLaterPart>((context, part, newVersionPart) => LazyLoadHandlers(newVersionPart));
OnRemoved<PublishLaterPart>((context, part) => publishingTaskManager.DeleteTasks(part.ContentItem));
OnPublishing<PublishLaterPart>((context, part) =>
{
var existingPublishTask = publishingTaskManager.GetPublishTask(context.ContentItem);
//Check if there is already and existing publish task for old version.
if (existingPublishTask != null)
{
//If exists remove it in order no to override the latest published version.
publishingTaskManager.DeleteTasks(context.ContentItem);
}
});
}
protected void LazyLoadHandlers(PublishLaterPart part) {

View File

@@ -30,6 +30,9 @@ namespace Orchard.SecureSocketsLayer.Drivers {
protected override DriverResult Editor(SslSettingsPart part, IUpdateModel updater, dynamic shapeHelper) {
if (updater.TryUpdateModel(part, Prefix, null, null)) {
_signals.Trigger(SslSettingsPart.CacheKey);
if (!part.Enabled) part.SecureEverything = false;
if (!part.SecureEverything) part.SendStrictTransportSecurityHeaders = false;
if (!part.StrictTransportSecurityIncludeSubdomains) part.StrictTransportSecurityPreload = false;
}
return Editor(part, shapeHelper);

View File

@@ -16,8 +16,7 @@ namespace Orchard.SecureSocketsLayer.Models {
public class SslSettingsPart : ContentPart {
public const string CacheKey = "SslSettingsPart";
public string Urls
{
public string Urls {
get { return this.As<InfosetPart>().Get<SslSettingsPart>("Urls"); }
set { this.As<InfosetPart>().Set<SslSettingsPart>("Urls", value); }
}
@@ -57,5 +56,26 @@ namespace Orchard.SecureSocketsLayer.Models {
get { return this.As<InfosetPart>().Get<SslSettingsPart>("InsecureHostName"); }
set { this.As<InfosetPart>().Set<SslSettingsPart>("InsecureHostName", value); }
}
public bool SendStrictTransportSecurityHeaders {
get { return this.Retrieve(x => x.SendStrictTransportSecurityHeaders); }
set { this.Store(x => x.SendStrictTransportSecurityHeaders, value); }
}
public bool StrictTransportSecurityIncludeSubdomains {
get { return this.Retrieve(x => x.StrictTransportSecurityIncludeSubdomains); }
set { this.Store(x => x.StrictTransportSecurityIncludeSubdomains, value); }
}
[Required]
public int StrictTransportSecurityMaxAge {
get { return this.Retrieve(x => x.StrictTransportSecurityMaxAge, 31536000); }
set { this.Store(x => x.StrictTransportSecurityMaxAge, value); }
}
public bool StrictTransportSecurityPreload {
get { return this.Retrieve(x => x.StrictTransportSecurityPreload); }
set { this.Store(x => x.StrictTransportSecurityPreload, value); }
}
}
}

View File

@@ -49,10 +49,18 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Owin, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.ComponentModel.DataAnnotations">
@@ -96,6 +104,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\StrictTransportSecurityMiddlewareProvider.cs" />
<Content Include="Module.txt" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.Logging;
using Orchard.Owin;
using Orchard.SecureSocketsLayer.Models;
using Owin;
namespace Orchard.SecureSocketsLayer.Services {
public class StrictTransportSecurityMiddlewareProvider : IOwinMiddlewareProvider {
private readonly IWorkContextAccessor _wca;
public ILogger Logger { get; set; }
public StrictTransportSecurityMiddlewareProvider(IWorkContextAccessor wca) {
_wca = wca;
Logger = NullLogger.Instance;
}
public IEnumerable<OwinMiddlewareRegistration> GetOwinMiddlewares() {
return new[] {
new OwinMiddlewareRegistration {
Configure = app =>
app.Use(async (context, next) => {
var sslSettings = _wca.GetContext().CurrentSite.As<SslSettingsPart>();
if (sslSettings.SendStrictTransportSecurityHeaders) {
string responseValue = "max-age=" + sslSettings.StrictTransportSecurityMaxAge;
if (sslSettings.StrictTransportSecurityIncludeSubdomains) {
responseValue += "; includeSubDomains";
}
if (sslSettings.StrictTransportSecurityPreload) {
responseValue += "; preload";
}
context.Response.Headers.Append("Strict-Transport-Security", responseValue);
}
await next.Invoke();
})
}
};
}
}
}

View File

@@ -11,18 +11,47 @@
<div>
<label for="@Html.FieldIdFor(m => m.SecureHostName)">@T("Secure Host Name")</label>
@Html.TextBoxFor(m => m.SecureHostName, new { @class = "textMedium" })
<span class="hint">@T("(Mandatory) The host name secured traffic should be redirected to (e.g. localhost:44300, secure.mydomain.com). Don't include the protocol or anything else than the host name. A port can be specified after a colon if necessary (e.g. secure.127-0-0-1.org.uk:4333).")</span>
@Html.Hint(T("(Mandatory) The host name secured traffic should be redirected to (e.g. localhost:44300, secure.mydomain.com). Don't include the protocol or anything else than the host name. A port can be specified after a colon if necessary (e.g. secure.127-0-0-1.org.uk:4333)."))
</div>
<div>
<label for="@Html.FieldIdFor(m => m.InsecureHostName)">@T("Insecure Host Name")</label>
@Html.TextBoxFor(m => m.InsecureHostName, new { @class = "textMedium" })
<span class="hint">@T("(Mandatory) The host name non-secured traffic should be redirected to (e.g. localhost:30321, mydomain.com). Don't include the protocol or anything else than the host name. A port can be specified after a colon if necessary (e.g. dev.127-0-0-1.org.uk:4333).")</span>
@Html.Hint(T("(Mandatory) The host name non-secured traffic should be redirected to (e.g. localhost:30321, mydomain.com). Don't include the protocol or anything else than the host name. A port can be specified after a colon if necessary (e.g. dev.127-0-0-1.org.uk:4333)."))
</div>
<div>
@Html.EditorFor(m => m.SecureEverything)
<label for="@Html.FieldIdFor(m => m.SecureEverything)" class="forcheckbox">@T("Force SSL on all pages")</label>
@Html.ValidationMessage("SecureEverything", "*")
</div>
<div data-controllerid="@Html.FieldIdFor(m => m.SecureEverything)">
<div>
@Html.EditorFor(m => m.SendStrictTransportSecurityHeaders)
<label for="@Html.FieldIdFor(m => m.SendStrictTransportSecurityHeaders)" class="forcheckbox">@T("Enable HTTP Strict Transport Security")</label>
@Html.Hint(@T("<a href='https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security' target='_blank'>More information about HSTS</a>"))
@Html.ValidationMessage("SendStrictTransportSecurityHeaders", "*")
</div>
<div data-controllerid="@Html.FieldIdFor(m => m.SendStrictTransportSecurityHeaders)">
<div>
<label for="@Html.FieldIdFor(m => m.StrictTransportSecurityMaxAge)">@T("Expiration time")</label>
@Html.TextBoxFor(m => m.StrictTransportSecurityMaxAge, new { @class = "textMedium" })
@Html.Hint(T("(Mandatory) Specify the max age of HSTS in seconds (e.g. 31536000 is one year). If you set this parameter to 0, the HSTS Header will immediately expire."))
</div>
<div>
@Html.EditorFor(m => m.StrictTransportSecurityIncludeSubdomains)
<label for="@Html.FieldIdFor(m => m.StrictTransportSecurityIncludeSubdomains)" class="forcheckbox">@T("Include Subdomains")</label>
@Html.ValidationMessage("StrictTransportSecurityIncludeSubdomains", "*")
@Html.Hint(T("Applies the HSTS rules to all of the site's subdomains."))
</div>
<div data-controllerid="@Html.FieldIdFor(m => m.StrictTransportSecurityIncludeSubdomains)">
<div>
@Html.EditorFor(m => m.StrictTransportSecurityPreload)
<label for="@Html.FieldIdFor(m => m.StrictTransportSecurityPreload)" class="forcheckbox">@T("Preload")</label>
@Html.ValidationMessage("StrictTransportSecurityPreload", "*")
@Html.Hint(T("You can use this if you successfully submitted your domain to an HSTS preload service and serve all subdomains over HTTPS. If you send this directive, it can have PERMANENT CONSEQUENCES. Please read the <a href='https://hstspreload.appspot.com/' target='_blank'>details</a> before sending the header with \"preload\"."))
</div>
</div>
</div>
</div>
<div>
<div>
@Html.EditorFor(m => m.CustomEnabled)
@@ -34,11 +63,10 @@
<label for="@Html.FieldIdFor(m => m.Urls)">@T("Urls")</label>
@Html.TextAreaFor(m => m.Urls, new { @class = "textMedium", rows = "5" })
@Html.ValidationMessage("Urls", "*")
<span class="hint">@T("Provide a list of urls, one per line. Urls can contains wildcard matches using '*', or root identifier like '~/'")</span>
<span class="hint">@T("Examples: http://mysite.com/mypage, ~/Profile/Edit, ~/Profile/*")</span>
@Html.Hint(T("Provide a list of urls, one per line. Urls can contains wildcard matches using '*', or root identifier like '~/'"))
@Html.Hint(T("Examples: http://mysite.com/mypage, ~/Profile/Edit, ~/Profile/*"))
</div>
</div>
</div>
</div>
</fieldset>
</fieldset>

View File

@@ -3,5 +3,7 @@
<package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net452" />
<package id="Microsoft.Owin" version="3.0.0" targetFramework="net452" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net452" />
</packages>

View File

@@ -1,31 +1,16 @@
using System;
using System.Linq;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Tags.Models;
using Orchard.Widgets.Models;
using Orchard.Widgets.Services;
namespace Orchard.Tags.Commands {
public class TagWidgetCommands : DefaultOrchardCommandHandler {
private readonly IWidgetsService _widgetsService;
private readonly ISiteService _siteService;
private readonly IMembershipService _membershipService;
private readonly IContentManager _contentManager;
private readonly IWidgetCommandsService _widgetCommandsService;
public TagWidgetCommands(
IWidgetsService widgetsService,
ISiteService siteService,
IMembershipService membershipService,
IContentManager contentManager) {
_widgetsService = widgetsService;
_siteService = siteService;
_membershipService = membershipService;
_contentManager = contentManager;
IWidgetCommandsService widgetCommandsService) {
_widgetCommandsService = widgetCommandsService;
RenderTitle = true;
}
@@ -66,34 +51,18 @@ namespace Orchard.Tags.Commands {
public void CreateTagsCloudWidget() {
var type = "TagCloud";
var layer = GetLayer(Layer);
if (layer == null) {
Context.Output.WriteLine(T("Creating {0} widget failed: layer {1} was not found.", type, Layer));
return;
}
// Check any custom parameters that could cause creating the widget to fail.
// Nothing to check in this widget, see BlogWidgetCommands.cs for an example.
var widget = _widgetsService.CreateWidget(layer.ContentItem.Id, type, T(Title).Text, Position, Zone);
if (!String.IsNullOrWhiteSpace(Name)) {
widget.Name = Name.Trim();
}
widget.RenderTitle = RenderTitle;
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}
var owner = _membershipService.GetUser(Owner);
widget.As<ICommonPart>().Owner = owner;
if (widget.Has<IdentityPart>() && !String.IsNullOrEmpty(Identity)) {
widget.As<IdentityPart>().Identifier = Identity;
}
// Create the widget using the standard parameters.
var widget = _widgetCommandsService.CreateBaseWidget(
Context, type, Title, Name, Zone, Position, Layer, Identity, RenderTitle, Owner, null, false, null);
if (widget == null) {
return;
}
// Set the custom parameters.
widget.As<TagCloudPart>().Slug = Slug;
// It's an optional parameter and defaults to 5.
@@ -104,13 +73,9 @@ namespace Orchard.Tags.Commands {
}
}
_contentManager.Publish(widget.ContentItem);
// Publish the successfully created widget.
_widgetCommandsService.Publish(widget);
Context.Output.WriteLine(T("{0} widget created successfully.", type).Text);
}
private LayerPart GetLayer(string layer) {
var layers = _widgetsService.GetLayers();
return layers.FirstOrDefault(layerPart => String.Equals(layerPart.Name, layer, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@@ -42,11 +42,13 @@ namespace Orchard.Taxonomies.Controllers {
var taxonomy = _taxonomyService.GetTaxonomy(taxonomyId);
var terms = TermPart.Sort(_taxonomyService.GetTermsQuery(taxonomyId).Slice(pager.GetStartIndex(), pager.PageSize));
var allTerms = TermPart.Sort(_taxonomyService.GetTermsQuery(taxonomyId).List());
var pagerShape = Shape.Pager(pager).TotalItemCount(_taxonomyService.GetTermsQuery(taxonomyId).Count());
var termsPage = pager.PageSize > 0 ? allTerms.Skip(pager.GetStartIndex()).Take(pager.PageSize) : allTerms;
var entries = terms
var pagerShape = Shape.Pager(pager).TotalItemCount(allTerms.Count());
var entries = termsPage
.Select(term => term.CreateTermEntry())
.ToList();

View File

@@ -11,35 +11,45 @@ using Orchard.Templates.Models;
using Orchard.Templates.Services;
using Orchard.Templates.ViewModels;
using Orchard.Utility.Extensions;
using Orchard.Core.Title.Models;
namespace Orchard.Templates.Drivers {
public class ShapePartDriver : ContentPartDriver<ShapePart> {
private readonly IEnumerable<ITemplateProcessor> _processors;
private readonly ITransactionManager _transactions;
private readonly IContentManager _contentManager;
public ShapePartDriver(
IEnumerable<ITemplateProcessor> processors,
ITransactionManager transactions) {
ITransactionManager transactions,
IContentManager contentManager) {
_processors = processors;
_transactions = transactions;
_contentManager = contentManager;
T = NullLocalizer.Instance;
}
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) {
return Editor(part, null, shapeHelper);
}
protected override DriverResult Editor(ShapePart part, IUpdateModel updater, dynamic shapeHelper) {
var viewModel = new ShapePartViewModel {
Template = part.Template
Template = part.Template,
RenderingMode = part.RenderingMode
};
if (updater != null
&& updater.TryUpdateModel(viewModel, Prefix, null, new[] { "AvailableLanguages" })
&& ValidateShapeName(part, updater)) {
part.Template = viewModel.Template;
part.RenderingMode = viewModel.RenderingMode;
try {
var processor = _processors.FirstOrDefault(x => String.Equals(x.Type, part.ProcessorName, StringComparison.OrdinalIgnoreCase)) ?? _processors.First();
@@ -49,12 +59,32 @@ namespace Orchard.Templates.Drivers {
updater.AddModelError("", T("Template processing error: {0}", ex.Message));
_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));
}
protected override void Exporting(ShapePart part, ExportContentContext context) {
context.Element(part.PartDefinition.Name).Add(new XCData(part.Template));
var element = context.Element(part.PartDefinition.Name);
element.Add(new XCData(part.Template));
element.SetAttributeValue("RenderingMode", part.RenderingMode.ToString());
}
protected override void Importing(ShapePart part, ImportContentContext context) {
@@ -65,8 +95,10 @@ namespace Orchard.Templates.Drivers {
var shapeElement = context.Data.Element(part.PartDefinition.Name);
if (shapeElement != null)
if (shapeElement != null) {
part.Template = shapeElement.Value;
context.ImportAttribute(part.PartDefinition.Name, "RenderingMode", value => part.RenderingMode = (RenderingMode)Enum.Parse(typeof(RenderingMode), value));
}
}
private bool ValidateShapeName(ShapePart part, IUpdateModel updater) {

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

View File

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

View File

@@ -10,6 +10,8 @@ using System.Web;
using System.Web.Mvc;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Descriptors;
using System.Web.Routing;
using Orchard.UI.Admin;
namespace Orchard.Templates.Services {
public class TemplateShapeBindingResolver : IShapeBindingResolver {
@@ -18,30 +20,50 @@ namespace Orchard.Templates.Services {
private IContentManager _contentManager;
private IContentDefinitionManager _contentDefinitionManager;
private ITemplateService _templateService;
private readonly RequestContext _requestContext;
public TemplateShapeBindingResolver(
ICacheManager cacheManager,
ISignals signals,
IContentManager contentManager,
IContentDefinitionManager contentDefinitionManager,
ITemplateService templateService
) {
ITemplateService templateService,
RequestContext requestContext) {
_cacheManager = cacheManager;
_signals = signals;
_contentManager = contentManager;
_contentDefinitionManager = contentDefinitionManager;
_templateService = templateService;
_requestContext = requestContext;
}
public bool TryGetDescriptorBinding(string shapeType, out ShapeBinding shapeBinding) {
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;
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 {
BindingName = "Templates",
Binding = ctx => CoerceHtmlString(_templateService.Execute(
templateResult.Template,
templateResult.Template,
templateResult.Name,
templateResult.Processor, ctx.Value)),
ShapeDescriptor = new ShapeDescriptor { ShapeType = shapeType }
@@ -54,7 +76,7 @@ namespace Orchard.Templates.Services {
return false;
}
private IDictionary<string, TemplateResult> BuildShapeProcessors() {
private ILookup<string, TemplateResult> BuildShapeProcessors() {
return _cacheManager.Get("Template.ShapeProcessors", true, ctx => {
ctx.Monitor(_signals.When(DefaultTemplateService.TemplatesSignal));
@@ -67,11 +89,12 @@ namespace Orchard.Templates.Services {
var allTemplates = _contentManager.Query<ShapePart>(typesWithShapePart).List();
return allTemplates.Select(x => new TemplateResult {
Name = x.Name,
Template = x.Template,
Processor = x.ProcessorName
}).ToDictionary(x => x.Name, x => x);
return allTemplates.Select(shapePart => new TemplateResult {
Name = shapePart.Name,
Template = shapePart.Template,
Processor = shapePart.ProcessorName,
RenderingMode = shapePart.RenderingMode
}).ToLookup(template => template.Name);
});
}
@@ -83,6 +106,7 @@ namespace Orchard.Templates.Services {
public string Name { get; set; }
public string Processor { 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 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("template-editor.css");
@@ -15,4 +16,8 @@
<div>
@Html.TextAreaFor(m => m.Template, new { @class = "text large code-editor" })
</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>

View File

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

View File

@@ -1,16 +1,21 @@
using System;
using System.Linq;
using System.Web;
using Orchard.ContentManagement;
using Orchard.Localization;
using System.Globalization;
using System.Text.RegularExpressions;
namespace Orchard.Tokens.Providers {
public class RequestTokens : ITokenProvider {
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IContentManager _contentManager;
private static string[] _textChainableTokens;
public RequestTokens(IWorkContextAccessor workContextAccessor, IContentManager contentManager) {
_workContextAccessor = workContextAccessor;
_contentManager = contentManager;
_textChainableTokens = new string[] { "QueryString", "Form" };
T = NullLocalizer.Instance;
}
@@ -18,8 +23,8 @@ namespace Orchard.Tokens.Providers {
public void Describe(DescribeContext context) {
context.For("Request", T("Http Request"), T("Current Http Request tokens."))
.Token("QueryString:*", T("QueryString:<element>"), T("The Query String value for the specified element."))
.Token("Form:*", T("Form:<element>"), T("The Form value for the specified element."))
.Token("QueryString:*", T("QueryString:<element>"), T("The Query String value for the specified element. To chain text, surround the <element> with parentheses, e.g. 'QueryString:(param1)'."))
.Token("Form:*", T("Form:<element>"), T("The Form value for the specified element. To chain text, surround the <element> with parentheses, e.g. 'Form:(param1)'."))
.Token("Route:*", T("Route:<key>"), T("The Route value for the specified key."))
.Token("Content", T("Content"), T("The request routed Content Item."), "Content")
;
@@ -29,20 +34,30 @@ namespace Orchard.Tokens.Providers {
if (_workContextAccessor.GetContext().HttpContext == null) {
return;
}
/* Supported Syntaxes for Request and Form tokens are:
* 1. QueryString:(param1) or Form:(param1)
* 2. QueryString:param1 or Form:param1
* 3. QueryString:(param1).SomeOtherTextToken or Form:(param1).SomeOtherTextToken
*
* If you want to Chain TextTokens you have to use the 3rd syntax
* the element, here param1, has been surrounded with parentheses in order to preserve backward compatibility.
*/
context.For("Request", _workContextAccessor.GetContext().HttpContext.Request)
.Token(
token => token.StartsWith("QueryString:", StringComparison.OrdinalIgnoreCase) ? token.Substring("QueryString:".Length) : null,
(token, request) => request.QueryString.Get(token)
FilterTokenParam,
(token, request) => {
return request.QueryString.Get(token);
}
)
.Token(
token => token.StartsWith("Form:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Form:".Length) : null,
FilterTokenParam,
(token, request) => request.Form.Get(token)
)
.Token(
token => token.StartsWith("Route:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Route:".Length) : null,
(token, request) => GetRouteValue(token, request)
)
.Chain(FilterChainParam, "Text", (token, request) => request.QueryString.Get(token))
.Token("Content",
(request) => DisplayText(GetRoutedContentItem(request))
)
@@ -98,5 +113,53 @@ namespace Orchard.Tokens.Providers {
return _contentManager.GetItemMetadata(content).DisplayText;
}
private static string FilterTokenParam(string token) {
string tokenPrefix;
int chainIndex, tokenLength;
if (token.IndexOf(":") == -1) {
return null;
}
tokenPrefix = token.Substring(0, token.IndexOf(":"));
if (!_textChainableTokens.Contains(tokenPrefix, StringComparer.OrdinalIgnoreCase)) {
return null;
}
// use ")." as chars combination to discover the end of the parameter
chainIndex = token.IndexOf(").") + 1;
tokenLength = (tokenPrefix + ":").Length;
if (chainIndex == 0) {// ")." has not be found
return token.Substring(tokenLength).Trim(new char[] { '(', ')' });
}
if (!token.StartsWith((tokenPrefix + ":"), StringComparison.OrdinalIgnoreCase) || chainIndex <= tokenLength) {
return null;
}
return token.Substring(tokenLength, chainIndex - tokenLength).Trim(new char[] { '(', ')' });
}
private static Tuple<string, string> FilterChainParam(string token) {
string tokenPrefix;
int chainIndex, tokenLength;
if (token.IndexOf(":") == -1) {
return null;
}
tokenPrefix = token.Substring(0, token.IndexOf(":"));
if (!_textChainableTokens.Contains(tokenPrefix, StringComparer.OrdinalIgnoreCase)) {
return null;
}
// use ")." as chars combination to discover the end of the parameter
chainIndex = token.IndexOf(").") + 1;
tokenLength = (tokenPrefix + ":").Length;
if (chainIndex == 0) { // ")." has not be found
return new Tuple<string, string>(token.Substring(tokenLength).Trim(new char[] { '(', ')' }), token.Length.ToString());
}
if (!token.StartsWith((tokenPrefix + ":"), StringComparison.OrdinalIgnoreCase) || chainIndex <= tokenLength) {
return null;
}
return new Tuple<string, string>(token.Substring(tokenLength, chainIndex - tokenLength).Trim(new char[] { '(', ')' }), token.Substring(chainIndex + 1));
}
}
}
}

View File

@@ -1,37 +1,13 @@
using System;
using System.Linq;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Navigation.Services;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Widgets.Models;
using Orchard.Commands;
using Orchard.Widgets.Services;
namespace Orchard.Widgets.Commands {
public class WidgetCommands : DefaultOrchardCommandHandler {
private readonly IWidgetsService _widgetsService;
private readonly ISiteService _siteService;
private readonly IMembershipService _membershipService;
private readonly IMenuService _menuService;
private readonly IContentManager _contentManager;
private const string LoremIpsum = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
private readonly IWidgetCommandsService _widgetCommandsService;
public WidgetCommands(
IWidgetsService widgetsService,
ISiteService siteService,
IMembershipService membershipService,
IMenuService menuService,
IContentManager contentManager) {
_widgetsService = widgetsService;
_siteService = siteService;
_membershipService = membershipService;
_menuService = menuService;
_contentManager = contentManager;
IWidgetCommandsService widgetCommandsService) {
_widgetCommandsService = widgetCommandsService;
RenderTitle = true;
}
@@ -76,67 +52,15 @@ namespace Orchard.Widgets.Commands {
[CommandHelp("widget create <type> /Title:<title> /Name:<name> /Zone:<zone> /Position:<position> /Layer:<layer> [/Identity:<identity>] [/RenderTitle:true|false] [/Owner:<owner>] [/Text:<text>] [/UseLoremIpsumText:true|false] [/MenuName:<name>]\r\n\t" + "Creates a new widget")]
[OrchardSwitches("Title,Name,Zone,Position,Layer,Identity,Owner,Text,UseLoremIpsumText,MenuName,RenderTitle")]
public void Create(string type) {
var widgetTypeNames = _widgetsService.GetWidgetTypeNames().ToList();
if (!widgetTypeNames.Contains(type)) {
Context.Output.WriteLine(T("Creating widget failed : type {0} was not found. Supported widget types are: {1}.",
type,
string.Join(" ", widgetTypeNames)));
var widget = _widgetCommandsService.CreateBaseWidget(
Context, type, Title, Name, Zone, Position, Layer, Identity, RenderTitle, Owner, Text, UseLoremIpsumText, MenuName);
if (widget == null) {
return;
}
var layer = GetLayer(Layer);
if (layer == null) {
Context.Output.WriteLine(T("Creating widget failed : layer {0} was not found.", Layer));
return;
}
var widget = _widgetsService.CreateWidget(layer.ContentItem.Id, type, T(Title).Text, Position, Zone);
if (!String.IsNullOrWhiteSpace(Name)) {
widget.Name = Name.Trim();
}
var text = String.Empty;
if (widget.Has<BodyPart>()) {
if (UseLoremIpsumText) {
text = T(LoremIpsum).Text;
}
else {
if (!String.IsNullOrEmpty(Text)) {
text = Text;
}
}
widget.As<BodyPart>().Text = text;
}
widget.RenderTitle = RenderTitle;
if(widget.Has<MenuWidgetPart>() && !String.IsNullOrWhiteSpace(MenuName)) {
var menu = _menuService.GetMenu(MenuName);
if(menu != null) {
widget.RenderTitle = false;
widget.As<MenuWidgetPart>().MenuContentItemId = menu.ContentItem.Id;
}
}
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}
var owner = _membershipService.GetUser(Owner);
widget.As<ICommonPart>().Owner = owner;
if (widget.Has<IdentityPart>() && !String.IsNullOrEmpty(Identity)) {
widget.As<IdentityPart>().Identifier = Identity;
}
_contentManager.Publish(widget.ContentItem);
_widgetCommandsService.Publish(widget);
Context.Output.WriteLine(T("Widget created successfully.").Text);
}
private LayerPart GetLayer(string layer) {
var layers = _widgetsService.GetLayers();
return layers.FirstOrDefault(layerPart => String.Equals(layerPart.Name, layer, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@@ -116,6 +116,7 @@
<Compile Include="Filters\WidgetFilter.cs" />
<Compile Include="ResourceManifest.cs" />
<Compile Include="Conditions\ContentDisplayedRuleProvider.cs" />
<Compile Include="Services\IWidgetCommandsService.cs" />
<Compile Include="Services\RuleManager.cs" />
<Compile Include="Services\DefaultLayerEvaluationService.cs" />
<Compile Include="Services\ILayerEvaluationService.cs" />
@@ -124,6 +125,7 @@
<Compile Include="Services\IWidgetsService.cs" />
<Compile Include="Services\LayerResolverSelector.cs" />
<Compile Include="Services\RuleContext.cs" />
<Compile Include="Services\WidgetCommandsService.cs" />
<Compile Include="Services\WidgetsService.cs" />
<Compile Include="Shapes.cs" />
<Compile Include="ViewModels\WidgetElementViewModel.cs" />

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard.Commands;
using Orchard.Widgets.Models;
namespace Orchard.Widgets.Services {
public interface IWidgetCommandsService : IDependency {
WidgetPart CreateBaseWidget(CommandContext context, string type, string title, string name, string zone, string position, string layer, string identity, bool renderTitle, string owner, string text, bool useLoremIpsumText, string menuName);
void Publish(WidgetPart widget);
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Linq;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Navigation.Services;
using Orchard.Localization;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Widgets.Models;
namespace Orchard.Widgets.Services {
public class WidgetCommandsService : IWidgetCommandsService {
private readonly IMenuService _menuService;
private readonly IWidgetsService _widgetsService;
private readonly ISiteService _siteService;
private readonly IMembershipService _membershipService;
private readonly IContentManager _contentManager;
private const string LoremIpsum = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
public WidgetCommandsService(
IWidgetsService widgetsService,
IMenuService menuService,
ISiteService siteService,
IMembershipService membershipService,
IContentManager contentManager) {
_siteService = siteService;
_membershipService = membershipService;
_widgetsService = widgetsService;
_menuService = menuService;
_contentManager = contentManager;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public WidgetPart CreateBaseWidget(CommandContext context, string type, string title, string name, string zone, string position, string layer, string identity, bool renderTitle, string owner, string text, bool useLoremIpsumText, string menuName) {
var widgetTypeNames = _widgetsService.GetWidgetTypeNames().ToList();
if (!widgetTypeNames.Contains(type)) {
context.Output.WriteLine(T("Creating widget failed : type {0} was not found. Supported widget types are: {1}.",
type,
string.Join(" ", widgetTypeNames)));
return null;
}
var layerPart = GetLayer(layer);
if (layerPart == null) {
context.Output.WriteLine(T("Creating widget failed : layer {0} was not found.", layer));
return null;
}
var widget = _widgetsService.CreateWidget(layerPart.ContentItem.Id, type, T(title).Text, position, zone);
if (!String.IsNullOrWhiteSpace(name)) {
widget.Name = name.Trim();
}
var widgetText = String.Empty;
if (widget.Has<BodyPart>()) {
if (useLoremIpsumText) {
widgetText = T(LoremIpsum).Text;
}
else {
if (!String.IsNullOrEmpty(text)) {
widgetText = text;
}
}
widget.As<BodyPart>().Text = text;
}
widget.RenderTitle = renderTitle;
if (widget.Has<MenuWidgetPart>() && !String.IsNullOrWhiteSpace(menuName)) {
var menu = _menuService.GetMenu(menuName);
if (menu != null) {
widget.RenderTitle = false;
widget.As<MenuWidgetPart>().MenuContentItemId = menu.ContentItem.Id;
}
}
if (String.IsNullOrEmpty(owner)) {
owner = _siteService.GetSiteSettings().SuperUser;
}
var widgetOwner = _membershipService.GetUser(owner);
widget.As<ICommonPart>().Owner = widgetOwner;
if (widget.Has<IdentityPart>() && !String.IsNullOrEmpty(identity)) {
widget.As<IdentityPart>().Identifier = identity;
}
return widget;
}
private LayerPart GetLayer(string layer) {
var layers = _widgetsService.GetLayers();
return layers.FirstOrDefault(layerPart => String.Equals(layerPart.Name, layer, StringComparison.OrdinalIgnoreCase));
}
public void Publish(WidgetPart widget) {
_contentManager.Publish(widget.ContentItem);
}
}
}

View File

@@ -9,8 +9,8 @@
var returnUrl = Request.RawUrl;
}
<div id="widgets-layers-control" class="widgets-container">
@if (layers.Any()) {
using (Html.BeginForm("index", "admin", FormMethod.Get, new { area = "Orchard.Widgets" })) {
@using (Html.BeginForm("index", "admin", FormMethod.Get, new { area = "Orchard.Widgets" })) {
if (layers.Any()) {
<fieldset class="bulk-actions-auto">
<label for="layerId">@T("Current Layer:")</label>
<select id="layerId" name="layerId">
@@ -22,23 +22,20 @@
@Html.Link(T("Edit").Text, Url.Action("EditLayer", "Admin", new { area = "Orchard.Widgets", id = Model.CurrentLayer.Id, returnUrl }), new { @class = "button" })
</fieldset>
}
}
<div id="widgets-layer-add">
@Html.Link(T("Add a new layer...").Text, Url.Action("AddLayer", "Admin", new { area = "Orchard.Widgets", returnUrl }))
</div>
@if (cultures.Count() > 1) {
using (Html.BeginForm("index", "admin", FormMethod.Get, new { area = "Orchard.Widgets" })) {
<fieldset class="bulk-actions-auto">
<label for="culture">@T("Current Culture:")</label>
<select id="culture" name="culture">
@Html.SelectOption((string)Model.CurrentCulture, "", T("any (show all)").ToString())
@foreach (var culture in cultures) {
@Html.SelectOption((string)Model.CurrentCulture, (string)culture, System.Globalization.CultureInfo.GetCultureInfo(culture).DisplayName)
}
</select>
<button type="submit" class="apply-bulk-actions-auto">@T("Show")</button>
</fieldset>
<div id="widgets-layer-add">
@Html.Link(T("Add a new layer...").Text, Url.Action("AddLayer", "Admin", new { area = "Orchard.Widgets", returnUrl }))
</div>
if (cultures.Count() > 1) {
<fieldset class="bulk-actions-auto">
<label for="culture">@T("Current Culture:")</label>
<select id="culture" name="culture">
@Html.SelectOption((string)Model.CurrentCulture, "", T("any (show all)").ToString())
@foreach (var culture in cultures) {
@Html.SelectOption((string)Model.CurrentCulture, (string)culture, System.Globalization.CultureInfo.GetCultureInfo(culture).DisplayName)
}
</select>
<button type="submit" class="apply-bulk-actions-auto">@T("Show")</button>
</fieldset>
}
}

View File

@@ -6,5 +6,5 @@ Version: 1.10.1
OrchardVersion: 1.9
Description: The TinyMCE module enables rich text contents to be created using a "What You See Is What You Get" user interface.
FeatureDescription: TinyMCE HTML WYSIWYG editor.
FeatureDependencies: Orchard.Resources
Dependencies: Orchard.Resources
Category: Input Editor