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:
Benedek Farkas 2024-03-08 15:04:03 +01:00
commit bfe1e6aa01
53 changed files with 1907 additions and 1353 deletions

View File

@ -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; }
}
}
}

View File

@ -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>

View File

@ -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);

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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>

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View 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;}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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) {

View File

@ -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;

View File

@ -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));
}
}
}

View File

@ -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);
}))
;
}
}

View File

@ -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; }

View File

@ -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;
});
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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;
}
}
}
}

View File

@ -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>

View File

@ -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 };
}
}
}

View File

@ -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>();
});
}
}
}

View File

@ -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(),

View File

@ -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>

View File

@ -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.
},

View File

@ -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";

View File

@ -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

View File

@ -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());
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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");
}
}
}
}

View File

@ -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

View File

@ -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();

View File

@ -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
});
}
}

View File

@ -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;
}
}
}

View File

@ -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) {

View File

@ -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);