mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-07-15 19:19:16 +08:00
Merge branch '1.10.x' into issue/8773
# Conflicts: # src/Orchard.Web/Core/Common/Views/Body-Large.Editor.cshtml # src/Orchard.Web/Core/Common/Views/Body-Small.Editor.cshtml # src/Orchard.Web/Core/Common/Views/Body-Textarea.Editor.cshtml # src/Orchard.Web/Core/Common/Views/Body-Wide.Editor.cshtml # src/Orchard.Web/Core/Common/Views/EditorTemplates/Fields.Common.Text.Edit.cshtml # src/Orchard.Web/Modules/Orchard.Email/Migrations.cs # src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs # src/Orchard.Web/Modules/Orchard.Email/Orchard.Email.csproj # src/Orchard.Web/Modules/Orchard.Email/Services/SmtpMessageChannel.cs # src/Orchard.Web/Modules/Orchard.Email/packages.config # src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.js # src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.min.js # src/Orchard.Web/Modules/Orchard.Layouts/Styles/LayoutEditor.css # src/Orchard.Web/Modules/Orchard.Layouts/Styles/LayoutEditor.min.css # src/Orchard.Web/Modules/Orchard.MediaLibrary/Handlers/MediaLibraryPickerFieldHandler.cs # src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs # src/Orchard.Web/Modules/Orchard.Taxonomies/Settings/TaxonomyFieldEditorEvents.cs
This commit is contained in:
commit
bfe1e6aa01
@ -7,6 +7,8 @@ using Orchard.ContentManagement;
|
||||
namespace Orchard.Tests.ContentManagement {
|
||||
[TestFixture]
|
||||
public class XmlHelperTests {
|
||||
private const string _testGuidString = "98f3dc0a-01c3-4975-bd52-1b4f5a678d73";
|
||||
|
||||
[Test]
|
||||
public void AddEl() {
|
||||
var el = new XElement("data");
|
||||
@ -84,6 +86,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(el.Attribute("foo").Value, Is.EqualTo("1970-05-21T13:55:21.934Z"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GuidToAttribute() {
|
||||
var el = new XElement("data");
|
||||
el.Attr("guid", new Guid(_testGuidString));
|
||||
|
||||
Assert.That(el.Attribute("guid").Value, Is.EqualTo(_testGuidString));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoubleFloatDecimalToAttribute() {
|
||||
var el = new XElement("data");
|
||||
@ -150,6 +160,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(el.Element("decimal").Value, Is.EqualTo("12.458"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GuidToElement() {
|
||||
var el = new XElement("data");
|
||||
el.El("guid", new Guid(_testGuidString));
|
||||
|
||||
Assert.That(el.Element("guid").Value, Is.EqualTo(_testGuidString));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadElement() {
|
||||
var el = XElement.Parse("<data><foo>bar</foo></data>");
|
||||
@ -168,12 +186,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
ADouble = 12.345D,
|
||||
AFloat = 23.456F,
|
||||
ADecimal = 34.567M,
|
||||
AGuid = new Guid(_testGuidString),
|
||||
ANullableInt = 42,
|
||||
ANullableBoolean = true,
|
||||
ANullableDate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc),
|
||||
ANullableDouble = 12.345D,
|
||||
ANullableFloat = 23.456F,
|
||||
ANullableDecimal = 34.567M
|
||||
ANullableDecimal = 34.567M,
|
||||
ANullableGuid = new Guid(_testGuidString)
|
||||
};
|
||||
var el = new XElement("data");
|
||||
el.With(target)
|
||||
@ -184,12 +204,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
.ToAttr(t => t.ADouble)
|
||||
.ToAttr(t => t.AFloat)
|
||||
.ToAttr(t => t.ADecimal)
|
||||
.ToAttr(t => t.AGuid)
|
||||
.ToAttr(t => t.ANullableInt)
|
||||
.ToAttr(t => t.ANullableBoolean)
|
||||
.ToAttr(t => t.ANullableDate)
|
||||
.ToAttr(t => t.ANullableDouble)
|
||||
.ToAttr(t => t.ANullableFloat)
|
||||
.ToAttr(t => t.ANullableDecimal);
|
||||
.ToAttr(t => t.ANullableDecimal)
|
||||
.ToAttr(t => t.ANullableGuid);
|
||||
|
||||
|
||||
Assert.That(el.Attr("AString"), Is.EqualTo("foo"));
|
||||
@ -199,12 +221,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(el.Attr("ADouble"), Is.EqualTo("12.345"));
|
||||
Assert.That(el.Attr("AFloat"), Is.EqualTo("23.456"));
|
||||
Assert.That(el.Attr("ADecimal"), Is.EqualTo("34.567"));
|
||||
Assert.That(el.Attr("AGuid"), Is.EqualTo(_testGuidString));
|
||||
Assert.That(el.Attr("ANullableInt"), Is.EqualTo("42"));
|
||||
Assert.That(el.Attr("ANullableBoolean"), Is.EqualTo("true"));
|
||||
Assert.That(el.Attr("ANullableDate"), Is.EqualTo("1970-05-21T13:55:21.934Z"));
|
||||
Assert.That(el.Attr("ANullableDouble"), Is.EqualTo("12.345"));
|
||||
Assert.That(el.Attr("ANullableFloat"), Is.EqualTo("23.456"));
|
||||
Assert.That(el.Attr("ANullableDecimal"), Is.EqualTo("34.567"));
|
||||
Assert.That(el.Attr("ANullableGuid"), Is.EqualTo(_testGuidString));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -214,10 +238,10 @@ namespace Orchard.Tests.ContentManagement {
|
||||
XElement.Parse(
|
||||
"<data AString=\"foo\" AnInt=\"42\" ABoolean=\"true\" " +
|
||||
"ADate=\"1970-05-21T13:55:21.934Z\" ADouble=\"12.345\" " +
|
||||
"AFloat=\"23.456\" ADecimal=\"34.567\" " +
|
||||
$"AFloat=\"23.456\" ADecimal=\"34.567\" AGuid=\"{_testGuidString}\" " +
|
||||
"ANullableInt=\"42\" ANullableBoolean=\"true\" " +
|
||||
"ANullableDate=\"1970-05-21T13:55:21.934Z\" ANullableDouble=\"12.345\" " +
|
||||
"ANullableFloat=\"23.456\" ANullableDecimal=\"34.567\"/>");
|
||||
$"ANullableFloat=\"23.456\" ANullableDecimal=\"34.567\" ANullableGuid=\"{_testGuidString}\"/>");
|
||||
el.With(target)
|
||||
.FromAttr(t => t.AString)
|
||||
.FromAttr(t => t.AnInt)
|
||||
@ -226,12 +250,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
.FromAttr(t => t.ADouble)
|
||||
.FromAttr(t => t.AFloat)
|
||||
.FromAttr(t => t.ADecimal)
|
||||
.FromAttr(t => t.AGuid)
|
||||
.FromAttr(t => t.ANullableInt)
|
||||
.FromAttr(t => t.ANullableBoolean)
|
||||
.FromAttr(t => t.ANullableDate)
|
||||
.FromAttr(t => t.ANullableDouble)
|
||||
.FromAttr(t => t.ANullableFloat)
|
||||
.FromAttr(t => t.ANullableDecimal);
|
||||
.FromAttr(t => t.ANullableDecimal)
|
||||
.FromAttr(t => t.ANullableGuid);
|
||||
|
||||
Assert.That(target.AString, Is.EqualTo("foo"));
|
||||
Assert.That(target.AnInt, Is.EqualTo(42));
|
||||
@ -240,12 +266,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(target.ADouble, Is.EqualTo(12.345D));
|
||||
Assert.That(target.AFloat, Is.EqualTo(23.456F));
|
||||
Assert.That(target.ADecimal, Is.EqualTo(34.567M));
|
||||
Assert.That(target.AGuid, Is.EqualTo(new Guid(_testGuidString)));
|
||||
Assert.That(target.ANullableInt, Is.EqualTo(42));
|
||||
Assert.That(target.ANullableBoolean, Is.True);
|
||||
Assert.That(target.ANullableDate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)));
|
||||
Assert.That(target.ANullableDouble, Is.EqualTo(12.345D));
|
||||
Assert.That(target.ANullableFloat, Is.EqualTo(23.456F));
|
||||
Assert.That(target.ANullableDecimal, Is.EqualTo(34.567M));
|
||||
Assert.That(target.ANullableGuid, Is.EqualTo(new Guid(_testGuidString)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -258,12 +286,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
ADouble = 12.345D,
|
||||
AFloat = 23.456F,
|
||||
ADecimal = 34.567M,
|
||||
AGuid = new Guid(_testGuidString),
|
||||
ANullableInt = 42,
|
||||
ANullableBoolean = true,
|
||||
ANullableDate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc),
|
||||
ANullableDouble = 12.345D,
|
||||
ANullableFloat = 23.456F,
|
||||
ANullableDecimal = 34.567M
|
||||
ANullableDecimal = 34.567M,
|
||||
ANullableGuid = new Guid(_testGuidString)
|
||||
};
|
||||
var el = new XElement("data");
|
||||
el.With(target)
|
||||
@ -274,12 +304,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
.FromAttr(t => t.ADouble)
|
||||
.FromAttr(t => t.AFloat)
|
||||
.FromAttr(t => t.ADecimal)
|
||||
.FromAttr(t => t.AGuid)
|
||||
.FromAttr(t => t.ANullableInt)
|
||||
.FromAttr(t => t.ANullableBoolean)
|
||||
.FromAttr(t => t.ANullableDate)
|
||||
.FromAttr(t => t.ANullableDouble)
|
||||
.FromAttr(t => t.ANullableFloat)
|
||||
.FromAttr(t => t.ANullableDecimal);
|
||||
.FromAttr(t => t.ANullableDecimal)
|
||||
.FromAttr(t => t.ANullableGuid);
|
||||
|
||||
Assert.That(target.AString, Is.EqualTo("foo"));
|
||||
Assert.That(target.AnInt, Is.EqualTo(42));
|
||||
@ -288,12 +320,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(target.ADouble, Is.EqualTo(12.345D));
|
||||
Assert.That(target.AFloat, Is.EqualTo(23.456F));
|
||||
Assert.That(target.ADecimal, Is.EqualTo(34.567M));
|
||||
Assert.That(target.AGuid, Is.EqualTo(new Guid(_testGuidString)));
|
||||
Assert.That(target.ANullableInt, Is.EqualTo(42));
|
||||
Assert.That(target.ANullableBoolean, Is.True);
|
||||
Assert.That(target.ANullableDate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)));
|
||||
Assert.That(target.ANullableDouble, Is.EqualTo(12.345D));
|
||||
Assert.That(target.ANullableFloat, Is.EqualTo(23.456F));
|
||||
Assert.That(target.ANullableDecimal, Is.EqualTo(34.567M));
|
||||
Assert.That(target.ANullableGuid, Is.EqualTo(new Guid(_testGuidString)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -339,7 +373,8 @@ namespace Orchard.Tests.ContentManagement {
|
||||
.ToAttr(t => t.ANullableDate)
|
||||
.ToAttr(t => t.ANullableDouble)
|
||||
.ToAttr(t => t.ANullableFloat)
|
||||
.ToAttr(t => t.ANullableDecimal);
|
||||
.ToAttr(t => t.ANullableDecimal)
|
||||
.ToAttr(t => t.ANullableGuid);
|
||||
|
||||
Assert.That(el.Attr("AString"), Is.EqualTo(""));
|
||||
Assert.That(el.Attr("ANullableInt"), Is.EqualTo("null"));
|
||||
@ -348,6 +383,7 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(el.Attr("ANullableDouble"), Is.EqualTo("null"));
|
||||
Assert.That(el.Attr("ANullableFloat"), Is.EqualTo("null"));
|
||||
Assert.That(el.Attr("ANullableDecimal"), Is.EqualTo("null"));
|
||||
Assert.That(el.Attr("ANullableGuid"), Is.EqualTo("null"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -357,7 +393,7 @@ namespace Orchard.Tests.ContentManagement {
|
||||
XElement.Parse(
|
||||
"<data AString=\"null\" ANullableInt=\"null\" ANullableBoolean=\"null\" " +
|
||||
"ANullableDate=\"null\" ANullableDouble=\"null\" " +
|
||||
"ANullableFloat=\"null\" ANullableDecimal=\"null\"/>");
|
||||
"ANullableFloat=\"null\" ANullableDecimal=\"null\" ANullableGuid=\"null\"/>");
|
||||
el.With(target)
|
||||
.FromAttr(t => t.AString)
|
||||
.FromAttr(t => t.ANullableInt)
|
||||
@ -365,7 +401,8 @@ namespace Orchard.Tests.ContentManagement {
|
||||
.FromAttr(t => t.ANullableDate)
|
||||
.FromAttr(t => t.ANullableDouble)
|
||||
.FromAttr(t => t.ANullableFloat)
|
||||
.FromAttr(t => t.ANullableDecimal);
|
||||
.FromAttr(t => t.ANullableDecimal)
|
||||
.FromAttr(t => t.ANullableGuid);
|
||||
|
||||
Assert.That(target.AString, Is.EqualTo("null"));
|
||||
Assert.That(target.ANullableInt, Is.Null);
|
||||
@ -374,6 +411,7 @@ namespace Orchard.Tests.ContentManagement {
|
||||
Assert.That(target.ANullableDouble, Is.Null);
|
||||
Assert.That(target.ANullableFloat, Is.Null);
|
||||
Assert.That(target.ANullableDecimal, Is.Null);
|
||||
Assert.That(target.ANullableGuid, Is.Null);
|
||||
}
|
||||
|
||||
private class Target {
|
||||
@ -384,12 +422,14 @@ namespace Orchard.Tests.ContentManagement {
|
||||
public double ADouble { get; set; }
|
||||
public float AFloat { get; set; }
|
||||
public decimal ADecimal { get; set; }
|
||||
public Guid AGuid { get; set; }
|
||||
public int? ANullableInt { get; set; }
|
||||
public bool? ANullableBoolean { get; set; }
|
||||
public DateTime? ANullableDate { get; set; }
|
||||
public double? ANullableDouble { get; set; }
|
||||
public float? ANullableFloat { get; set; }
|
||||
public decimal? ANullableDecimal { get; set; }
|
||||
public Guid? ANullableGuid { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -142,7 +142,6 @@
|
||||
<HintPath>..\..\lib\sqlce\System.Data.SqlServerCe.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<Reference Include="System.Net.Http.Formatting, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -10,6 +10,7 @@ using Orchard.Core.Common.Settings;
|
||||
using Orchard.Core.Common.ViewModels;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Services;
|
||||
using Orchard.Utility.Extensions;
|
||||
|
||||
namespace Orchard.Core.Common.Drivers {
|
||||
public class TextFieldDriver : ContentFieldDriver<TextField> {
|
||||
@ -71,6 +72,16 @@ namespace Orchard.Core.Common.Drivers {
|
||||
if (settings.Required && String.IsNullOrWhiteSpace(field.Value)) {
|
||||
updater.AddModelError("Text", T("The {0} field is required.", T(field.DisplayName)));
|
||||
}
|
||||
|
||||
if (settings.MaxLength > 0) {
|
||||
|
||||
var value = new HtmlString(_htmlFilters.Aggregate(field.Value, (text, filter) => filter.ProcessContent(text, settings.Flavor)))
|
||||
.ToString().RemoveTags();
|
||||
|
||||
if (value.Length > settings.MaxLength) {
|
||||
updater.AddModelError("Text", T("The maximum allowed length for the field {0} is {1}", T(field.DisplayName), settings.MaxLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Editor(part, field, shapeHelper);
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Orchard.Core.Common.Settings {
|
||||
|
||||
@ -9,5 +10,8 @@ namespace Orchard.Core.Common.Settings {
|
||||
public string Hint { get; set; }
|
||||
public string Placeholder { get; set; }
|
||||
public string DefaultValue { get; set; }
|
||||
[Range(0, int.MaxValue)]
|
||||
[DisplayName("Maximum Length")]
|
||||
public int MaxLength { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,10 @@ namespace Orchard.Core.Common.Settings {
|
||||
builder.WithSetting("TextFieldSettings.Required", model.Settings.Required.ToString(CultureInfo.InvariantCulture));
|
||||
builder.WithSetting("TextFieldSettings.Placeholder", model.Settings.Placeholder);
|
||||
builder.WithSetting("TextFieldSettings.DefaultValue", model.Settings.DefaultValue);
|
||||
|
||||
yield return DefinitionTemplate(model);
|
||||
builder.WithSetting("TextFieldSettings.MaxLength", model.Settings.MaxLength.ToString());
|
||||
}
|
||||
|
||||
yield return DefinitionTemplate(model);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
@{
|
||||
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
|
||||
var htmlAttributes = (Dictionary<string, object>)Model.HtmlAttributes;
|
||||
|
||||
var htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", "text large"}
|
||||
};
|
||||
if (htmlAttributes == null) {
|
||||
htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", "text large"}
|
||||
};
|
||||
}
|
||||
|
||||
if (Model.Required == true) {
|
||||
htmlAttributes["required"] = "required";
|
||||
|
@ -1,9 +1,12 @@
|
||||
@{
|
||||
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
|
||||
var htmlAttributes = (Dictionary<string, object>)Model.HtmlAttributes;
|
||||
|
||||
var htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", "text small"}
|
||||
};
|
||||
if (htmlAttributes == null) {
|
||||
htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", "text small"}
|
||||
};
|
||||
}
|
||||
|
||||
if (Model.Required == true) {
|
||||
htmlAttributes["required"] = "required";
|
||||
|
@ -1,7 +1,10 @@
|
||||
@{
|
||||
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
|
||||
var htmlAttributes = (Dictionary<string, object>)Model.HtmlAttributes;
|
||||
|
||||
var htmlAttributes = new Dictionary<string, object>();
|
||||
if (htmlAttributes == null) {
|
||||
htmlAttributes = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
if (Model.Required == true) {
|
||||
htmlAttributes["required"] = "required";
|
||||
|
@ -1,9 +1,12 @@
|
||||
@{
|
||||
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
|
||||
var htmlAttributes = (Dictionary<string, object>)Model.HtmlAttributes;
|
||||
|
||||
var htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", "text medium"}
|
||||
};
|
||||
if (htmlAttributes == null) {
|
||||
htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", "text medium"}
|
||||
};
|
||||
}
|
||||
|
||||
if (Model.Required == true) {
|
||||
htmlAttributes["required"] = "required";
|
||||
|
@ -3,10 +3,16 @@
|
||||
var propertyName = Model.PropertyName != null ? (string)Model.PropertyName : "Text";
|
||||
|
||||
string editorFlavor = Model.EditorFlavor;
|
||||
var htmlAttributes = (Dictionary<string, object>)Model.HtmlAttributes;
|
||||
|
||||
var htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", editorFlavor.HtmlClassify()}
|
||||
};
|
||||
if (htmlAttributes == null) {
|
||||
htmlAttributes = new Dictionary<string, object> {
|
||||
{"class", editorFlavor.HtmlClassify()}};
|
||||
|
||||
}
|
||||
else {
|
||||
htmlAttributes["class"] = editorFlavor.HtmlClassify();
|
||||
}
|
||||
|
||||
if (Model.Required == true) {
|
||||
htmlAttributes["required"] = "required";
|
||||
|
@ -7,6 +7,12 @@
|
||||
@Html.ValidationMessageFor(m => m.Settings.Flavor)
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="@Html.FieldIdFor(m => m.Settings.MaxLength)">@T("Maximum length")</label>
|
||||
@Html.EditorFor(m => m.Settings.MaxLength, new { htmlAttributes = new { min = 0 } })
|
||||
<span class="hint">@T("Maximum length allowed for this field. Setting the value to 0 means unlimited length.")</span>
|
||||
@Html.ValidationMessageFor(m => m.Settings.MaxLength)
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div>
|
||||
@Html.CheckBoxFor(m => m.Settings.Required) <label for="@Html.FieldIdFor(m => m.Settings.Required)" class="forcheckbox">@T("Required")</label>
|
||||
@ -29,4 +35,4 @@
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@Display.DefinitionTemplate(TemplateName: "TextFieldDefaultValueEditor", Model: Model)
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
@ -1,17 +1,25 @@
|
||||
@model Orchard.Core.Common.ViewModels.TextFieldDriverViewModel
|
||||
@{
|
||||
var maxLength = Model.Settings.MaxLength > 0 ? Model.Settings.MaxLength.ToString() : "";
|
||||
}
|
||||
|
||||
<fieldset>
|
||||
<label for="@Html.FieldIdFor(m => m.Text)" @if(Model.Settings.Required) { <text>class="required"</text> }>@Model.Field.DisplayName</label>
|
||||
<label for="@Html.FieldIdFor(m => m.Text)" @if (Model.Settings.Required) { <text> class="required" </text> }>@Model.Field.DisplayName</label>
|
||||
@if (String.IsNullOrWhiteSpace(Model.Settings.Flavor)) {
|
||||
@(Model.Settings.Required
|
||||
? Html.TextBoxFor(m => m.Text, new { @class = "text", required = "required", placeholder = Model.Settings.Placeholder })
|
||||
: Html.TextBoxFor(m => m.Text, new { @class = "text", placeholder = Model.Settings.Placeholder }))
|
||||
? Html.TextBoxFor(m => m.Text, new { @class = "text", maxlength = maxLength, placeholder = Model.Settings.Placeholder, required = "required" })
|
||||
: Html.TextBoxFor(m => m.Text, new { @class = "text", maxlength = maxLength, placeholder = Model.Settings.Placeholder }))
|
||||
@Html.ValidationMessageFor(m => m.Text)
|
||||
}
|
||||
else {
|
||||
@Display.Body_Editor(Text: Model.Text, EditorFlavor: Model.Settings.Flavor, Required: Model.Settings.Required, ContentItem: Model.ContentItem, Placeholder: Model.Settings.Placeholder, Field: Model.Field)
|
||||
var htmlAttributes = new Dictionary<string, object> {
|
||||
{"maxlength", maxLength}
|
||||
};
|
||||
|
||||
@Display.Body_Editor(Text: Model.Text, EditorFlavor: Model.Settings.Flavor, Required: Model.Settings.Required,
|
||||
ContentItem: Model.ContentItem, Placeholder: Model.Settings.Placeholder, Field: Model.Field, HtmlAttributes: htmlAttributes)
|
||||
}
|
||||
@if (HasText(Model.Settings.Hint)) {
|
||||
<span class="hint">@Model.Settings.Hint</span>
|
||||
<span class="hint">@Model.Settings.Hint</span>
|
||||
}
|
||||
</fieldset>
|
@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Core.Navigation.Models;
|
||||
|
||||
namespace Orchard.Core.Navigation.ViewModels {
|
||||
public class MenuPartViewModel {
|
||||
@ -7,7 +9,8 @@ namespace Orchard.Core.Navigation.ViewModels {
|
||||
public int CurrentMenuId { get; set; }
|
||||
public bool OnMenu { get; set; }
|
||||
|
||||
public ContentItem ContentItem { get; set; }
|
||||
public ContentItem ContentItem { get; set; }
|
||||
[StringLength(MenuPartRecord.DefaultMenuTextLength)]
|
||||
public string MenuText { get; set; }
|
||||
}
|
||||
}
|
@ -297,6 +297,8 @@
|
||||
<Compile Include="Title\Migrations.cs" />
|
||||
<Compile Include="Title\Models\TitlePart.cs" />
|
||||
<Compile Include="Title\Models\TitlePartRecord.cs" />
|
||||
<Compile Include="Title\Settings\TitlePartSettings.cs" />
|
||||
<Compile Include="Title\Settings\TitlePartSettingsEvents.cs" />
|
||||
<Compile Include="XmlRpc\Controllers\HomeController.cs" />
|
||||
<Compile Include="XmlRpc\Controllers\LiveWriterController.cs" />
|
||||
<Compile Include="XmlRpc\IXmlRpcDriver.cs" />
|
||||
@ -614,6 +616,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<Content Include="Title\Views\DefinitionTemplates\TitlePartSettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
|
@ -1,7 +1,9 @@
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Core.Common.Settings;
|
||||
using Orchard.Core.Title.Models;
|
||||
using Orchard.Core.Title.Settings;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.Core.Title.Drivers {
|
||||
@ -33,7 +35,14 @@ namespace Orchard.Core.Title.Drivers {
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(TitlePart part, IUpdateModel updater, dynamic shapeHelper) {
|
||||
updater.TryUpdateModel(part, Prefix, null, null);
|
||||
if (updater.TryUpdateModel(part, Prefix, null, null)){
|
||||
|
||||
var settings = part.Settings.GetModel<TitlePartSettings>();
|
||||
|
||||
if (settings.MaxLength > 0 && part.Title.Length > settings.MaxLength) {
|
||||
updater.AddModelError("Title", T("The maximum allowed length for the title is {0}", settings.MaxLength));
|
||||
}
|
||||
}
|
||||
|
||||
return Editor(part, shapeHelper);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Core.Contents.Extensions;
|
||||
using Orchard.Data.Migration;
|
||||
using Orchard.Core.Title.Settings;
|
||||
|
||||
namespace Orchard.Core.Title {
|
||||
public class Migrations : DataMigrationImpl {
|
||||
@ -9,7 +10,7 @@ namespace Orchard.Core.Title {
|
||||
SchemaBuilder.CreateTable("TitlePartRecord",
|
||||
table => table
|
||||
.ContentPartVersionRecord()
|
||||
.Column<string>("Title", column => column.WithLength(1024))
|
||||
.Column<string>("Title", column => column.WithLength(TitlePartSettings.MaxTitleLength))
|
||||
);
|
||||
|
||||
ContentDefinitionManager.AlterPartDefinition("TitlePart", builder => builder
|
||||
|
19
src/Orchard.Web/Core/Title/Settings/TitlePartSettings.cs
Normal file
19
src/Orchard.Web/Core/Title/Settings/TitlePartSettings.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.Core.Title.Settings {
|
||||
|
||||
|
||||
public class TitlePartSettings {
|
||||
// Whenever this constant is changed a new migration step must be created to update the length of the field on the DB
|
||||
public const int MaxTitleLength = 1024;
|
||||
[Range(0, MaxTitleLength)]
|
||||
[DisplayName("Maximum Length")]
|
||||
public int MaxLength {get; set;}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
using Orchard.ContentManagement.MetaData.Models;
|
||||
using Orchard.ContentManagement.ViewModels;
|
||||
|
||||
namespace Orchard.Core.Title.Settings {
|
||||
public class TitlePartSettingsEvents : ContentDefinitionEditorEventsBase {
|
||||
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditor(ContentTypePartDefinition definition) {
|
||||
if (definition.PartDefinition.Name != "TitlePart") {
|
||||
yield break;
|
||||
}
|
||||
|
||||
var settings = definition
|
||||
.Settings
|
||||
.GetModel<TitlePartSettings>()
|
||||
?? new TitlePartSettings();
|
||||
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditorUpdate(ContentTypePartDefinitionBuilder builder, IUpdateModel updateModel) {
|
||||
|
||||
if (builder.Name != "TitlePart") {
|
||||
yield break;
|
||||
}
|
||||
|
||||
var model = new TitlePartSettings();
|
||||
|
||||
if (updateModel.TryUpdateModel(model, "TitlePartSettings", null, null)) {
|
||||
builder.WithSetting("TitlePartSettings.MaxLength", model.MaxLength.ToString());
|
||||
|
||||
}
|
||||
yield return DefinitionTemplate(model);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
@using Orchard.Core.Title.Settings;
|
||||
@model TitlePartSettings
|
||||
@{
|
||||
var maxLength = TitlePartSettings.MaxTitleLength;
|
||||
}
|
||||
|
||||
<fieldset>
|
||||
<label for="@Html.FieldIdFor(m => m.MaxLength)">@T("Maximum length")</label>
|
||||
@Html.EditorFor(m => m.MaxLength, new { htmlAttributes = new { min = 0, max = maxLength } })
|
||||
<span class="hint">@T("Maximum length allowed for the title. Setting the value to 0 means the maximum allowed length is {0} characters.", maxLength)</span>
|
||||
@Html.ValidationMessageFor(m => m.MaxLength)
|
||||
</fieldset>
|
@ -1,7 +1,11 @@
|
||||
@model Orchard.Core.Title.Models.TitlePart
|
||||
@using Orchard.Core.Title.Settings
|
||||
@model Orchard.Core.Title.Models.TitlePart
|
||||
@{
|
||||
var maxLength = Model.Settings.GetModel<TitlePartSettings>().MaxLength > 0 ? Model.Settings.GetModel<TitlePartSettings>().MaxLength.ToString() : "";
|
||||
}
|
||||
|
||||
<fieldset>
|
||||
<label for="@Html.FieldIdFor(m => m.Title)" class="required">@T("Title")</label>
|
||||
@Html.TextBoxFor(m => m.Title, new { @class = "text large", autofocus = "autofocus" })
|
||||
<label for="@Html.FieldIdFor(m => m.Title)" class="required">@T("Title")</label>
|
||||
@Html.TextBoxFor(m => m.Title, new { @class = "text large", autofocus = "autofocus", maxlength = maxLength })
|
||||
<span class="hint">@T("You must provide a title for this content item")</span>
|
||||
</fieldset>
|
||||
|
@ -41,6 +41,7 @@ namespace Orchard.Autoroute.Providers {
|
||||
context.For<IContent>("Content")
|
||||
// {Content.Slug}
|
||||
.Token("Slug", (content => content == null ? String.Empty : _slugService.Slugify(content)))
|
||||
.Chain("Slug", "Text", (content => content == null ? String.Empty : _slugService.Slugify(content)))
|
||||
.Token("Path", (content => {
|
||||
var autoroutePart = content.As<AutoroutePart>();
|
||||
if (autoroutePart == null) {
|
||||
|
@ -186,7 +186,7 @@ namespace Orchard.Autoroute.Services {
|
||||
}
|
||||
|
||||
public string GenerateUniqueSlug(AutoroutePart part, IEnumerable<string> existingPaths) {
|
||||
if (existingPaths == null || !existingPaths.Contains(part.Path))
|
||||
if (existingPaths == null || !existingPaths.Contains(part.Path, StringComparer.OrdinalIgnoreCase))
|
||||
return part.Path;
|
||||
|
||||
var version = existingPaths.Select(s => GetSlugVersion(part.Path, s)).OrderBy(i => i).LastOrDefault();
|
||||
@ -287,7 +287,8 @@ namespace Orchard.Autoroute.Services {
|
||||
|
||||
private static int? GetSlugVersion(string path, string potentialConflictingPath) {
|
||||
int v;
|
||||
var slugParts = potentialConflictingPath.Split(new[] { path }, StringSplitOptions.RemoveEmptyEntries);
|
||||
// Matching needs to ignore case, so both paths are forced to lowercase.
|
||||
var slugParts = potentialConflictingPath.ToLower().Split(new[] { path.ToLower() }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (slugParts.Length == 0)
|
||||
return 2;
|
||||
|
@ -6,14 +6,11 @@ using Orchard.ContentPicker.Fields;
|
||||
|
||||
namespace Orchard.ContentPicker.Handlers {
|
||||
public class ContentPickerFieldHandler : ContentHandler {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
|
||||
public ContentPickerFieldHandler(
|
||||
IContentManager contentManager,
|
||||
IContentDefinitionManager contentDefinitionManager) {
|
||||
|
||||
_contentManager = contentManager;
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
}
|
||||
|
||||
@ -30,7 +27,8 @@ namespace Orchard.ContentPicker.Handlers {
|
||||
|
||||
foreach (var field in fields) {
|
||||
var localField = field;
|
||||
field._contentItems.Loader(() => _contentManager.GetMany<ContentItem>(localField.Ids, VersionOptions.Published, QueryHints.Empty));
|
||||
// Using context content item's ContentManager instead of injected one to avoid lifetime scope exceptions in case of LazyFields.
|
||||
field._contentItems.Loader(() => context.ContentItem.ContentManager.GetMany<ContentItem>(localField.Ids, VersionOptions.Published, QueryHints.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using Orchard.ContentManagement;
|
||||
using Orchard.Events;
|
||||
using Orchard.ContentPicker.Fields;
|
||||
using Orchard.Localization;
|
||||
using NHibernate.Util;
|
||||
|
||||
namespace Orchard.ContentPicker.Tokens {
|
||||
public interface ITokenProvider : IEventHandler {
|
||||
@ -28,7 +29,10 @@ namespace Orchard.ContentPicker.Tokens {
|
||||
public void Evaluate(dynamic context) {
|
||||
context.For<ContentPickerField>("ContentPickerField")
|
||||
.Token("Content", (Func<ContentPickerField, object>)(field => field.Ids[0]))
|
||||
.Chain("Content", "Content", (Func<ContentPickerField, object>)(field => _contentManager.Get(field.Ids[0])))
|
||||
.Chain("Content", "Content", (Func<ContentPickerField, object>)(field => {
|
||||
var id = field.Ids.Any() ? field.Ids[0] : 0;
|
||||
return _contentManager.Get(id);
|
||||
}))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ namespace Orchard.Email.Controllers {
|
||||
smtpSettings.ReplyTo = testSettings.ReplyTo;
|
||||
smtpSettings.Host = testSettings.Host;
|
||||
smtpSettings.Port = testSettings.Port;
|
||||
smtpSettings.EnableSsl = testSettings.EnableSsl;
|
||||
smtpSettings.AutoSelectEncryption = testSettings.AutoSelectEncryption;
|
||||
smtpSettings.EncryptionMethod = testSettings.EncryptionMethod;
|
||||
smtpSettings.RequireCredentials = testSettings.RequireCredentials;
|
||||
smtpSettings.UseDefaultCredentials = testSettings.UseDefaultCredentials;
|
||||
smtpSettings.UserName = testSettings.UserName;
|
||||
smtpSettings.Password = testSettings.Password;
|
||||
smtpSettings.ListUnsubscribe = testSettings.ListUnsubscribe;
|
||||
@ -92,9 +92,9 @@ namespace Orchard.Email.Controllers {
|
||||
public string ReplyTo { get; set; }
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public bool EnableSsl { get; set; }
|
||||
public SmtpEncryptionMethod EncryptionMethod { get; set; }
|
||||
public bool AutoSelectEncryption { get; set; }
|
||||
public bool RequireCredentials { get; set; }
|
||||
public bool UseDefaultCredentials { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string To { get; set; }
|
||||
|
@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Text;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Email.Models;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Email.Models;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Security;
|
||||
using System.Configuration;
|
||||
|
||||
namespace Orchard.Email.Handlers {
|
||||
public class SmtpSettingsPartHandler : ContentHandler {
|
||||
@ -24,7 +24,8 @@ namespace Orchard.Email.Handlers {
|
||||
OnInitializing<SmtpSettingsPart>((context, part) => {
|
||||
part.Port = 25;
|
||||
part.RequireCredentials = false;
|
||||
part.EnableSsl = false;
|
||||
part.AutoSelectEncryption = true;
|
||||
part.EncryptionMethod = SmtpEncryptionMethod.None;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,7 @@ using Orchard.Email.Models;
|
||||
namespace Orchard.Email {
|
||||
public class Migrations : DataMigrationImpl {
|
||||
private readonly IContentManager _contentManager;
|
||||
|
||||
public Migrations(IContentManager contentManager) => _contentManager = contentManager;
|
||||
|
||||
// The first migration without any content should not exist but it has been deployed so we need to keep it.
|
||||
public int Create() => 1;
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
namespace Orchard.Email.Models {
|
||||
/// <summary>
|
||||
/// Represents an enumeration for mail encryption methods.
|
||||
/// </summary>
|
||||
public enum SmtpEncryptionMethod {
|
||||
None = 0,
|
||||
SslTls = 1,
|
||||
StartTls = 2
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Configuration;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Net.Configuration;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Utilities;
|
||||
@ -38,9 +39,23 @@ namespace Orchard.Email.Models {
|
||||
set => this.Store(x => x.Port, value);
|
||||
}
|
||||
|
||||
public bool EnableSsl {
|
||||
get => this.Retrieve(x => x.EnableSsl);
|
||||
set => this.Store(x => x.EnableSsl, value);
|
||||
[Obsolete($"Use {nameof(AutoSelectEncryption)} and/or {nameof(EncryptionMethod)} instead.")]
|
||||
public bool EnableSsl => this.Retrieve(x => x.EnableSsl);
|
||||
|
||||
public SmtpEncryptionMethod EncryptionMethod {
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// Reading EnableSsl is necessary to keep the correct settings during the upgrade to MailKit.
|
||||
get { return this.Retrieve(x => x.EncryptionMethod, EnableSsl ? (Port == 587 ? SmtpEncryptionMethod.StartTls : SmtpEncryptionMethod.SslTls) : SmtpEncryptionMethod.None); }
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
set { this.Store(x => x.EncryptionMethod, value); }
|
||||
}
|
||||
|
||||
public bool AutoSelectEncryption {
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// Reading EnableSsl is necessary to keep the correct settings during the upgrade to MailKit.
|
||||
get { return this.Retrieve(x => x.AutoSelectEncryption, !EnableSsl); }
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
set { this.Store(x => x.AutoSelectEncryption, value); }
|
||||
}
|
||||
|
||||
public bool RequireCredentials {
|
||||
@ -48,11 +63,6 @@ namespace Orchard.Email.Models {
|
||||
set => this.Store(x => x.RequireCredentials, value);
|
||||
}
|
||||
|
||||
public bool UseDefaultCredentials {
|
||||
get => this.Retrieve(x => x.UseDefaultCredentials);
|
||||
set => this.Store(x => x.UseDefaultCredentials, value);
|
||||
}
|
||||
|
||||
public string UserName {
|
||||
get => this.Retrieve(x => x.UserName);
|
||||
set => this.Store(x => x.UserName, value);
|
||||
@ -91,4 +101,4 @@ namespace Orchard.Email.Models {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,12 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BouncyCastle.Crypto, Version=1.9.0.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Portable.BouncyCastle.1.9.0\lib\net40\BouncyCastle.Crypto.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MailKit, Version=3.1.0.0, Culture=neutral, PublicKeyToken=4e064fe7c44a8f1b, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\MailKit.3.1.1\lib\net48\MailKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
|
||||
</Reference>
|
||||
@ -62,14 +68,24 @@
|
||||
<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>
|
||||
</Reference>
|
||||
<Reference Include="MimeKit, Version=3.1.0.0, Culture=neutral, PublicKeyToken=bede1c8a46c66814, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\MimeKit.3.1.1\lib\net48\MimeKit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Configuration">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System.Web.DynamicData" />
|
||||
<Reference Include="System.Web.Entity" />
|
||||
@ -105,6 +121,7 @@
|
||||
<Compile Include="Drivers\SmtpSettingsPartDriver.cs" />
|
||||
<Compile Include="Handlers\SmtpSettingsPartHandler.cs" />
|
||||
<Compile Include="Models\EmailMessage.cs" />
|
||||
<Compile Include="Models\SmtpEncryptionMethod.cs" />
|
||||
<Compile Include="Models\SmtpSettingsPart.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Rules\MailActions.cs" />
|
||||
@ -219,4 +236,4 @@
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Mail;
|
||||
using System.Web.Mvc;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Email.Models;
|
||||
@ -24,8 +23,8 @@ namespace Orchard.Email.Services {
|
||||
|
||||
if (smtpSettings == null || !smtpSettings.IsValid()) {
|
||||
var urlHelper = new UrlHelper(workContext.HttpContext.Request.RequestContext);
|
||||
var url = urlHelper.Action("Email", "Admin", new {Area = "Settings"});
|
||||
yield return new NotifyEntry {Message = T("The <a href=\"{0}\">SMTP settings</a> need to be configured.", url), Type = NotifyType.Warning};
|
||||
var url = urlHelper.Action("Email", "Admin", new { Area = "Settings" });
|
||||
yield return new NotifyEntry { Message = T("The <a href=\"{0}\">SMTP settings</a> need to be configured.", url), Type = NotifyType.Warning };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using System.Net.Configuration;
|
||||
using System.Net.Mail;
|
||||
using System.Web.Mvc;
|
||||
using MailKit.Security;
|
||||
using MimeKit;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.DisplayManagement;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Email.Models;
|
||||
using System.IO;
|
||||
using Orchard.Logging;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace Orchard.Email.Services {
|
||||
public class SmtpMessageChannel : Component, ISmtpChannel, IDisposable {
|
||||
@ -40,6 +42,7 @@ namespace Orchard.Email.Services {
|
||||
}
|
||||
|
||||
public void Process(IDictionary<string, object> parameters) {
|
||||
|
||||
if (!_smtpSettings.IsValid()) {
|
||||
return;
|
||||
}
|
||||
@ -49,25 +52,70 @@ namespace Orchard.Email.Services {
|
||||
Subject = Read(parameters, "Subject"),
|
||||
Recipients = Read(parameters, "Recipients"),
|
||||
ReplyTo = Read(parameters, "ReplyTo"),
|
||||
FromAddress = Read(parameters, "FromAddress"),
|
||||
FromName = Read(parameters, "FromName"),
|
||||
From = Read(parameters, "From"),
|
||||
Bcc = Read(parameters, "Bcc"),
|
||||
Cc = Read(parameters, "CC"),
|
||||
Attachments = (IEnumerable<string>)(
|
||||
parameters.ContainsKey("Attachments")
|
||||
? parameters["Attachments"]
|
||||
: new List<string>()
|
||||
)
|
||||
Cc = Read(parameters, "CC")
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(emailMessage.Recipients)) {
|
||||
if (emailMessage.Recipients.Length == 0) {
|
||||
Logger.Error("Email message doesn't have any recipient");
|
||||
return;
|
||||
}
|
||||
|
||||
var mailMessage = CreteMailMessage(parameters, emailMessage);
|
||||
// Apply default Body alteration for SmtpChannel.
|
||||
var template = _shapeFactory.Create("Template_Smtp_Wrapper", Arguments.From(new {
|
||||
Content = new MvcHtmlString(emailMessage.Body)
|
||||
}));
|
||||
|
||||
var mailMessage = new MimeMessage {
|
||||
Subject = emailMessage.Subject,
|
||||
};
|
||||
var mailBodyBuilder = new BodyBuilder {
|
||||
HtmlBody = _shapeDisplay.Display(template),
|
||||
};
|
||||
|
||||
if (parameters.TryGetValue("Message", out var possiblyMailMessage) && possiblyMailMessage is MailMessage legacyMessage) {
|
||||
// A full message object is provided by the sender.
|
||||
if (!String.IsNullOrWhiteSpace(legacyMessage.Subject)) {
|
||||
mailMessage.Subject = legacyMessage.Subject;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(legacyMessage.Body)) {
|
||||
mailBodyBuilder.TextBody = legacyMessage.IsBodyHtml ? null : legacyMessage.Body;
|
||||
mailBodyBuilder.HtmlBody = legacyMessage.IsBodyHtml ? legacyMessage.Body : null;
|
||||
}
|
||||
}
|
||||
|
||||
mailMessage.Body = mailBodyBuilder.ToMessageBody();
|
||||
|
||||
try {
|
||||
mailMessage.To.AddRange(ParseRecipients(emailMessage.Recipients));
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(emailMessage.Cc)) {
|
||||
mailMessage.Cc.AddRange(ParseRecipients(emailMessage.Cc));
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(emailMessage.Bcc)) {
|
||||
mailMessage.Bcc.AddRange(ParseRecipients(emailMessage.Bcc));
|
||||
}
|
||||
|
||||
var fromAddress = default(MailboxAddress);
|
||||
if (!String.IsNullOrWhiteSpace(emailMessage.From)) {
|
||||
fromAddress = MailboxAddress.Parse(emailMessage.From);
|
||||
}
|
||||
else {
|
||||
// Take 'From' address from site settings or web.config.
|
||||
fromAddress = !String.IsNullOrWhiteSpace(_smtpSettings.Address)
|
||||
? MailboxAddress.Parse(_smtpSettings.Address)
|
||||
: MailboxAddress.Parse(((SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp")).From);
|
||||
}
|
||||
|
||||
mailMessage.From.Add(fromAddress);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(emailMessage.ReplyTo)) {
|
||||
mailMessage.ReplyTo.AddRange(ParseRecipients(emailMessage.ReplyTo));
|
||||
}
|
||||
|
||||
_smtpClientField.Value.Send(mailMessage);
|
||||
}
|
||||
catch (Exception e) {
|
||||
@ -75,128 +123,69 @@ namespace Orchard.Email.Services {
|
||||
}
|
||||
}
|
||||
|
||||
private MailMessage CreteMailMessage(IDictionary<string, object> parameters, EmailMessage emailMessage) {
|
||||
|
||||
// Apply default Body alteration for SmtpChannel.
|
||||
var template = _shapeFactory.Create("Template_Smtp_Wrapper", Arguments.From(new {
|
||||
Content = new MvcHtmlString(emailMessage.Body)
|
||||
}));
|
||||
|
||||
var mailMessage = new MailMessage {
|
||||
Subject = emailMessage.Subject,
|
||||
Body = _shapeDisplay.Display(template),
|
||||
IsBodyHtml = true
|
||||
};
|
||||
|
||||
if (parameters.ContainsKey("Message")) {
|
||||
// A full message object is provided by the sender.
|
||||
var oldMessage = mailMessage;
|
||||
mailMessage = (MailMessage)parameters["Message"];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mailMessage.Subject))
|
||||
mailMessage.Subject = oldMessage.Subject;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mailMessage.Body)) {
|
||||
mailMessage.Body = oldMessage.Body;
|
||||
mailMessage.IsBodyHtml = oldMessage.IsBodyHtml;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var recipient in ParseRecipients(emailMessage.Recipients)) {
|
||||
mailMessage.To.Add(new MailAddress(recipient));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(emailMessage.Cc)) {
|
||||
foreach (var recipient in ParseRecipients(emailMessage.Cc)) {
|
||||
mailMessage.CC.Add(new MailAddress(recipient));
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(emailMessage.Bcc)) {
|
||||
foreach (var recipient in ParseRecipients(emailMessage.Bcc)) {
|
||||
mailMessage.Bcc.Add(new MailAddress(recipient));
|
||||
}
|
||||
}
|
||||
|
||||
var senderAddress =
|
||||
!string.IsNullOrWhiteSpace(emailMessage.FromAddress) ? emailMessage.FromAddress :
|
||||
!string.IsNullOrWhiteSpace(_smtpSettings.FromAddress) ? _smtpSettings.FromAddress :
|
||||
// Take 'From' address from site settings or web.config.
|
||||
((SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp")).From;
|
||||
|
||||
var senderName = !string.IsNullOrWhiteSpace(emailMessage.FromName)
|
||||
? emailMessage.FromName
|
||||
: _smtpSettings.FromName;
|
||||
|
||||
if (senderAddress != null && senderName != null) {
|
||||
mailMessage.From = new MailAddress(senderAddress, senderName);
|
||||
} else if (senderAddress != null && senderName == null) {
|
||||
mailMessage.From = new MailAddress(senderAddress);
|
||||
} else if (senderAddress == null && senderName == null) {
|
||||
throw new InvalidOperationException("No sender email address");
|
||||
}
|
||||
|
||||
var replyTo =
|
||||
!string.IsNullOrWhiteSpace(emailMessage.ReplyTo) ? ParseRecipients(emailMessage.ReplyTo) :
|
||||
!string.IsNullOrWhiteSpace(_smtpSettings.ReplyTo) ? new[] { _smtpSettings.ReplyTo } :
|
||||
Array.Empty<string>();
|
||||
|
||||
foreach (var recipient in replyTo) {
|
||||
mailMessage.ReplyToList.Add(new MailAddress(recipient));
|
||||
}
|
||||
|
||||
foreach (var attachmentPath in emailMessage.Attachments) {
|
||||
if (File.Exists(attachmentPath)) {
|
||||
mailMessage.Attachments.Add(new Attachment(attachmentPath));
|
||||
}
|
||||
else {
|
||||
throw new FileNotFoundException(T("One or more attachments not found.").Text);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.ContainsKey("NotifyReadEmail")) {
|
||||
if (parameters["NotifyReadEmail"] is bool) {
|
||||
if ((bool)(parameters["NotifyReadEmail"])) {
|
||||
mailMessage.Headers.Add("Disposition-Notification-To", mailMessage.From.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_smtpSettings.ListUnsubscribe)) {
|
||||
mailMessage.Headers.Add("List-Unsubscribe", _smtpSettings.ListUnsubscribe);
|
||||
}
|
||||
|
||||
return mailMessage;
|
||||
}
|
||||
|
||||
private SmtpClient CreateSmtpClient() {
|
||||
// If no properties are set in the dashboard, use the web.config value.
|
||||
if (string.IsNullOrWhiteSpace(_smtpSettings.Host)) {
|
||||
return new SmtpClient();
|
||||
}
|
||||
|
||||
var smtpClient = new SmtpClient {
|
||||
UseDefaultCredentials = _smtpSettings.RequireCredentials && _smtpSettings.UseDefaultCredentials
|
||||
var smtpConfiguration = new {
|
||||
_smtpSettings.Host,
|
||||
_smtpSettings.Port,
|
||||
_smtpSettings.EncryptionMethod,
|
||||
_smtpSettings.AutoSelectEncryption,
|
||||
_smtpSettings.RequireCredentials,
|
||||
_smtpSettings.UserName,
|
||||
_smtpSettings.Password,
|
||||
};
|
||||
// If no properties are set in the dashboard, use the web.config value.
|
||||
if (String.IsNullOrWhiteSpace(_smtpSettings.Host)) {
|
||||
var smtpSection = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
|
||||
if (smtpSection.DeliveryMethod != SmtpDeliveryMethod.Network) {
|
||||
throw new NotSupportedException($"Only the {SmtpDeliveryMethod.Network} delivery method is supported, but "
|
||||
+ $"{smtpSection.DeliveryMethod} delivery method is configured. Please check your Web.config.");
|
||||
}
|
||||
|
||||
if (!smtpClient.UseDefaultCredentials && !string.IsNullOrWhiteSpace(_smtpSettings.UserName)) {
|
||||
smtpClient.Credentials = new NetworkCredential(_smtpSettings.UserName, _smtpSettings.Password);
|
||||
smtpConfiguration = new {
|
||||
smtpSection.Network.Host,
|
||||
smtpSection.Network.Port,
|
||||
EncryptionMethod = smtpSection.Network.EnableSsl ? SmtpEncryptionMethod.SslTls : SmtpEncryptionMethod.None,
|
||||
AutoSelectEncryption = !smtpSection.Network.EnableSsl,
|
||||
RequireCredentials = smtpSection.Network.DefaultCredentials || !String.IsNullOrWhiteSpace(smtpSection.Network.UserName),
|
||||
smtpSection.Network.UserName,
|
||||
smtpSection.Network.Password,
|
||||
};
|
||||
}
|
||||
|
||||
if (_smtpSettings.Host != null) {
|
||||
smtpClient.Host = _smtpSettings.Host;
|
||||
var secureSocketOptions = SecureSocketOptions.Auto;
|
||||
if (!smtpConfiguration.AutoSelectEncryption) {
|
||||
secureSocketOptions = smtpConfiguration.EncryptionMethod switch {
|
||||
SmtpEncryptionMethod.None => SecureSocketOptions.None,
|
||||
SmtpEncryptionMethod.SslTls => SecureSocketOptions.SslOnConnect,
|
||||
SmtpEncryptionMethod.StartTls => SecureSocketOptions.StartTls,
|
||||
_ => SecureSocketOptions.None,
|
||||
};
|
||||
}
|
||||
|
||||
var smtpClient = new SmtpClient();
|
||||
smtpClient.Connect(smtpConfiguration.Host, smtpConfiguration.Port, secureSocketOptions);
|
||||
|
||||
if (smtpConfiguration.RequireCredentials) {
|
||||
smtpClient.Authenticate(smtpConfiguration.UserName, smtpConfiguration.Password);
|
||||
}
|
||||
|
||||
smtpClient.Port = _smtpSettings.Port;
|
||||
smtpClient.EnableSsl = _smtpSettings.EnableSsl;
|
||||
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
|
||||
return smtpClient;
|
||||
}
|
||||
|
||||
private string Read(IDictionary<string, object> dictionary, string key) =>
|
||||
dictionary.ContainsKey(key) ? dictionary[key] as string : null;
|
||||
private string Read(IDictionary<string, object> dictionary, string key) {
|
||||
return dictionary.ContainsKey(key) ? dictionary[key] as string : null;
|
||||
}
|
||||
|
||||
private IEnumerable<string> ParseRecipients(string recipients) =>
|
||||
recipients.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
private IEnumerable<MailboxAddress> ParseRecipients(string recipients) {
|
||||
return recipients.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.SelectMany(address => {
|
||||
if (MailboxAddress.TryParse(address, out var mailboxAddress)) {
|
||||
return new[] { mailboxAddress };
|
||||
}
|
||||
|
||||
Logger.Error("Invalid email address: {0}", address);
|
||||
return Enumerable.Empty<MailboxAddress>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
@using System.Net.Mail
|
||||
@using Orchard.Email.Models
|
||||
@using System.Net.Mail
|
||||
@model Orchard.Email.Models.SmtpSettingsPart
|
||||
@{
|
||||
var smtpClient = new SmtpClient();
|
||||
@ -29,15 +30,37 @@
|
||||
</div>
|
||||
<div>
|
||||
<label for="@Html.FieldIdFor(m => m.Port)">@T("Port number")</label>
|
||||
@Html.TextBoxFor(m => m.Port, new { placeholder = smtpClient.Port, @class = "text small" })
|
||||
@Html.TextBoxFor(m => m.Port, new { type = "number", placeholder = smtpClient.Port, min = 1, max = 65535 })
|
||||
@Html.ValidationMessage("Port", "*")
|
||||
<span class="hint">@T("The SMTP server port, usually 25.")</span>
|
||||
</div>
|
||||
<div>
|
||||
@Html.EditorFor(m => m.EnableSsl)
|
||||
<label for="@Html.FieldIdFor(m => m.EnableSsl)" class="forcheckbox">@T("Enable SSL communications")</label>
|
||||
@Html.ValidationMessage("EnableSsl", "*")
|
||||
<span class="hint">@T("Check if the SMTP server requires SSL communications.")</span>
|
||||
<label for="@Html.FieldIdFor(m => m.EncryptionMethod)">@T("Encryption method to use")</label>
|
||||
<select for="@Html.FieldIdFor(m => m.EncryptionMethod)"
|
||||
id="@Html.FieldIdFor(m => m.EncryptionMethod)"
|
||||
name="@Html.FieldNameFor(m => m.EncryptionMethod)"
|
||||
disabled="@Model.AutoSelectEncryption">
|
||||
@Html.SelectOption(
|
||||
Model.EncryptionMethod,
|
||||
SmtpEncryptionMethod.None,
|
||||
String.Join(String.Empty, T("None").ToString(), " - ", T("Connect to server using insecure connection.").ToString()))
|
||||
@Html.SelectOption(
|
||||
Model.EncryptionMethod,
|
||||
SmtpEncryptionMethod.SslTls,
|
||||
String.Join(String.Empty, T("SSL/TLS").ToString(), " - ", T("Connect to server using SSL/TSL secure connection.").ToString()))
|
||||
@Html.SelectOption(
|
||||
Model.EncryptionMethod,
|
||||
SmtpEncryptionMethod.StartTls,
|
||||
String.Join(String.Empty, T("STARTTLS").ToString(), " - ", T("Connect to server using insecure connection and upgrade to secure using SSL/TLS.").ToString()))
|
||||
</select>
|
||||
@Html.ValidationMessage("EncryptionMethod", "*")
|
||||
<span class="hint">@T("The encryption method used when connecting to mail server.")</span>
|
||||
</div>
|
||||
<div>
|
||||
@Html.EditorFor(m => m.AutoSelectEncryption)
|
||||
<label for="@Html.FieldIdFor(m => m.AutoSelectEncryption)" class="forcheckbox">@T("Auto Select Encryption method")</label>
|
||||
@Html.ValidationMessage("AutoSelectEncryption", "*")
|
||||
<span class="hint">@T("Check to let the system select the encryption method based on port.")</span>
|
||||
</div>
|
||||
<div>
|
||||
@Html.EditorFor(m => m.RequireCredentials)
|
||||
@ -46,30 +69,16 @@
|
||||
</div>
|
||||
<div data-controllerid="@Html.FieldIdFor(m => m.RequireCredentials)">
|
||||
<div>
|
||||
@Html.RadioButtonFor(m => m.UseDefaultCredentials, false, new { id = "customCredentialsOption", name = "UseDefaultCredentials" })
|
||||
<label for="customCredentialsOption" class="forcheckbox">@T("Specify username/password")</label>
|
||||
@Html.ValidationMessage("UseDefaultCredentials", "*")
|
||||
<label for="@Html.FieldIdFor(m => m.UserName)">@T("User name")</label>
|
||||
@Html.TextBoxFor(m => m.UserName, new { @class = "text medium" })
|
||||
@Html.ValidationMessage("UserName", "*")
|
||||
<span class="hint">@T("The username for authentication.")</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@Html.RadioButtonFor(m => m.UseDefaultCredentials, true, new { id = "defaultCredentialsOptions", name = "UseDefaultCredentials" })
|
||||
<label for="defaultCredentialsOptions" class="forcheckbox">@T("Use Windows authentication")</label>
|
||||
@Html.ValidationMessage("UseDefaultCredentials", "*")
|
||||
<span class="hint">@T("When this option is selected, the aplication pool or host-process identity is used to authenticate with the mail server. ")</span>
|
||||
</div>
|
||||
<div class="options">
|
||||
<span data-controllerid="customCredentialsOption">
|
||||
<label for="@Html.FieldIdFor(m => m.UserName)">@T("User name")</label>
|
||||
@Html.TextBoxFor(m => m.UserName, new { @class = "text" })
|
||||
@Html.ValidationMessage("UserName", "*")
|
||||
<span class="hint">@T("The username for authentication.")</span>
|
||||
</span>
|
||||
<span data-controllerid="customCredentialsOption">
|
||||
<label for="@Html.FieldIdFor(m => m.Password)">@T("Password")</label>
|
||||
@Html.TextBoxFor(m => m.Password, new { type = "password", @class = "text medium" })
|
||||
@Html.ValidationMessage("Password", "*")
|
||||
<span class="hint">@T("The password for authentication.")</span>
|
||||
</span>
|
||||
<label for="@Html.FieldIdFor(m => m.Password)">@T("Password")</label>
|
||||
@Html.TextBoxFor(m => m.Password, new { type = "password", @class = "text medium" })
|
||||
@Html.ValidationMessage("Password", "*")
|
||||
<span class="hint">@T("The password for authentication.")</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -101,9 +110,9 @@
|
||||
replyTo = $("#@Html.FieldIdFor(m => m.ReplyTo)"),
|
||||
host = $("#@Html.FieldIdFor(m => m.Host)"),
|
||||
port = $("#@Html.FieldIdFor(m => m.Port)"),
|
||||
enableSsl = $("#@Html.FieldIdFor(m => m.EnableSsl)"),
|
||||
encryptionMethod = $("#@Html.FieldIdFor(m => m.EncryptionMethod)"),
|
||||
autoSelectEncryption = $("#@Html.FieldIdFor(m => m.AutoSelectEncryption)"),
|
||||
requireCredentials = $("#@Html.FieldIdFor(m => m.RequireCredentials)"),
|
||||
useDefaultCredentials = $("input[name='@Html.NameFor(m => m.UseDefaultCredentials)']"),
|
||||
userName = $("#@Html.FieldIdFor(m => m.UserName)"),
|
||||
password = $("#@Html.FieldIdFor(m => m.Password)"),
|
||||
listUnsubscribe = $("#@Html.FieldIdFor(m => m.ListUnsubscribe)"),
|
||||
@ -116,9 +125,9 @@
|
||||
replyTo: replyTo.val(),
|
||||
host: host.val(),
|
||||
port: port.val(),
|
||||
enableSsl: enableSsl.prop("checked"),
|
||||
encryptionMethod: encryptionMethod.val(),
|
||||
autoSelectEncryption: autoSelectEncryption.prop("checked"),
|
||||
requireCredentials: requireCredentials.prop("checked"),
|
||||
useDefaultCredentials: useDefaultCredentials.filter(':checked').val(),
|
||||
userName: userName.val(),
|
||||
password: password.val(),
|
||||
listUnsubscribe: listUnsubscribe.val(),
|
||||
|
@ -1,9 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MailKit" version="3.1.1" targetFramework="net48" />
|
||||
<package id="Microsoft.AspNet.Mvc" version="5.2.7" targetFramework="net48" />
|
||||
<package id="Microsoft.AspNet.Razor" version="3.2.7" targetFramework="net48" />
|
||||
<package id="Microsoft.AspNet.WebPages" version="3.2.7" targetFramework="net48" />
|
||||
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="2.0.1" targetFramework="net48" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net48" />
|
||||
<package id="MimeKit" version="3.1.1" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
|
||||
</packages>
|
||||
<package id="Portable.BouncyCastle" version="1.9.0" targetFramework="net48" />
|
||||
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
|
||||
</packages>
|
@ -145,6 +145,8 @@
|
||||
connectWith: _(parentClasses).map(function (e) { return "#" + editorId + " " + e + ":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"; }).join(", "),
|
||||
placeholder: placeholderClasses,
|
||||
"ui-floating": floating,
|
||||
helper: "clone", // We clone the element and we append it to the body because the container overflow is set to auto (see: Assets\Less\LayoutEditor\Toolbox.less) and otherwise it could not be moved with drag&drop
|
||||
appendTo: "body",
|
||||
create: function (e, ui) {
|
||||
e.target.isToolbox = true; // Will indicate to connected sortables that dropped items were sent from toolbox.
|
||||
},
|
||||
|
@ -1,11 +1,54 @@
|
||||
@import "Variables.less";
|
||||
|
||||
body {
|
||||
.layout-toolbox-item {
|
||||
border: 1px solid @gray-border;
|
||||
background-color: #fff;
|
||||
padding: (@container-padding - 3) @container-padding;
|
||||
cursor: default;
|
||||
list-style-type: none;
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
}
|
||||
|
||||
+ .layout-toolbox-item {
|
||||
margin-top: @container-padding / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-editor {
|
||||
> .layout-toolbox-wrapper {
|
||||
position: relative;
|
||||
margin-left: @container-padding;
|
||||
width: 220px;
|
||||
width: 218px;
|
||||
-webkit-flex-shrink: 0;
|
||||
-ms-flex-negative: 0;
|
||||
flex-shrink: 0;
|
||||
/* forcing the toolbox height to be smaller than the viewport height
|
||||
so it is always visible and scrollable*/
|
||||
max-height: calc(100vh - 80px);
|
||||
overflow-x: clip;
|
||||
overflow-y: auto;
|
||||
/* forcing the toolbox to stay sticked at the the top of its container */
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgb(0, 0, 0, .3);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
> .layout-toolbox {
|
||||
border: 1px solid @gray-border;
|
||||
@ -33,7 +76,7 @@
|
||||
display: block;
|
||||
margin-bottom: @container-padding / 3;
|
||||
text-decoration: none;
|
||||
|
||||
|
||||
&:before {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
@ -43,7 +86,7 @@
|
||||
content: "\f0d7";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.collapsed {
|
||||
.layout-toolbox-group-heading:before {
|
||||
content: "\f0da";
|
||||
|
@ -825,6 +825,8 @@ angular
|
||||
connectWith: _(parentClasses).map(function (e) { return "#" + editorId + " " + e + ":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"; }).join(", "),
|
||||
placeholder: placeholderClasses,
|
||||
"ui-floating": floating,
|
||||
helper: "clone",
|
||||
appendTo: "body",
|
||||
create: function (e, ui) {
|
||||
e.target.isToolbox = true; // Will indicate to connected sortables that dropped items were sent from toolbox.
|
||||
},
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -7,44 +7,34 @@ using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.MediaLibrary.Fields;
|
||||
using Orchard.MediaLibrary.Models;
|
||||
|
||||
namespace Orchard.MediaLibrary.Handlers
|
||||
{
|
||||
public class MediaLibraryPickerFieldHandler : ContentHandler
|
||||
{
|
||||
private readonly IContentManager _contentManager;
|
||||
namespace Orchard.MediaLibrary.Handlers {
|
||||
public class MediaLibraryPickerFieldHandler : ContentHandler {
|
||||
private readonly IContentDefinitionManager _contentDefinitionManager;
|
||||
|
||||
public MediaLibraryPickerFieldHandler(
|
||||
IContentManager contentManager,
|
||||
IContentDefinitionManager contentDefinitionManager)
|
||||
{
|
||||
IContentDefinitionManager contentDefinitionManager) {
|
||||
|
||||
_contentManager = contentManager;
|
||||
_contentDefinitionManager = contentDefinitionManager;
|
||||
|
||||
}
|
||||
|
||||
protected override void Loaded(LoadContentContext context)
|
||||
{
|
||||
protected override void Loaded(LoadContentContext context) {
|
||||
base.Loaded(context);
|
||||
InitilizeLoader(context.ContentItem);
|
||||
}
|
||||
|
||||
private void InitilizeLoader(ContentItem contentItem)
|
||||
{
|
||||
private void InitilizeLoader(ContentItem contentItem) {
|
||||
var fields = contentItem.Parts.SelectMany(x => x.Fields.OfType<MediaLibraryPickerField>());
|
||||
|
||||
// define lazy initializer for MediaLibraryPickerField.MediaParts
|
||||
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(contentItem.ContentType);
|
||||
if (contentTypeDefinition == null)
|
||||
{
|
||||
if (contentTypeDefinition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
foreach (var field in fields) {
|
||||
var localField = field;
|
||||
localField._contentItems.Loader(() => _contentManager.GetMany<MediaPart>(localField.Ids, VersionOptions.Published, QueryHints.Empty).ToList());
|
||||
// Using context content item's ContentManager instead of injected one to avoid lifetime scope exceptions in case of LazyFields.
|
||||
localField._contentItems = new Lazy<IEnumerable<MediaPart>>(() => contentItem.ContentManager.GetMany<MediaPart>(localField.Ids, VersionOptions.Published, QueryHints.Empty).ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,4 @@
|
||||
}
|
||||
|
||||
@* Don't render the audio tag as thumbnails or the whole file is downloaded automatically by browsers *@
|
||||
<div class="media-thumbnail media-thumbnail-@contentItem.ContentType.HtmlClassify() mime-type-@media.MimeType.HtmlClassify()" />
|
||||
<div class="media-thumbnail media-thumbnail-@contentItem.ContentType.HtmlClassify() mime-type-@media.MimeType.HtmlClassify()"></div>
|
@ -10,4 +10,4 @@
|
||||
}
|
||||
|
||||
@* Don't render the video tag as thumbnails or the whole file is downloaded automatically by browsers *@
|
||||
<div class="media-thumbnail media-thumbnail-@contentItem.ContentType.HtmlClassify() mime-type-@media.MimeType.HtmlClassify()" />
|
||||
<div class="media-thumbnail media-thumbnail-@contentItem.ContentType.HtmlClassify() mime-type-@media.MimeType.HtmlClassify()"></div>
|
||||
|
@ -123,6 +123,10 @@
|
||||
<Project>{642A49D7-8752-4177-80D6-BFBBCFAD3DE0}</Project>
|
||||
<Name>Orchard.Forms</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.MediaLibrary\Orchard.MediaLibrary.csproj">
|
||||
<Project>{73a7688a-5bd3-4f7e-adfa-ce36c5a10e3b}</Project>
|
||||
<Name>Orchard.MediaLibrary</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Tokens\Orchard.Tokens.csproj">
|
||||
<Project>{6f759635-13d7-4e94-bcc9-80445d63f117}</Project>
|
||||
<Name>Orchard.Tokens</Name>
|
||||
@ -241,4 +245,4 @@
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
@ -4,12 +4,12 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.FileSystems.Media;
|
||||
using Orchard.Forms.Services;
|
||||
using Orchard.Logging;
|
||||
using Orchard.MediaLibrary.Models;
|
||||
using Orchard.MediaProcessing.Descriptors.Filter;
|
||||
using Orchard.MediaProcessing.Media;
|
||||
using Orchard.MediaProcessing.Models;
|
||||
@ -44,7 +44,7 @@ namespace Orchard.MediaProcessing.Services {
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public string GetImageProfileUrl(string path, string profileName) {
|
||||
public string GetImageProfileUrl(string path, string profileName) {
|
||||
return GetImageProfileUrl(path, profileName, null, new FilterRecord[] { });
|
||||
}
|
||||
|
||||
@ -69,42 +69,56 @@ namespace Orchard.MediaProcessing.Services {
|
||||
var filePath = _fileNameProvider.GetFileName(profileName, System.Web.HttpUtility.UrlDecode(path));
|
||||
bool process = false;
|
||||
|
||||
//after reboot the app cache is empty so we reload the image in the cache if it exists in the _Profiles folder
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
var profileFilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, System.Web.HttpUtility.UrlDecode(path)));
|
||||
// Before checking everything else, ensure that the content item that needs to be processed has a ImagePart.
|
||||
// If it's not the case (e.g. if media is a svg file), processing would throw a exception.
|
||||
// If content item is null (it means it's not passed as a parameter of the ResizeMediaUrl call),
|
||||
// this function processes the file like it did before this patch;
|
||||
// this means it could possibly throw and log exceptions for svg files.
|
||||
bool checkForProfile = (contentItem == null || contentItem.Has<ImagePart>());
|
||||
|
||||
if (_storageProvider.FileExists(profileFilePath)) {
|
||||
_fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), profileFilePath);
|
||||
filePath = profileFilePath;
|
||||
if (checkForProfile) {
|
||||
//after reboot the app cache is empty so we reload the image in the cache if it exists in the _Profiles folder
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
var profileFilePath = _storageProvider.Combine("_Profiles", FormatProfilePath(profileName, System.Web.HttpUtility.UrlDecode(path)));
|
||||
|
||||
if (_storageProvider.FileExists(profileFilePath)) {
|
||||
_fileNameProvider.UpdateFileName(profileName, System.Web.HttpUtility.UrlDecode(path), profileFilePath);
|
||||
filePath = profileFilePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the filename is not cached, process it
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
Logger.Debug("FilePath is null, processing required, profile {0} for image {1}", profileName, path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
// if the filename is not cached, process it
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
Logger.Debug("FilePath is null, processing required, profile {0} for image {1}", profileName, path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
|
||||
// the processd file doesn't exist anymore, process it
|
||||
else if (!_storageProvider.FileExists(filePath)) {
|
||||
Logger.Debug("Processed file no longer exists, processing required, profile {0} for image {1}", profileName, path);
|
||||
else if (!_storageProvider.FileExists(filePath)) {
|
||||
Logger.Debug("Processed file no longer exists, processing required, profile {0} for image {1}", profileName, path);
|
||||
|
||||
process = true;
|
||||
}
|
||||
process = true;
|
||||
}
|
||||
|
||||
// if the original file is more recent, process it
|
||||
else {
|
||||
DateTime pathLastUpdated;
|
||||
if (TryGetImageLastUpdated(path, out pathLastUpdated)) {
|
||||
var filePathLastUpdated = _storageProvider.GetFile(filePath).GetLastUpdated();
|
||||
// if the original file is more recent, process it
|
||||
else {
|
||||
DateTime pathLastUpdated;
|
||||
if (TryGetImageLastUpdated(path, out pathLastUpdated)) {
|
||||
var filePathLastUpdated = _storageProvider.GetFile(filePath).GetLastUpdated();
|
||||
|
||||
if (pathLastUpdated > filePathLastUpdated) {
|
||||
Logger.Debug("Original file more recent, processing required, profile {0} for image {1}", profileName, path);
|
||||
if (pathLastUpdated > filePathLastUpdated) {
|
||||
Logger.Debug("Original file more recent, processing required, profile {0} for image {1}", profileName, path);
|
||||
|
||||
process = true;
|
||||
process = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Since media with no ImagePart have no profile, filePath is null, so it's set again to its original path on the storage provider.
|
||||
if (string.IsNullOrWhiteSpace(filePath)) {
|
||||
filePath = _storageProvider.GetStoragePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: regenerate the file if the profile is newer, by deleting the associated filename cache entries.
|
||||
@ -117,11 +131,10 @@ namespace Orchard.MediaProcessing.Services {
|
||||
profilePart = _profileService.GetImageProfileByName(profileName);
|
||||
if (profilePart == null)
|
||||
return String.Empty;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
profilePart = _services.ContentManager.New<ImageProfilePart>("ImageProfile");
|
||||
profilePart.Name = profileName;
|
||||
foreach (var customFilter in customFilters) {
|
||||
foreach (var customFilter in customFilters) {
|
||||
profilePart.Filters.Add(customFilter);
|
||||
}
|
||||
}
|
||||
@ -174,8 +187,7 @@ namespace Orchard.MediaProcessing.Services {
|
||||
// the storage provider may have altered the filepath
|
||||
filterContext.FilePath = newFile.GetPath();
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "A profile could not be processed: " + path);
|
||||
}
|
||||
}
|
||||
@ -203,8 +215,7 @@ namespace Orchard.MediaProcessing.Services {
|
||||
try {
|
||||
var file = _storageProvider.GetFile(storagePath);
|
||||
return file.OpenRead();
|
||||
}
|
||||
catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "path:" + path + " storagePath:" + storagePath);
|
||||
}
|
||||
}
|
||||
@ -237,7 +248,7 @@ namespace Orchard.MediaProcessing.Services {
|
||||
}
|
||||
|
||||
private string FormatProfilePath(string profileName, string path) {
|
||||
|
||||
|
||||
var filenameWithExtension = Path.GetFileName(path) ?? "";
|
||||
var fileLocation = path.Substring(0, path.Length - filenameWithExtension.Length);
|
||||
|
||||
|
@ -22,13 +22,14 @@ namespace Orchard.MediaProcessing.Shapes {
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Shape]
|
||||
public void ResizeMediaUrl(dynamic Shape, dynamic Display, TextWriter Output, ContentItem ContentItem, string Path, int Width, int Height, string Mode, string Alignment, string PadColor) {
|
||||
public void ResizeMediaUrl(dynamic Shape, dynamic Display, TextWriter Output, ContentItem ContentItem, string Path, int Width, int Height, string Mode, string Alignment, string PadColor, string Scale= "upscaleOnly") {
|
||||
var state = new Dictionary<string, string> {
|
||||
{"Width", Width.ToString(CultureInfo.InvariantCulture)},
|
||||
{"Height", Height.ToString(CultureInfo.InvariantCulture)},
|
||||
{"Mode", Mode},
|
||||
{"Alignment", Alignment},
|
||||
{"PadColor", PadColor},
|
||||
{"Scale", Scale},
|
||||
};
|
||||
|
||||
var filter = new FilterRecord {
|
||||
@ -42,7 +43,8 @@ namespace Orchard.MediaProcessing.Shapes {
|
||||
+ "_h_" + Convert.ToString(Height)
|
||||
+ "_m_" + Convert.ToString(Mode)
|
||||
+ "_a_" + Convert.ToString(Alignment)
|
||||
+ "_c_" + Convert.ToString(PadColor);
|
||||
+ "_c_" + Convert.ToString(PadColor)
|
||||
+ "_s_" + Convert.ToString(Scale);
|
||||
|
||||
MediaUrl(Shape, Display, Output, profile, Path, ContentItem, filter);
|
||||
}
|
||||
|
@ -98,6 +98,19 @@ namespace Orchard.Projections {
|
||||
|
||||
SchemaBuilder.CreateTable("FieldIndexPartRecord", table => table.ContentPartRecord());
|
||||
|
||||
//Adds indexes for better performances in queries
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" }));
|
||||
SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" }));
|
||||
|
||||
// Query
|
||||
|
||||
ContentDefinitionManager.AlterTypeDefinition("Query",
|
||||
@ -153,6 +166,7 @@ namespace Orchard.Projections {
|
||||
.Column<int>("Display")
|
||||
.Column<int>("QueryPartRecord_id")
|
||||
.Column<int>("GroupProperty_id")
|
||||
.Column<string>("GUIdentifier", column => column.WithLength(68))
|
||||
);
|
||||
|
||||
SchemaBuilder.CreateTable("PropertyRecord",
|
||||
@ -243,6 +257,24 @@ namespace Orchard.Projections {
|
||||
.DisplayedAs("Projection")
|
||||
);
|
||||
|
||||
SchemaBuilder.CreateTable("NavigationQueryPartRecord",
|
||||
table => table.ContentPartRecord()
|
||||
.Column<int>("Items")
|
||||
.Column<int>("Skip")
|
||||
.Column<int>("QueryPartRecord_id")
|
||||
);
|
||||
|
||||
ContentDefinitionManager.AlterTypeDefinition("NavigationQueryMenuItem",
|
||||
cfg => cfg
|
||||
.WithIdentity()
|
||||
.WithPart("NavigationQueryPart")
|
||||
.WithPart("MenuPart")
|
||||
.WithPart("CommonPart")
|
||||
.DisplayedAs("Query Link")
|
||||
.WithSetting("Description", "Injects menu items from a Query")
|
||||
.WithSetting("Stereotype", "MenuItem")
|
||||
);
|
||||
|
||||
// Default Model Bindings - CommonPartRecord
|
||||
|
||||
_memberBindingRepository.Create(new MemberBindingRecord {
|
||||
@ -284,24 +316,6 @@ namespace Orchard.Projections {
|
||||
Description = T("The text from the Body part").Text
|
||||
});
|
||||
|
||||
SchemaBuilder.CreateTable("NavigationQueryPartRecord",
|
||||
table => table.ContentPartRecord()
|
||||
.Column<int>("Items")
|
||||
.Column<int>("Skip")
|
||||
.Column<int>("QueryPartRecord_id")
|
||||
);
|
||||
|
||||
ContentDefinitionManager.AlterTypeDefinition("NavigationQueryMenuItem",
|
||||
cfg => cfg
|
||||
.WithPart("NavigationQueryPart")
|
||||
.WithPart("MenuPart")
|
||||
.WithPart("CommonPart")
|
||||
.DisplayedAs("Query Link")
|
||||
.WithSetting("Description", "Injects menu items from a Query")
|
||||
.WithSetting("Stereotype", "MenuItem")
|
||||
.WithIdentity()
|
||||
);
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ namespace Orchard.SecureSocketsLayer.Handlers {
|
||||
Filters.Add(new ActivatingFilter<SslSettingsPart>("Site"));
|
||||
|
||||
// Evict cached content when updated, removed or destroyed.
|
||||
OnUpdated<SslSettingsPart>((context, part) => Invalidate(part));
|
||||
OnPublished<SslSettingsPart>((context, part) => Invalidate(part));
|
||||
OnRemoved<SslSettingsPart>((context, part) => Invalidate(part));
|
||||
OnDestroyed<SslSettingsPart>((context, part) => Invalidate(part));
|
||||
@ -40,4 +41,4 @@ namespace Orchard.SecureSocketsLayer.Handlers {
|
||||
_signals.Trigger("SslSettingsPart_EvictAll");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,8 @@ namespace Orchard.Taxonomies.Handlers {
|
||||
var tempField = field.Name;
|
||||
field.TermsField.Loader(() => {
|
||||
var fieldTermRecordIds = part.Record.Terms.Where(t => t.Field == tempField).Select(tci => tci.TermRecord.Id);
|
||||
var terms = _contentManager.GetMany<TermPart>(fieldTermRecordIds, VersionOptions.Published, queryHint);
|
||||
// Using context content item's ContentManager instead of injected one to avoid lifetime scope exceptions in case of LazyFields.
|
||||
var terms = part.ContentItem.ContentManager.GetMany<TermPart>(fieldTermRecordIds, VersionOptions.Published, queryHint);
|
||||
return terms.ToList();
|
||||
});
|
||||
}
|
||||
@ -73,7 +74,8 @@ namespace Orchard.Taxonomies.Handlers {
|
||||
part._termParts = new LazyField<IEnumerable<TermContentItemPart>>();
|
||||
part._termParts.Loader(() => {
|
||||
var ids = part.Terms.Select(t => t.TermRecord.Id).Distinct();
|
||||
var terms = _contentManager.GetMany<TermPart>(ids, VersionOptions.Published, queryHint)
|
||||
// Using context content item's ContentManager instead of injected one to avoid lifetime scope exceptions in case of LazyFields.
|
||||
var terms = part.ContentItem.ContentManager.GetMany<TermPart>(ids, VersionOptions.Published, queryHint)
|
||||
.ToDictionary(t => t.Id, t => t);
|
||||
var publishedTermIds = terms.Select(t => t.Key);
|
||||
return
|
||||
|
@ -6,6 +6,8 @@ using Orchard.Taxonomies.Services;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Events;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Taxonomies.Drivers;
|
||||
|
||||
namespace Orchard.Taxonomies.Projections {
|
||||
public interface IFilterProvider : IEventHandler {
|
||||
@ -14,10 +16,13 @@ namespace Orchard.Taxonomies.Projections {
|
||||
|
||||
public class TermsFilter : IFilterProvider {
|
||||
private readonly ITaxonomyService _taxonomyService;
|
||||
private readonly IWorkContextAccessor _workContextAccessor;
|
||||
private int _termsFilterId;
|
||||
|
||||
public TermsFilter(ITaxonomyService taxonomyService) {
|
||||
public TermsFilter(ITaxonomyService taxonomyService,
|
||||
IWorkContextAccessor workContextAccessor) {
|
||||
_taxonomyService = taxonomyService;
|
||||
_workContextAccessor = workContextAccessor;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
@ -36,7 +41,10 @@ namespace Orchard.Taxonomies.Projections {
|
||||
var termIds = (string)context.State.TermIds;
|
||||
|
||||
if (!String.IsNullOrEmpty(termIds)) {
|
||||
var ids = termIds.Split(new[] { ',' }).Select(Int32.Parse).ToArray();
|
||||
var ids = termIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
// Int32.Parse throws for empty strings
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(Int32.Parse).ToArray();
|
||||
|
||||
if (ids.Length == 0) {
|
||||
return;
|
||||
@ -45,13 +53,30 @@ namespace Orchard.Taxonomies.Projections {
|
||||
int op = Convert.ToInt32(context.State.Operator);
|
||||
|
||||
var terms = ids.Select(_taxonomyService.GetTerm).ToList();
|
||||
|
||||
bool.TryParse(context.State.TranslateTerms?.Value, out bool translateTerms);
|
||||
if (translateTerms &&
|
||||
_workContextAccessor.GetContext().TryResolve<ILocalizationService>(out var localizationService)) {
|
||||
var localizedTerms = new List<TermPart>();
|
||||
foreach (var termPart in terms) {
|
||||
localizedTerms.AddRange(
|
||||
localizationService.GetLocalizations(termPart)
|
||||
.Select(l => l.As<TermPart>()));
|
||||
}
|
||||
terms.AddRange(localizedTerms);
|
||||
terms = terms.Distinct(new TermPartComparer()).ToList();
|
||||
}
|
||||
|
||||
var allChildren = new List<TermPart>();
|
||||
bool.TryParse(context.State.ExcludeChildren?.Value, out bool excludeChildren);
|
||||
foreach (var term in terms) {
|
||||
bool.TryParse(context.State.ExcludeChildren?.Value, out bool excludeChildren);
|
||||
if (!excludeChildren)
|
||||
if (term == null) {
|
||||
continue;
|
||||
}
|
||||
allChildren.Add(term);
|
||||
if (!excludeChildren) {
|
||||
allChildren.AddRange(_taxonomyService.GetChildren(term));
|
||||
if (term != null)
|
||||
allChildren.Add(term);
|
||||
}
|
||||
}
|
||||
|
||||
allChildren = allChildren.Distinct().ToList();
|
||||
|
@ -53,11 +53,17 @@ namespace Orchard.Taxonomies.Projections {
|
||||
Id: "ExcludeChildren", Name: "ExcludeChildren",
|
||||
Title: T("Automatically exclude children terms in filtering"),
|
||||
Value: "true"
|
||||
),
|
||||
_TranslateTerms: Shape.Checkbox(
|
||||
Id: "TranslateTerms", Name: "TranslateTerms",
|
||||
Title: T("Automatically include terms' localizations in filtering"),
|
||||
Value: "true"
|
||||
)
|
||||
);
|
||||
|
||||
foreach (var taxonomy in _taxonomyService.GetTaxonomies()) {
|
||||
f._Terms.Add(new SelectListItem { Value = String.Empty, Text = taxonomy.Name });
|
||||
var tGroup = new SelectListGroup { Name = taxonomy.Name };
|
||||
f._Terms.Add(tGroup);
|
||||
foreach (var term in _taxonomyService.GetTerms(taxonomy.Id)) {
|
||||
var gap = new string('-', term.GetLevels());
|
||||
|
||||
@ -65,7 +71,11 @@ namespace Orchard.Taxonomies.Projections {
|
||||
gap += " ";
|
||||
}
|
||||
|
||||
f._Terms.Add(new SelectListItem { Value = term.Id.ToString(), Text = gap + term.Name });
|
||||
f._Terms.Add(new SelectListItem {
|
||||
Value = term.Id.ToString(),
|
||||
Text = gap + term.Name,
|
||||
Group = tGroup
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,8 @@ using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
using Orchard.ContentManagement.MetaData.Models;
|
||||
using Orchard.ContentManagement.ViewModels;
|
||||
using Orchard.Taxonomies.Services;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Taxonomies.Services;
|
||||
|
||||
namespace Orchard.Taxonomies.Settings {
|
||||
public class TaxonomyFieldEditorEvents : ContentDefinitionEditorEventsBase {
|
||||
@ -21,7 +21,7 @@ namespace Orchard.Taxonomies.Settings {
|
||||
|
||||
public override IEnumerable<TemplateViewModel> PartFieldEditor(ContentPartFieldDefinition definition) {
|
||||
if (definition.FieldDefinition.Name == "TaxonomyField") {
|
||||
var model = definition.Settings.GetModel<TaxonomyFieldSettings>();
|
||||
var model = GetCurrentSettings(definition);
|
||||
model.Taxonomies = _taxonomyService.GetTaxonomies().Where(tax => !tax.ContentItem.HasDraft());
|
||||
yield return DefinitionTemplate(model);
|
||||
}
|
||||
@ -32,7 +32,8 @@ namespace Orchard.Taxonomies.Settings {
|
||||
yield break;
|
||||
}
|
||||
|
||||
var model = new TaxonomyFieldSettings();
|
||||
// Init this model preventively so if the TryUpdateModel doesn't execute correctly it doesn't cause an error
|
||||
var model = GetCurrentSettings(builder.Current);
|
||||
|
||||
if (updateModel.TryUpdateModel(model, "TaxonomyFieldSettings", null, null)) {
|
||||
builder
|
||||
@ -44,8 +45,15 @@ namespace Orchard.Taxonomies.Settings {
|
||||
.WithSetting("TaxonomyFieldSettings.AllowCustomTerms", model.AllowCustomTerms.ToString())
|
||||
.WithSetting("TaxonomyFieldSettings.Hint", model.Hint);
|
||||
}
|
||||
|
||||
|
||||
yield return DefinitionTemplate(model);
|
||||
}
|
||||
|
||||
private TaxonomyFieldSettings GetCurrentSettings(ContentPartFieldDefinition definition) {
|
||||
var model = definition.Settings.GetModel<TaxonomyFieldSettings>();
|
||||
model.Taxonomies = _taxonomyService.GetTaxonomies();
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
@ -157,7 +157,7 @@ namespace Orchard.ContentManagement {
|
||||
return Convert.ToString(value);
|
||||
}
|
||||
if ((!type.IsValueType || Nullable.GetUnderlyingType(type) != null) &&
|
||||
value == null &&
|
||||
value == null &&
|
||||
type != typeof(string)) {
|
||||
|
||||
return "null";
|
||||
@ -214,6 +214,10 @@ namespace Orchard.ContentManagement {
|
||||
return decimalValue.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (type == typeof(Guid) || type == typeof(Guid?)) {
|
||||
return value == null ? "null" : value.ToString();
|
||||
}
|
||||
|
||||
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
|
||||
if (underlyingType.IsEnum) {
|
||||
@ -277,6 +281,10 @@ namespace Orchard.ContentManagement {
|
||||
return (T)(object)decimal.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (type == typeof(Guid) || type == typeof(Guid?)) {
|
||||
return (T)(object)Guid.Parse(value);
|
||||
}
|
||||
|
||||
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
|
||||
if (underlyingType.IsEnum) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
@ -87,6 +87,9 @@ namespace Orchard.Localization.Services {
|
||||
});
|
||||
}
|
||||
public CultureRecord GetCultureByName(string cultureName) {
|
||||
if (string.IsNullOrWhiteSpace(cultureName)) {
|
||||
return null;
|
||||
}
|
||||
var cultures = GetAllCulturesByName();
|
||||
CultureRecord result;
|
||||
cultures.TryGetValue(cultureName, out result);
|
||||
|
Loading…
Reference in New Issue
Block a user