mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-01-23 21:32:14 +08:00
Refactoring comments to be able to use SpamFilter
--HG-- branch : 1.x
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<Settings>
|
||||
<SiteSettingsPart PageSize="30" />
|
||||
<CommentSettingsPart enableSpamProtection="true" />
|
||||
<CommentSettingsPart />
|
||||
</Settings>
|
||||
|
||||
<Migration features="f2,f4"/>
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
|
||||
@@ -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, "~/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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(" | ")
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
);
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,5 @@ namespace Orchard.Comments.Models {
|
||||
public enum CommentStatus {
|
||||
Pending,
|
||||
Approved,
|
||||
Spam
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Orchard.Comments.Models;
|
||||
|
||||
namespace Orchard.Comments.Services {
|
||||
public interface ICommentValidator : IDependency {
|
||||
bool ValidateComment(CommentPart commentPart);
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,12 @@ namespace Orchard.Comments.ViewModels {
|
||||
None,
|
||||
Unapprove,
|
||||
Approve,
|
||||
MarkAsSpam,
|
||||
Delete,
|
||||
Delete
|
||||
}
|
||||
|
||||
public enum CommentDetailsFilter {
|
||||
All,
|
||||
Pending,
|
||||
Approved,
|
||||
Spam,
|
||||
Approved
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
@if (Model.Content != null) {
|
||||
@Display(Model.Content)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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(" | ")
|
||||
}
|
||||
@@ -16,6 +16,4 @@
|
||||
</header>
|
||||
<p class="text">@Html.Raw(Convert.ToString(Model.FormattedText))</p>
|
||||
|
||||
@**@
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -9,5 +9,7 @@ namespace Orchard.ContentManagement.Handlers {
|
||||
|
||||
public ContentItemVersionRecord PublishingItemVersionRecord { get; set; }
|
||||
public ContentItemVersionRecord PreviousItemVersionRecord { get; set; }
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user