Refactoring comments to be able to use SpamFilter

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2012-11-07 16:31:36 -08:00
parent 37f0fbd41e
commit 19a22c6473
53 changed files with 386 additions and 409 deletions

View File

@@ -32,7 +32,6 @@ namespace Orchard.Tests.Modules.Comments.Services {
public override void Register(ContainerBuilder builder) {
builder.RegisterType<CommentService>().As<ICommentService>();
builder.RegisterType<StubCommentValidator>().As<ICommentValidator>();
builder.RegisterType<DefaultContentManager>().As<IContentManager>();
builder.RegisterType<DefaultContentManagerSession>().As<IContentManagerSession>();
builder.RegisterInstance(new Mock<IContentDefinitionManager>().Object);
@@ -141,21 +140,6 @@ namespace Orchard.Tests.Modules.Comments.Services {
Assert.That(_commentService.GetComment(commentId).Record.Status, Is.EqualTo(CommentStatus.Pending));
}
[Test]
public void MarkAsSpamShouldFlagComments() {
var commentedItem = _contentManager.New("commentedItem");
_contentManager.Create(commentedItem);
_contentManager.Create(commentedItem, VersionOptions.Published);
int commentId = commentedItem.As<CommentPart>().Id;
_commentService.ApproveComment(commentId);
Assert.That(_commentService.GetComment(commentId).Record.Status, Is.EqualTo(CommentStatus.Approved));
_commentService.MarkCommentAsSpam(commentId);
Assert.That(_commentService.GetComment(commentId).Record.Status, Is.EqualTo(CommentStatus.Spam));
}
[Test]
public void DeleteShouldRemoveComments() {
var commentIds = new int[12];
@@ -194,10 +178,4 @@ namespace Orchard.Tests.Modules.Comments.Services {
public class CommentedItemDriver : ContentPartDriver<CommentedItem> {
public static readonly string ContentTypeName = "commentedItem";
}
public class StubCommentValidator : ICommentValidator {
public bool ValidateComment(CommentPart commentPart) {
return true;
}
}
}

View File

@@ -32,7 +32,7 @@
<Settings>
<SiteSettingsPart PageSize="30" />
<CommentSettingsPart enableSpamProtection="true" />
<CommentSettingsPart />
</Settings>
<Migration features="f2,f4"/>

View File

@@ -7,7 +7,7 @@ namespace Orchard.AntiSpam {
public string MenuName { get { return "admin"; } }
public void GetNavigation(NavigationBuilder builder) {
builder.Add(T("Spam"), "11",
builder.Add(T("Spam"), "4.1",
menu => menu
.Add(T("Manage Spam"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.AntiSpam" }).Permission(Permissions.ManageAntiSpam))
);

View File

@@ -12,6 +12,7 @@ using Orchard.DisplayManagement;
using Orchard.Localization;
using Orchard.Settings;
using Orchard.UI.Navigation;
using Orchard.Mvc.Extensions;
namespace Orchard.AntiSpam.Controllers {
[ValidateInput(false)]
@@ -46,7 +47,7 @@ namespace Orchard.AntiSpam.Controllers {
if (options == null)
options = new SpamIndexOptions();
var query = Services.ContentManager.Query().ForPart<SpamFilterPart>();
var query = Services.ContentManager.Query().ForPart<SpamFilterPart>().ForVersion(VersionOptions.Latest);
switch(options.Filter) {
case SpamFilter.Spam:
@@ -104,25 +105,27 @@ namespace Orchard.AntiSpam.Controllers {
break;
case SpamBulkAction.Spam:
foreach (var checkedId in itemIds) {
var spam = Services.ContentManager.Get(checkedId);
var spam = Services.ContentManager.Get(checkedId, VersionOptions.Latest);
if(spam != null) {
spam.As<SpamFilterPart>().Status = SpamStatus.Spam;
_spamService.ReportSpam(spam.As<SpamFilterPart>());
Services.ContentManager.Publish(spam);
}
}
break;
case SpamBulkAction.Ham:
foreach (var checkedId in itemIds) {
var ham = Services.ContentManager.Get(checkedId);
var ham = Services.ContentManager.Get(checkedId, VersionOptions.Latest);
if (ham != null) {
ham.As<SpamFilterPart>().Status = SpamStatus.Ham;
_spamService.ReportHam(ham.As<SpamFilterPart>());
Services.ContentManager.Publish(ham);
}
}
break;
case SpamBulkAction.Delete:
foreach (var checkedId in itemIds) {
Services.ContentManager.Remove(Services.ContentManager.Get(checkedId));
Services.ContentManager.Remove(Services.ContentManager.Get(checkedId, VersionOptions.Latest));
}
break;
}
@@ -131,5 +134,35 @@ namespace Orchard.AntiSpam.Controllers {
return Index(options, new PagerParameters());
}
[HttpPost]
public ActionResult ReportSpam(int id, string returnUrl) {
if (!Services.Authorizer.Authorize(Permissions.ManageAntiSpam, T("Not authorized to manage spam")))
return new HttpUnauthorizedResult();
var spam = Services.ContentManager.Get(id, VersionOptions.Latest);
if (spam != null) {
spam.As<SpamFilterPart>().Status = SpamStatus.Spam;
_spamService.ReportSpam(spam.As<SpamFilterPart>());
Services.ContentManager.Publish(spam);
}
return this.RedirectLocal(returnUrl, "~/");
}
[HttpPost]
public ActionResult ReportHam(int id, string returnUrl) {
if (!Services.Authorizer.Authorize(Permissions.ManageAntiSpam, T("Not authorized to manage spam")))
return new HttpUnauthorizedResult();
var spam = Services.ContentManager.Get(id, VersionOptions.Latest);
if (spam != null) {
spam.As<SpamFilterPart>().Status = SpamStatus.Ham;
_spamService.ReportSpam(spam.As<SpamFilterPart>());
Services.ContentManager.Publish(spam);
}
return this.RedirectLocal(returnUrl, "~/");
}
}
}

View File

@@ -5,7 +5,6 @@ using System.Net;
using System.Text;
using System.Web;
using Orchard.AntiSpam.Models;
using Orchard.AntiSpam.Settings;
using Orchard.AntiSpam.ViewModels;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
@@ -26,9 +25,10 @@ namespace Orchard.AntiSpam.Drivers {
protected override DriverResult Editor(ReCaptchaPart part, dynamic shapeHelper) {
return ContentShape("Parts_ReCaptcha_Fields", () => {
var settings = part.TypePartDefinition.Settings.GetModel<ReCaptchaPartSettings>();
var workContext = _workContextAccessor.GetContext();
var settings = workContext.CurrentSite.As<ReCaptchaSettingsPart>();
if(settings.ByPassAuthenticated && _workContextAccessor.GetContext().CurrentUser != null) {
if(settings.TrustAuthenticatedUsers && workContext.CurrentUser != null) {
return null;
}
@@ -41,16 +41,17 @@ namespace Orchard.AntiSpam.Drivers {
}
protected override DriverResult Editor(ReCaptchaPart part, IUpdateModel updater, dynamic shapeHelper) {
var workContext = _workContextAccessor.GetContext();
var settings = workContext.CurrentSite.As<ReCaptchaSettingsPart>();
var settings = part.TypePartDefinition.Settings.GetModel<ReCaptchaPartSettings>();
if (settings.ByPassAuthenticated && _workContextAccessor.GetContext().CurrentUser != null) {
if (settings.TrustAuthenticatedUsers && workContext.CurrentUser != null) {
return null;
}
var submitViewModel = new ReCaptchaPartSubmitViewModel();
if(updater.TryUpdateModel(submitViewModel, String.Empty, null, null)) {
var context = _workContextAccessor.GetContext().HttpContext;
var context = workContext.HttpContext;
var result = ExecuteValidateRequest(
settings.PrivateKey,

View File

@@ -21,8 +21,10 @@ namespace Orchard.AntiSpam.Drivers {
}
protected override DriverResult Display(SpamFilterPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_SpamFilter_Metadata_SummaryAdmin",
() => shapeHelper.Parts_SpamFilter_Metadata_SummaryAdmin());
return Combined(
ContentShape("Parts_SpamFilter_Metadata_SummaryAdmin", () => shapeHelper.Parts_SpamFilter_Metadata_SummaryAdmin()),
ContentShape("Parts_SpamFilter_Metadata_Actions", () => shapeHelper.Parts_SpamFilter_Metadata_Actions())
);
}
protected override void Importing(SpamFilterPart part, ImportContentContext context) {

View File

@@ -22,7 +22,7 @@ namespace Orchard.AntiSpam.EventHandlers {
return;
}
context.IsSpam = _spamService.CheckForSpam(context.Text, SpamFilterAction.One) == SpamStatus.Spam;
context.IsSpam = _spamService.CheckForSpam(context.Text, SpamFilterAction.One, context.Content) == SpamStatus.Spam;
}
}
}

View File

@@ -2,6 +2,13 @@
namespace Orchard.AntiSpam.EventHandlers {
public interface ICheckSpamEventHandler : IEventHandler {
/// <param name="context">
/// Dynamic object representing the parameters for the call
/// - Content (in IContent): the IContent that should trigger events when checked
/// - Text (in string): the text which is submitted for spam analysis
/// - Checked (out bool): will be assigned to true if the spam could be checked
/// - IsPam (out bool): True if the text has been reported as spam
/// </param>
void CheckSpam(dynamic context);
}
}

View File

@@ -0,0 +1,27 @@
using JetBrains.Annotations;
using Orchard.AntiSpam.Models;
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.ContentManagement.Handlers;
using Orchard.Localization;
namespace Orchard.AntiSpam.Handlers {
[UsedImplicitly]
public class ReCaptchaSettingsPartHandler : ContentHandler {
public ReCaptchaSettingsPartHandler(IRepository<ReCaptchaSettingsPartRecord> repository) {
T = NullLocalizer.Instance;
Filters.Add(new ActivatingFilter<ReCaptchaSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
Filters.Add(new TemplateFilterForRecord<ReCaptchaSettingsPartRecord>("ReCaptchaSettings", "Parts/AntiSpam.ReCaptchaSettings", "spam"));
}
public Localizer T { get; set; }
protected override void GetItemMetadata(GetContentItemMetadataContext context) {
if (context.ContentItem.ContentType != "Site")
return;
base.GetItemMetadata(context);
context.Metadata.EditorGroupInfo.Add(new GroupInfo(T("Spam")));
}
}
}

View File

@@ -1,16 +1,35 @@
using Orchard.AntiSpam.Models;
using Orchard.AntiSpam.Services;
using Orchard.AntiSpam.Settings;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
namespace Orchard.AntiSpam.Handlers {
public class SpamFilterPartHandler : ContentHandler {
public SpamFilterPartHandler(ISpamService spamService, IRepository<SpamFilterPartRecord> repository) {
private readonly ITransactionManager _transactionManager;
public SpamFilterPartHandler(
ISpamService spamService,
IRepository<SpamFilterPartRecord> repository,
ITransactionManager transactionManager
) {
_transactionManager = transactionManager;
Filters.Add(StorageFilter.For(repository));
OnUpdated<SpamFilterPart>( (context, part) => {
part.Status = spamService.CheckForSpam(part);
});
OnPublishing<SpamFilterPart>((context, part) => {
if (part.Status == SpamStatus.Spam) {
if (part.Settings.GetModel<SpamFilterPartSettings>().DeleteSpam) {
_transactionManager.Cancel();
}
context.Cancel = true;
}
});
}
}
}

View File

@@ -16,6 +16,13 @@ namespace Orchard.AntiSpam {
.Attachable()
);
SchemaBuilder.CreateTable("ReCaptchaSettingsPartRecord",
table => table.ContentPartVersionRecord()
.Column<string>("PublicKey")
.Column<string>("PrivateKey")
.Column<bool>("TrustAuthenticatedUsers")
);
ContentDefinitionManager.AlterPartDefinition("SpamFilterPart", cfg => cfg
.Attachable()
);
@@ -25,10 +32,23 @@ namespace Orchard.AntiSpam {
.Column<string>("Status", c => c.WithLength(64))
);
return 1;
return 2;
}
public int UpdateFrom1() {
SchemaBuilder.CreateTable("ReCaptchaSettingsPartRecord",
table => table.ContentPartVersionRecord()
.Column<string>("PublicKey")
.Column<string>("PrivateKey")
.Column<bool>("TrustAuthenticatedUsers")
);
return 2;
}
}
[OrchardFeature("Akismet.Filter")]
public class AkismetMigrations : DataMigrationImpl {

View File

@@ -0,0 +1,20 @@
using Orchard.ContentManagement;
namespace Orchard.AntiSpam.Models {
public class ReCaptchaSettingsPart : ContentPart<ReCaptchaSettingsPartRecord> {
public string PublicKey {
get { return Record.PublicKey; }
set { Record.PublicKey = value; }
}
public string PrivateKey {
get { return Record.PrivateKey; }
set { Record.PrivateKey = value; }
}
public bool TrustAuthenticatedUsers {
get { return Record.TrustAuthenticatedUsers; }
set { Record.TrustAuthenticatedUsers = value; }
}
}
}

View File

@@ -0,0 +1,9 @@
using Orchard.ContentManagement.Records;
namespace Orchard.AntiSpam.Models {
public class ReCaptchaSettingsPartRecord : ContentPartRecord {
public virtual string PublicKey { get; set; }
public virtual string PrivateKey { get; set; }
public virtual bool TrustAuthenticatedUsers { get; set; }
}
}

View File

@@ -96,11 +96,14 @@
<Compile Include="EventHandlers\DefaultCheckSpamEventHandler.cs" />
<Compile Include="EventHandlers\ICheckSpamEventHandler.cs" />
<Compile Include="Handlers\AkismetSettingsPartHandler.cs" />
<Compile Include="Handlers\ReCaptchaSettingsPartHandler.cs" />
<Compile Include="Handlers\TypePadSettingsPartHandler.cs" />
<Compile Include="Handlers\SpamFilterPartHandler.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Models\AkismetSettingsPartRecord.cs" />
<Compile Include="Models\AkismetSettingsPart.cs" />
<Compile Include="Models\ReCaptchaSettingsPart.cs" />
<Compile Include="Models\ReCaptchaSettingsPartRecord.cs" />
<Compile Include="Models\TypePadSettingsPart.cs" />
<Compile Include="Models\TypePadSettingsPartRecord.cs" />
<Compile Include="Models\SpamFilterPart.cs" />
@@ -114,6 +117,7 @@
<Compile Include="Services\AkismetApiSpamFilter.cs" />
<Compile Include="Services\AkismetSpamFilterProvider.cs" />
<Compile Include="Services\ISpamEventHandler.cs" />
<Compile Include="Services\MissingFilterBanner.cs" />
<Compile Include="Services\TypePadSpamFilterProvider.cs" />
<Compile Include="Services\DefaultSpamFilterProvider.cs" />
<Compile Include="Services\NullSpamFilterProvider.cs" />
@@ -123,8 +127,6 @@
<Compile Include="Services\ISpamService.cs" />
<Compile Include="Settings\SpamFilterPartSettings.cs" />
<Compile Include="Settings\SpamFilterPartSettingsEvents.cs" />
<Compile Include="Settings\ReCaptchaPartSettings.cs" />
<Compile Include="Settings\ReCaptchaPartSettingsEvents.cs" />
<Compile Include="Settings\SubmissionLimitPartSettings.cs" />
<Compile Include="Settings\SubmissionLimitPartSettingsEvents.cs" />
<Compile Include="ViewModels\SpamIndexViewModel.cs" />
@@ -133,9 +135,6 @@
<ItemGroup>
<Content Include="Views\Admin\Index.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\DefinitionTemplates\ReCaptchaPartSettings.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\DefinitionTemplates\SubmissionLimitPartSettings.cshtml" />
</ItemGroup>
@@ -162,7 +161,12 @@
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts\AntiSpam.TypePadSettings.cshtml" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Content Include="Views\Parts\SpamFilter.Metadata.Actions.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts\AntiSpam.ReCaptchaSettings.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -2,6 +2,7 @@
<Place Parts_ReCaptcha_Fields="Content:20"/>
<Match DisplayType="SummaryAdmin">
<Place Parts_SpamFilter_Metadata_SummaryAdmin="Meta:2"/>
<Place Parts_SpamFilter_Metadata_Actions="Actions:1"/>
</Match>
<Place Parts_AntiSpam_AkismetSettings="Content:9"/>

View File

@@ -4,6 +4,7 @@ using System.Linq;
using Orchard.AntiSpam.Models;
using Orchard.AntiSpam.Rules;
using Orchard.AntiSpam.Settings;
using Orchard.ContentManagement;
using Orchard.Tokens;
namespace Orchard.AntiSpam.Services {
@@ -25,7 +26,7 @@ namespace Orchard.AntiSpam.Services {
_rulesManager = rulesManager;
}
public SpamStatus CheckForSpam(string text, SpamFilterAction action) {
public SpamStatus CheckForSpam(string text, SpamFilterAction action, IContent content) {
if (string.IsNullOrWhiteSpace(text)) {
return SpamStatus.Ham;
@@ -33,22 +34,40 @@ namespace Orchard.AntiSpam.Services {
var spamFilters = GetSpamFilters().ToList();
var result = SpamStatus.Ham;
switch (action) {
case SpamFilterAction.AllOrNothing:
if (spamFilters.All(x => x.CheckForSpam(text) == SpamStatus.Spam)) {
return SpamStatus.Spam;
result = SpamStatus.Spam;
}
return SpamStatus.Ham;
break;
case SpamFilterAction.One:
if (spamFilters.Any(x => x.CheckForSpam(text) == SpamStatus.Spam)) {
return SpamStatus.Spam;
result = SpamStatus.Spam;
}
return SpamStatus.Ham;
break;
default:
throw new ArgumentOutOfRangeException();
}
// trigger events and rules
switch (result) {
case SpamStatus.Spam:
_spamEventHandler.SpamReported(content);
_rulesManager.TriggerEvent("AntiSpam", "Spam", () => new Dictionary<string, object> { { "Content", content } });
break;
case SpamStatus.Ham:
_spamEventHandler.HamReported(content);
_rulesManager.TriggerEvent("AntiSpam", "Ham", () => new Dictionary<string, object> { { "Content", content } });
break;
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
public SpamStatus CheckForSpam(SpamFilterPart part) {
@@ -62,21 +81,7 @@ namespace Orchard.AntiSpam.Services {
return SpamStatus.Ham;
}
var result = CheckForSpam(text, settings.Action);
// trigger events and rules
switch (result) {
case SpamStatus.Spam:
_spamEventHandler.SpamReported(part);
_rulesManager.TriggerEvent("AntiSpam", "Spam", () => new Dictionary<string, object> { { "Content", part.ContentItem } });
break;
case SpamStatus.Ham:
_spamEventHandler.HamReported(part);
_rulesManager.TriggerEvent("AntiSpam", "Ham", () => new Dictionary<string, object> { { "Content", part.ContentItem } });
break;
default:
throw new ArgumentOutOfRangeException();
}
var result = CheckForSpam(text, settings.Action, part);
return result;
}

View File

@@ -1,10 +1,11 @@
using System.Collections.Generic;
using Orchard.AntiSpam.Models;
using Orchard.AntiSpam.Settings;
using Orchard.ContentManagement;
namespace Orchard.AntiSpam.Services {
public interface ISpamService : IDependency {
SpamStatus CheckForSpam(string text, SpamFilterAction action);
SpamStatus CheckForSpam(string text, SpamFilterAction action, IContent content);
SpamStatus CheckForSpam(SpamFilterPart part);
/// <summary>

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.AntiSpam.Models;
using Orchard.ContentManagement.MetaData;
using Orchard.Localization;
using Orchard.UI.Admin.Notification;
using Orchard.UI.Notify;
namespace Orchard.AntiSpam.Services {
public class MissingFilterBanner : INotificationProvider {
private readonly ISpamService _spamService;
private readonly IContentDefinitionManager _contentDefinitionManager;
public MissingFilterBanner(ISpamService spamService, IContentDefinitionManager contentDefinitionManager) {
_spamService = spamService;
_contentDefinitionManager = contentDefinitionManager;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public IEnumerable<NotifyEntry> GetNotifications() {
// if there is any content type with Spam Part, ensure there is a filter available
var typeHasPart = _contentDefinitionManager.ListTypeDefinitions().Any(t => t.Parts.Any(p => p.PartDefinition.Name.Equals(typeof (SpamFilterPart).Name)));
if(typeHasPart && !_spamService.GetSpamFilters().Any()) {
yield return new NotifyEntry {Message = T("Anti-spam protection requires at least one anti-spam filter to be enabled and configured."), Type = NotifyType.Warning};
}
}
}
}

View File

@@ -1,7 +0,0 @@
namespace Orchard.AntiSpam.Settings {
public class ReCaptchaPartSettings {
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
public bool ByPassAuthenticated { get; set; }
}
}

View File

@@ -1,40 +0,0 @@
using System.Collections.Generic;
using System.Globalization;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.ViewModels;
using Orchard.Localization;
namespace Orchard.AntiSpam.Settings {
public class ReCaptchaPartSettingsEvents : ContentDefinitionEditorEventsBase {
public Localizer T { get; set; }
public override IEnumerable<TemplateViewModel> TypePartEditor(ContentTypePartDefinition definition) {
if (definition.PartDefinition.Name != "ReCaptchaPart")
yield break;
var settings = definition.Settings.GetModel<ReCaptchaPartSettings>();
yield return DefinitionTemplate(settings);
}
public override IEnumerable<TemplateViewModel> TypePartEditorUpdate(ContentTypePartDefinitionBuilder builder, IUpdateModel updateModel) {
if (builder.Name != "ReCaptchaPart")
yield break;
var settings = new ReCaptchaPartSettings {
};
if (updateModel.TryUpdateModel(settings, "ReCaptchaPartSettings", null, null)) {
builder.WithSetting("ReCaptchaPartSettings.PublicKey", settings.PublicKey);
builder.WithSetting("ReCaptchaPartSettings.PrivateKey", settings.PrivateKey);
builder.WithSetting("ReCaptchaPartSettings.ByPassAuthenticated", settings.ByPassAuthenticated.ToString(CultureInfo.InvariantCulture));
}
yield return DefinitionTemplate(settings);
}
}
}

View File

@@ -2,13 +2,14 @@
public class SpamFilterPartSettings {
public SpamFilterAction Action { get; set; }
public string Pattern { get; set; }
public bool DeleteSpam { get; set; }
}
/// <summary>
/// The action to take when spam filters occur
/// </summary>
public enum SpamFilterAction {
AllOrNothing, // Mark as spam if all provider declare spam
One // Mark as spam if at least one declares spam
One, // Mark as spam if at least one declares spam
AllOrNothing // Mark as spam if all provider declare spam
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
@@ -30,6 +31,7 @@ namespace Orchard.AntiSpam.Settings {
if (updateModel.TryUpdateModel(settings, "SpamFilterPartSettings", null, null)) {
builder.WithSetting("SpamFilterPartSettings.Action", settings.Action.ToString());
builder.WithSetting("SpamFilterPartSettings.Pattern", settings.Pattern);
builder.WithSetting("SpamFilterPartSettings.DeleteSpam", settings.DeleteSpam.ToString(CultureInfo.InvariantCulture));
}
yield return DefinitionTemplate(settings);

View File

@@ -16,6 +16,10 @@ namespace Orchard.AntiSpam.ViewModels {
}
public class SpamIndexOptions {
public SpamIndexOptions() {
Filter = SpamFilter.Spam;
}
public string Search { get; set; }
public SpamOrder Order { get; set; }
public SpamFilter Filter { get; set; }

View File

@@ -22,3 +22,11 @@
<span class="hint">@T("The tokenized pattern generating the text to submit to spam filters.")</span>
</div>
</fieldset>
<fieldset>
<div>
@Html.EditorFor(m => m.DeleteSpam)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.DeleteSpam)">@T("Delete spam when found")</label>
<span class="hint">@T("Enable to have spam automatically deleted when found. You won't be able to find false positive.")</span>
</div>
</fieldset>

View File

@@ -1,27 +1,24 @@
@model ReCaptchaPartSettings
@using Orchard.AntiSpam.Settings;
@model Orchard.AntiSpam.Models.ReCaptchaSettingsPartRecord
<fieldset>
<legend>@T("ReCaptcha")</legend>
<div>
@Html.TextBoxFor(m => m.PublicKey, new { @class = "textMedium"})
<span class="hint">@T("Your public key.")</span>
</div>
</fieldset>
<fieldset>
<div>
@Html.TextBoxFor(m => m.PrivateKey, new { @class = "textMedium"})
<span class="hint">@T("Your private key.")</span>
</div>
</fieldset>
@T("Get custom keys:") <a href="http://www.google.com/recaptcha">http://www.google.com/recaptcha</a>
@T("Get custom keys:") <a href="http://www.google.com/recaptcha">http://www.google.com/recaptcha</a>
<fieldset>
<div>
@Html.EditorFor(m => m.ByPassAuthenticated, new { @class = "textMedium"})
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.ByPassAuthenticated)">@T("Hide for authenticated users")</label>
@Html.EditorFor(m => m.TrustAuthenticatedUsers, new { @class = "textMedium"})
<label class="forcheckbox" for="@Html.FieldIdFor( m => m.TrustAuthenticatedUsers)">@T("Hide for authenticated users")</label>
<span class="hint">@T("Enable to hide reCaptcha when the user is authenticated.")</span>
</div>
</fieldset>

View File

@@ -0,0 +1,14 @@
@using Orchard.AntiSpam.Models
@using Orchard.Utility.Extensions
@{
SpamFilterPart part = Model.ContentPart;
}
@if (Model.ContentPart.Status == SpamStatus.Spam) {
@Html.Link(@T("Not Spam").Text, Url.Action("ReportHam", "Admin", new {area = "Orchard.AntiSpam", part.Id, returnUrl = Request.ToUrlString()}), new {itemprop = "UnsafeUrl"})
}
else {
@Html.Link(@T("Spam").Text, Url.Action("ReportSpam", "Admin", new {area = "Orchard.AntiSpam", part.Id, returnUrl = Request.ToUrlString()}), new {itemprop = "UnsafeUrl"})
}
@T(" | ")

View File

@@ -61,9 +61,6 @@ namespace Orchard.Comments.Controllers {
case CommentIndexFilter.Pending:
commentsQuery = _commentService.GetComments(CommentStatus.Pending);
break;
case CommentIndexFilter.Spam:
commentsQuery = _commentService.GetComments(CommentStatus.Spam);
break;
default:
throw new ArgumentOutOfRangeException();
}
@@ -73,7 +70,7 @@ namespace Orchard.Comments.Controllers {
.OrderByDescending<CommentPartRecord>(cpr => cpr.CommentDateUtc)
.Slice(pager.GetStartIndex(), pager.PageSize)
.ToList()
.Select(comment => CreateCommentEntry(comment));
.Select(CreateCommentEntry);
var model = new CommentsIndexViewModel {
Comments = entries.ToList(),
@@ -94,14 +91,6 @@ namespace Orchard.Comments.Controllers {
switch (viewModel.Options.BulkAction) {
case CommentIndexBulkAction.None:
break;
case CommentIndexBulkAction.MarkAsSpam:
if (!_orchardServices.Authorizer.Authorize(Permissions.ManageComments, T("Couldn't moderate comment")))
return new HttpUnauthorizedResult();
//TODO: Transaction
foreach (CommentEntry entry in checkedEntries) {
_commentService.MarkCommentAsSpam(entry.Comment.Id);
}
break;
case CommentIndexBulkAction.Unapprove:
if (!_orchardServices.Authorizer.Authorize(Permissions.ManageComments, T("Couldn't moderate comment")))
return new HttpUnauthorizedResult();
@@ -151,9 +140,6 @@ namespace Orchard.Comments.Controllers {
case CommentDetailsFilter.Pending:
comments = _commentService.GetCommentsForCommentedContent(id, CommentStatus.Pending);
break;
case CommentDetailsFilter.Spam:
comments = _commentService.GetCommentsForCommentedContent(id, CommentStatus.Spam);
break;
default:
throw new ArgumentOutOfRangeException();
}
@@ -178,14 +164,6 @@ namespace Orchard.Comments.Controllers {
switch (viewModel.Options.BulkAction) {
case CommentDetailsBulkAction.None:
break;
case CommentDetailsBulkAction.MarkAsSpam:
if (!_orchardServices.Authorizer.Authorize(Permissions.ManageComments, T("Couldn't moderate comment")))
return new HttpUnauthorizedResult();
//TODO: Transaction
foreach (CommentEntry entry in checkedEntries) {
_commentService.MarkCommentAsSpam(entry.Comment.Id);
}
break;
case CommentDetailsBulkAction.Unapprove:
if (!_orchardServices.Authorizer.Authorize(Permissions.ManageComments, T("Couldn't moderate comment")))
return new HttpUnauthorizedResult();
@@ -296,21 +274,6 @@ namespace Orchard.Comments.Controllers {
return this.RedirectLocal(returnUrl, () => RedirectToAction("Details", new { id = commentedOn }));
}
[HttpPost]
public ActionResult MarkAsSpam(int id, string returnUrl) {
if (!_orchardServices.Authorizer.Authorize(Permissions.ManageComments, T("Couldn't mark comment as spam")))
return new HttpUnauthorizedResult();
var commentPart = _contentManager.Get<CommentPart>(id);
if (commentPart == null)
return new HttpNotFoundResult();
int commentedOn = commentPart.Record.CommentedOn;
_commentService.MarkCommentAsSpam(id);
return this.RedirectLocal(returnUrl, () => RedirectToAction("Details", new { id = commentedOn }));
}
[HttpPost]
public ActionResult Delete(int id, string returnUrl) {
if (!_orchardServices.Authorizer.Authorize(Permissions.ManageComments, T("Couldn't delete comment")))
@@ -329,7 +292,7 @@ namespace Orchard.Comments.Controllers {
private CommentEntry CreateCommentEntry(CommentPart item) {
return new CommentEntry {
Comment = item.Record,
Shape = _contentManager.BuildDisplay(item, "SummaryAdmin"),
CommentedOn = _commentService.GetCommentedContent(item.CommentedOn),
IsChecked = false,
};
}

View File

@@ -3,7 +3,6 @@ using System.Web.Mvc;
using Orchard.Comments.Models;
using Orchard.Comments.Services;
using Orchard.Comments.Settings;
using Orchard.Comments.ViewModels;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Mvc.Extensions;
@@ -30,82 +29,80 @@ namespace Orchard.Comments.Controllers {
if (!Services.Authorizer.Authorize(Permissions.AddComment, T("Couldn't add comment")))
return this.RedirectLocal(returnUrl, "~/");
var comment = Services.ContentManager.New("Comment");
var comment = Services.ContentManager.New<CommentPart>("Comment");
var editorShape = Services.ContentManager.UpdateEditor(comment, this);
Services.ContentManager.Create(comment);
if (ModelState.IsValid) {
if (comment.Has<CommentPart>()) {
var commentPart = comment.As<CommentPart>();
var commentPart = comment.As<CommentPart>();
// ensure the comments are not closed on the container, as the html could have been tampered manually
var container = Services.ContentManager.Get(commentPart.CommentedOn);
CommentsPart commentsPart = null;
if(container != null) {
commentsPart = container.As<CommentsPart>();
if (commentsPart != null) {
var settings = commentsPart.TypePartDefinition.Settings.GetModel<CommentsPartSettings>();
if (!commentsPart.CommentsActive
|| (settings.MustBeAuthenticated && Services.WorkContext.CurrentUser == null)) {
Services.TransactionManager.Cancel();
return this.RedirectLocal(returnUrl, "~/");
}
// ensure the comments are not closed on the container, as the html could have been tampered manually
var container = Services.ContentManager.Get(commentPart.CommentedOn);
CommentsPart commentsPart = null;
if(container != null) {
commentsPart = container.As<CommentsPart>();
if (commentsPart != null) {
var settings = commentsPart.TypePartDefinition.Settings.GetModel<CommentsPartSettings>();
if (!commentsPart.CommentsActive
|| (settings.MustBeAuthenticated && Services.WorkContext.CurrentUser == null)) {
Services.TransactionManager.Cancel();
return this.RedirectLocal(returnUrl, "~/");
}
}
}
// is it a response to another comment ?
if(commentPart.RepliedOn.HasValue && commentsPart != null && commentsPart.ThreadedComments) {
var replied = Services.ContentManager.Get(commentPart.RepliedOn.Value);
if(replied != null) {
var repliedPart = replied.As<CommentPart>();
// is it a response to another comment ?
if(commentPart.RepliedOn.HasValue && commentsPart != null && commentsPart.ThreadedComments) {
var replied = Services.ContentManager.Get(commentPart.RepliedOn.Value);
if(replied != null) {
var repliedPart = replied.As<CommentPart>();
// what is the next position after the anwered comment
if(repliedPart != null) {
// the next comment is the one right after the RepliedOn one, at the same level
var nextComment = _commentService.GetCommentsForCommentedContent(commentPart.CommentedOn)
.Where(x => x.RepliedOn == repliedPart.RepliedOn && x.CommentDateUtc > repliedPart.CommentDateUtc)
.OrderBy(x => x.Position)
.Slice(0, 1)
.FirstOrDefault();
// what is the next position after the anwered comment
if(repliedPart != null) {
// the next comment is the one right after the RepliedOn one, at the same level
var nextComment = _commentService.GetCommentsForCommentedContent(commentPart.CommentedOn)
.Where(x => x.RepliedOn == repliedPart.RepliedOn && x.CommentDateUtc > repliedPart.CommentDateUtc)
.OrderBy(x => x.Position)
.Slice(0, 1)
.FirstOrDefault();
// the previous comment is the last one under the RepliedOn
var previousComment = _commentService.GetCommentsForCommentedContent(commentPart.CommentedOn)
.Where(x => x.RepliedOn == commentPart.RepliedOn)
.OrderByDescending(x => x.Position)
.Slice(0, 1)
.FirstOrDefault();
// the previous comment is the last one under the RepliedOn
var previousComment = _commentService.GetCommentsForCommentedContent(commentPart.CommentedOn)
.Where(x => x.RepliedOn == commentPart.RepliedOn)
.OrderByDescending(x => x.Position)
.Slice(0, 1)
.FirstOrDefault();
if(nextComment == null) {
commentPart.Position = repliedPart.Position + 1;
if(nextComment == null) {
commentPart.Position = repliedPart.Position + 1;
}
else {
if (previousComment == null) {
commentPart.Position = (repliedPart.Position + nextComment.Position) / 2;
}
else {
if (previousComment == null) {
commentPart.Position = (repliedPart.Position + nextComment.Position) / 2;
}
else {
commentPart.Position = (previousComment.Position + nextComment.Position) / 2;
}
commentPart.Position = (previousComment.Position + nextComment.Position) / 2;
}
}
}
}
}
else {
// new comment, last in position
commentPart.RepliedOn = null;
commentPart.Position = comment.Id;
}
if (commentPart.Status == CommentStatus.Pending) {
// if the user who submitted the comment has the right to moderate, don't make this comment moderated
if (Services.Authorizer.Authorize(Permissions.ManageComments)) {
commentPart.Status = CommentStatus.Approved;
}
else {
// new comment, last in position
commentPart.RepliedOn = null;
commentPart.Position = comment.Id;
}
if (commentPart.Status == CommentStatus.Pending) {
// if the user who submitted the comment has the right to moderate, don't make this comment moderated
if (Services.Authorizer.Authorize(Permissions.ManageComments)) {
commentPart.Status = CommentStatus.Approved;
}
else {
Services.Notifier.Information(T("Your comment will appear after the site administrator approves it."));
}
Services.Notifier.Information(T("Your comment will appear after the site administrator approves it."));
}
}
}

View File

@@ -19,7 +19,6 @@ namespace Orchard.Comments.Drivers {
private readonly IContentManager _contentManager;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IClock _clock;
private readonly ICommentValidator _commentValidator;
private readonly IEnumerable<IHtmlFilter> _htmlFilters;
protected override string Prefix { get { return "Comments"; } }
@@ -31,12 +30,10 @@ namespace Orchard.Comments.Drivers {
IWorkContextAccessor workContextAccessor,
IClock clock,
ICommentService commentService,
ICommentValidator commentValidator,
IEnumerable<IHtmlFilter> htmlFilters) {
_contentManager = contentManager;
_workContextAccessor = workContextAccessor;
_clock = clock;
_commentValidator = commentValidator;
_htmlFilters = htmlFilters;
T = NullLocalizer.Instance;
@@ -86,11 +83,8 @@ namespace Orchard.Comments.Drivers {
if (String.IsNullOrEmpty(part.Author)) updater.AddModelError("NameMissing", T("You didn't specify your name."));
// applying anti-spam filters
var moderateComments = workContext.CurrentSite.As<CommentSettingsPart>().Record.ModerateComments;
part.Status = _commentValidator.ValidateComment(part)
? moderateComments ? CommentStatus.Pending : CommentStatus.Approved
: CommentStatus.Spam;
part.Status = moderateComments ? CommentStatus.Pending : CommentStatus.Approved;
var commentedOn = _contentManager.Get<ICommonPart>(part.CommentedOn);
if (commentedOn != null && commentedOn.Container != null) {

View File

@@ -1,39 +0,0 @@
using System.Collections.Generic;
using System.Dynamic;
using Orchard.Comments.Models;
using Orchard.Comments.Services;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.UI.Admin.Notification;
using Orchard.UI.Notify;
namespace Orchard.Comments.Drivers {
public class MissingFilterBanner : INotificationProvider {
private readonly IOrchardServices _orchardServices;
private readonly ICheckSpamEventHandler _spamEventHandler;
public MissingFilterBanner(IOrchardServices orchardServices, ICheckSpamEventHandler spamEventHandler) {
_orchardServices = orchardServices;
_spamEventHandler = spamEventHandler;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public IEnumerable<NotifyEntry> GetNotifications() {
var commentSettings = _orchardServices.WorkContext.CurrentSite.As<CommentSettingsPart>();
dynamic context = new ExpandoObject();
context.Checked = false;
context.IsSpam = false;
context.Text = string.Empty;
_spamEventHandler.CheckSpam(context);
if (commentSettings != null && commentSettings.EnableSpamProtection && !context.Checked) {
yield return new NotifyEntry {Message = T("Comments anti-spam protection requires at least one anti-spam filter to be enabled and configured."), Type = NotifyType.Warning};
}
}
}
}

View File

@@ -34,7 +34,6 @@ namespace Orchard.Comments {
SchemaBuilder.CreateTable("CommentSettingsPartRecord", table => table
.ContentPartRecord()
.Column<bool>("ModerateComments")
.Column<bool>("EnableSpamProtection")
);
SchemaBuilder.CreateTable("CommentsPartRecord", table => table
@@ -106,6 +105,10 @@ namespace Orchard.Comments {
.DropColumn("AkismetUrl")
);
SchemaBuilder.AlterTable("CommentSettingsPartRecord", table => table
.DropColumn("EnableSpamProtected")
);
SchemaBuilder.AlterTable("CommentPartRecord", table => table
.AddColumn<int>("RepliedOn", c => c.WithDefault(null))
);

View File

@@ -6,10 +6,5 @@ namespace Orchard.Comments.Models {
get { return Record.ModerateComments; }
set { Record.ModerateComments = value; }
}
public bool EnableSpamProtection {
get { return Record.EnableSpamProtection; }
set { Record.EnableSpamProtection = value; }
}
}
}

View File

@@ -3,6 +3,5 @@ using Orchard.ContentManagement.Records;
namespace Orchard.Comments.Models {
public class CommentSettingsPartRecord : ContentPartRecord {
public virtual bool ModerateComments { get; set; }
public virtual bool EnableSpamProtection { get; set; }
}
}

View File

@@ -2,6 +2,5 @@ namespace Orchard.Comments.Models {
public enum CommentStatus {
Pending,
Approved,
Spam
}
}

View File

@@ -63,7 +63,6 @@
<Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\CommentController.cs" />
<Compile Include="Drivers\MissingFilterBanner.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Drivers\CommentPartDriver.cs" />
<Compile Include="Drivers\CommentsContainerPartDriver.cs" />
@@ -73,9 +72,7 @@
<Compile Include="ResourceManifest.cs" />
<Compile Include="Rules\CommentsActions.cs" />
<Compile Include="Rules\CommentsForms.cs" />
<Compile Include="Services\CommentValidator.cs" />
<Compile Include="Services\HtmlEncodeFilter.cs" />
<Compile Include="Services\ICheckSpamEventHandler.cs" />
<Compile Include="Settings\CommentsPartSettings.cs" />
<Compile Include="Settings\CommentsPartSettingsEvents.cs" />
<Compile Include="Shapes.cs" />
@@ -100,7 +97,6 @@
<Compile Include="Permissions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\CommentService.cs" />
<Compile Include="Services\ICommentValidator.cs" />
<Compile Include="ViewModels\CommentsDetailsViewModel.cs" />
<Compile Include="ViewModels\CommentsIndexViewModel.cs" />
<Compile Include="ViewModels\CommentsPartSettingsViewModel.cs" />
@@ -161,7 +157,6 @@
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts.Comment.AdminEdit.cshtml" />
<Content Include="Views\Content-Comment.SummaryAdmin.cshtml" />
<Content Include="Views\ListOfComments.cshtml" />
<Content Include="Views\CommentReplyButton.cshtml" />
<Content Include="Views\DefinitionTemplates\CommentsPartSettingsViewModel.cshtml" />

View File

@@ -33,7 +33,7 @@
</Match>
<Match DisplayType="SummaryAdmin">
<Place Parts_Comment_SummaryAdmin="Content:10"
<Place Parts_Comment_SummaryAdmin="Actions:6"
Parts_Comments_Count_SummaryAdmin="Meta:4" />
</Match>
</Placement>

View File

@@ -63,11 +63,6 @@ namespace Orchard.Comments.Services {
commentPart.Record.Status = CommentStatus.Pending;
}
public void MarkCommentAsSpam(int commentId) {
var commentPart = GetCommentWithQueryHints(commentId);
commentPart.Record.Status = CommentStatus.Spam;
}
public void DeleteComment(int commentId) {
_orchardServices.ContentManager.Remove(_orchardServices.ContentManager.Get(commentId));
}

View File

@@ -1,28 +0,0 @@
using System.Dynamic;
using Orchard.Comments.Models;
namespace Orchard.Comments.Services {
public class AntiSpamFilterValidator : ICommentValidator {
private readonly ICheckSpamEventHandler _spamEventHandler;
public AntiSpamFilterValidator(ICheckSpamEventHandler spamEventHandler) {
_spamEventHandler = spamEventHandler;
}
public bool ValidateComment(CommentPart commentPart) {
var text = commentPart.Author + System.Environment.NewLine
+ commentPart.CommentText + System.Environment.NewLine
+ commentPart.Email + System.Environment.NewLine
+ commentPart.SiteName + System.Environment.NewLine;
dynamic context = new ExpandoObject();
context.Checked = false;
context.IsSpam = false;
context.Text = text;
_spamEventHandler.CheckSpam(context);
return !context.IsSpam;
}
}
}

View File

@@ -1,10 +0,0 @@
using Orchard.Events;
namespace Orchard.Comments.Services {
/// <summary>
/// Stub interface for Orchard.AntiSpam.EventHandlers.ISpamEventHandler
/// </summary>
public interface ICheckSpamEventHandler : IEventHandler {
void CheckSpam(dynamic context);
}
}

View File

@@ -12,7 +12,6 @@ namespace Orchard.Comments.Services {
ContentItem GetCommentedContent(int id);
void ApproveComment(int commentId);
void UnapproveComment(int commentId);
void MarkCommentAsSpam(int commentId);
void DeleteComment(int commentId);
bool CommentsDisabledForCommentedContent(int id);
void DisableCommentsForCommentedContent(int id);

View File

@@ -1,7 +0,0 @@
using Orchard.Comments.Models;
namespace Orchard.Comments.Services {
public interface ICommentValidator : IDependency {
bool ValidateComment(CommentPart commentPart);
}
}

View File

@@ -18,14 +18,12 @@ namespace Orchard.Comments.ViewModels {
None,
Unapprove,
Approve,
MarkAsSpam,
Delete,
Delete
}
public enum CommentDetailsFilter {
All,
Pending,
Approved,
Spam,
Approved
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Web.Mvc;
using Orchard.Comments.Models;
using Orchard.ContentManagement;
namespace Orchard.Comments.ViewModels {
public class CommentsIndexViewModel {
@@ -9,10 +10,9 @@ namespace Orchard.Comments.ViewModels {
public dynamic Pager { get; set; }
}
[Bind(Exclude = "Shape")]
public class CommentEntry {
public CommentPartRecord Comment { get; set; }
public dynamic Shape { get; set; }
public IContent CommentedOn { get; set; }
public bool IsChecked { get; set; }
}
@@ -25,7 +25,6 @@ namespace Orchard.Comments.ViewModels {
None,
Unapprove,
Approve,
MarkAsSpam,
Delete
}
@@ -33,6 +32,5 @@ namespace Orchard.Comments.ViewModels {
All,
Pending,
Approved,
Spam,
}
}

View File

@@ -1,5 +1,4 @@
@model Orchard.Comments.ViewModels.CommentsDetailsViewModel
@using Orchard.Comments.Models;
@model CommentsDetailsViewModel
@using Orchard.Comments.ViewModels;
@{ Layout.Title = T("Comments for {0}", Model.DisplayNameForCommentedItem).ToString(); }
@@ -27,7 +26,6 @@
@Html.SelectOption(Model.Options.BulkAction, CommentDetailsBulkAction.None, T("Choose action...").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentDetailsBulkAction.Approve, T("Approve").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentDetailsBulkAction.Unapprove, T("Unapprove").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentDetailsBulkAction.MarkAsSpam, T("Mark as Spam").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentDetailsBulkAction.Delete, T("Delete").ToString())
</select>
<button type="submit" name="submit.BulkEdit" value="@T("Apply")">@T("Apply")</button>
@@ -38,7 +36,6 @@
@Html.SelectOption(Model.Options.Filter, CommentDetailsFilter.All, T("All Comments").ToString())
@Html.SelectOption(Model.Options.Filter, CommentDetailsFilter.Approved, T("Approved Comments").ToString())
@Html.SelectOption(Model.Options.Filter, CommentDetailsFilter.Pending, T("Pending Comments").ToString())
@Html.SelectOption(Model.Options.Filter, CommentDetailsFilter.Spam, T("Spam").ToString())
</select>
<button type="submit" name="submit.Filter" value="@T("Apply")">@T("Apply")</button>
</fieldset>
@@ -72,8 +69,7 @@
<input type="hidden" value="@Model.CommentedItemId" name="CommentedItemId" />
</td>
<td>
@if (commentEntry.Comment.Status == CommentStatus.Spam) { @T("Spam"); }
else if (commentEntry.Comment.Status == CommentStatus.Pending) { @T("Pending"); }
if (commentEntry.Comment.Status == CommentStatus.Pending) { @T("Pending"); }
else { @T("Approved"); }
</td>
<td>@commentEntry.Comment.Author</td>

View File

@@ -1,8 +1,8 @@
@model Orchard.Comments.ViewModels.CommentsIndexViewModel
@using Orchard.Comments.Models;
@model CommentsIndexViewModel
@using Orchard.Comments.Models
@using Orchard.Comments.ViewModels;
@using Orchard.Mvc.Html;
@using Orchard.Utility.Extensions;
@using Orchard.Utility.Extensions
@{
Style.Require("Admin");
@@ -19,7 +19,6 @@
@Html.SelectOption(Model.Options.BulkAction, CommentIndexBulkAction.None, T("Choose action...").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentIndexBulkAction.Approve, T("Approve").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentIndexBulkAction.Unapprove, T("Unapprove").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentIndexBulkAction.MarkAsSpam, T("Mark as Spam").ToString())
@Html.SelectOption(Model.Options.BulkAction, CommentIndexBulkAction.Delete, T("Delete").ToString())
</select>
<button type="submit" name="submit.BulkEdit" value="@T("Apply")">@T("Apply")</button>
@@ -30,7 +29,6 @@
@Html.SelectOption(Model.Options.Filter, CommentIndexFilter.All, T("All Comments").ToString())
@Html.SelectOption(Model.Options.Filter, CommentIndexFilter.Approved, T("Approved Comments").ToString())
@Html.SelectOption(Model.Options.Filter, CommentIndexFilter.Pending, T("Pending Comments").ToString())
@Html.SelectOption(Model.Options.Filter, CommentIndexFilter.Spam, T("Spam").ToString())
</select>
<button type="submit" name="submit.Filter" value="@T("Apply")">@T("Apply")</button>
</fieldset>
@@ -65,8 +63,41 @@
<input type="hidden" value="@Model.Comments[commentIndex].Comment.Id" name="@Html.NameOf(m => m.Comments[commentIndex].Comment.Id)"/>
<input type="checkbox" value="true" name="@Html.NameOf(m => m.Comments[commentIndex].IsChecked)"/>
</td>
@Display(commentEntry.Shape)
</tr>
<td>
@if (commentEntry.Comment.Status == CommentStatus.Pending) { @T("Pending") }
else { @T("Approved") }
</td>
<td>
<div>@commentEntry.Comment.Author</div>
@if (HasText(commentEntry.Comment.UserName) && commentEntry.Comment.Author != commentEntry.Comment.UserName) {
<div class="authenticated-commenter-id">@commentEntry.Comment.UserName</div>
}
</td>
<td>
@* would ideally have permalinks for individual comments *@
<p><a href="@Url.ItemDisplayUrl(commentEntry.CommentedOn)#comments"><time>@Display.DateTime(DateTimeUtc: commentEntry.Comment.CommentDateUtc.GetValueOrDefault())</time></a></p>
@if (commentEntry.Comment.CommentText != null) {
var ellipsized = Html.Ellipsize(commentEntry.Comment.CommentText, 500);
var paragraphed = new HtmlString(ellipsized.ToHtmlString().Replace("\r\n", "</p><p>"));
<p>@paragraphed</p>
}
else {
@T("[Empty]")
}
</td>
<td>@Html.ItemDisplayLink(commentEntry.CommentedOn)</td>
<td>
<div class="actions">
@if (commentEntry.Comment.Status != CommentStatus.Approved) {
<a href="@Url.Action("Approve", new {commentEntry.Comment.Id, returnUrl = Request.ToUrlString()})" itemprop="ApproveUrl UnsafeUrl">@T("Approve")</a>@T(" | ")
}
else {
<a href="@Url.Action("Unapprove", new {commentEntry.Comment.Id, returnUrl = Request.ToUrlString()})" itemprop="UnapproveUrl UnsafeUrl">@T("Unapprove")</a>@T(" | ")
}
<a href="@Url.Action("Edit", new {commentEntry.Comment.Id})" title="@T("Edit")">@T("Edit")</a>@T(" | ")
<a href="@Url.Action("Delete", new {commentEntry.Comment.Id, returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString()})" itemprop="RemoveUrl UnsafeUrl">@T("Delete")</a>
</div>
</td> </tr>
commentIndex = commentIndex + 1;
}
</table>

View File

@@ -1,3 +0,0 @@
@if (Model.Content != null) {
@Display(Model.Content)
}

View File

@@ -35,10 +35,6 @@
@Html.RadioButtonFor(m => m.Status, CommentStatus.Approved)
<label class="forcheckbox" for="Status_Approved">@T("Approved")</label>
</div>
<div>
@Html.RadioButtonFor(m => m.Status, CommentStatus.Spam)
<label class="forcheckbox" for="Status_Spam">@T("Mark as spam")</label>
</div>
</fieldset>
<fieldset>
<button class="primaryAction" type="submit">@T("Save")</button>

View File

@@ -8,10 +8,4 @@
@Html.ValidationMessage("ModerateComments", "*")
<span class="hint">@T("Check if you want to manually approve comments before they can be displayed.")</span>
</div>
<div>
@Html.EditorFor(m => m.EnableSpamProtection)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.EnableSpamProtection)">@T("Enable spam protection")</label>
@Html.ValidationMessage("EnableSpamProtection", "*")
<span class="hint">@T("Check to use an anti-spam filter on incoming comments. This requires that you have also enabled an actual filter like Akismet or Typepad.")</span>
</div>
</fieldset>

View File

@@ -1,46 +1,19 @@
@using Orchard.Comments.Models;
@using Orchard.Utility.Extensions;
@using Orchard.ContentManagement
@using Orchard.Utility.Extensions
@{
var comment = (Orchard.Comments.Models.CommentPart)Model.ContentPart;
CommentPart comment = Model.ContentPart;
var settings = WorkContext.CurrentSite.As<CommentSettingsPart>();
}
<td>
@if (comment.Status == CommentStatus.Spam) { @T("Spam") }
else if (comment.Status == CommentStatus.Pending) { @T("Pending") }
else { @T("Approved") }
</td>
<td>
<div>@comment.Author</div>
@if (HasText(comment.UserName) && comment.Author != comment.UserName) {
<div class="authenticated-commenter-id">@comment.UserName</div>
}
</td>
<td>
@* would ideally have permalinks for individual comments *@
<p><a href="@Url.RouteUrl(comment.CommentedOnContentItemMetadata.DisplayRouteValues)#comments"><time>@Display.DateTime(DateTimeUtc: comment.CommentDateUtc.GetValueOrDefault())</time></a></p>
@if (comment.CommentText != null) {
var ellipsized = Html.Ellipsize(comment.CommentText, 500);
var paragraphed = new HtmlString(ellipsized.ToHtmlString().Replace("\r\n", "</p><p>"));
<p>@paragraphed</p>
}
else {
@T("[Empty]")
}
</td>
<td>@Html.Link(comment.CommentedOnContentItemMetadata.DisplayText, @Url.RouteUrl(comment.CommentedOnContentItemMetadata.DisplayRouteValues))</td>
<td>
<div class="actions">
@if (comment.Status != CommentStatus.Spam) {
<a href="@Url.Action("MarkAsSpam", new { comment.Id, returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() })" itemprop="RemoveUrl UnsafeUrl">@T("Spam")</a>@T(" | ")
}
@if (comment.Status != CommentStatus.Approved) {
<a href="@Url.Action("Approve", new { comment.Id, returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() })" itemprop="ApproveUrl UnsafeUrl">@T("Approve")</a>@T(" | ")
}
else {
<a href="@Url.Action("Unapprove", new { comment.Id, returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() })" itemprop="UnapproveUrl UnsafeUrl">@T("Unapprove")</a>@T(" | ")
}
<a href="@Url.Action("Edit", new { comment.Id })" title="@T("Edit")">@T("Edit")</a>@T(" | ")
<a href="@Url.Action("Delete", new { comment.Id, returnUrl = ViewContext.RequestContext.HttpContext.Request.ToUrlString() })" itemprop="RemoveUrl UnsafeUrl">@T("Delete")</a>
</div>
</td>
@if(settings.ModerateComments) {
if (comment.Status != CommentStatus.Pending) {
@Html.Link(@T("Approve").Text, Url.Action("Approve", "Admin", new {area = "Orchard.Comments", comment.Id, returnUrl = Request.ToUrlString()}), new {itemprop = "UnsafeUrl"})
}
else {
@Html.Link(@T("Unapprove").Text, Url.Action("Unapprove", "Admin", new {area = "Orchard.Comments", comment.Id, returnUrl = Request.ToUrlString()}), new {itemprop = "UnsafeUrl"})
}
@T(" | ")
}

View File

@@ -16,6 +16,4 @@
</header>
<p class="text">@Html.Raw(Convert.ToString(Model.FormattedText))</p>
@**@

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.FieldStorage;

View File

@@ -349,6 +349,10 @@ namespace Orchard.ContentManagement {
// invoke handlers to acquire state, or at least establish lazy loading callbacks
Handlers.Invoke(handler => handler.Publishing(context), Logger);
if(context.Cancel) {
return;
}
if (previous != null) {
previous.Published = false;
}

View File

@@ -9,5 +9,7 @@ namespace Orchard.ContentManagement.Handlers {
public ContentItemVersionRecord PublishingItemVersionRecord { get; set; }
public ContentItemVersionRecord PreviousItemVersionRecord { get; set; }
public bool Cancel { get; set; }
}
}