mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Including Orchard.AntiSpam
--HG-- branch : 1.x
This commit is contained in:
16
src/Orchard.Web/Modules/Orchard.AntiSpam/AdminMenu.cs
Normal file
16
src/Orchard.Web/Modules/Orchard.AntiSpam/AdminMenu.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Orchard.Localization;
|
||||
using Orchard.UI.Navigation;
|
||||
|
||||
namespace Orchard.AntiSpam {
|
||||
public class AdminMenu : INavigationProvider {
|
||||
public Localizer T { get; set; }
|
||||
public string MenuName { get { return "admin"; } }
|
||||
|
||||
public void GetNavigation(NavigationBuilder builder) {
|
||||
builder.Add(T("Spam"), "11",
|
||||
menu => menu
|
||||
.Add(T("Manage Spam"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.AntiSpam" }).Permission(Permissions.ManageAntiSpam))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
25
src/Orchard.Web/Modules/Orchard.AntiSpam/Content/Web.config
Normal file
25
src/Orchard.Web/Modules/Orchard.AntiSpam/Content/Web.config
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="webpages:Enabled" value="false" />
|
||||
</appSettings>
|
||||
<system.web>
|
||||
<httpHandlers>
|
||||
<!-- iis6 - for any request in this location, return via managed static file handler -->
|
||||
<add path="*" verb="*" type="System.Web.StaticFileHandler" />
|
||||
</httpHandlers>
|
||||
</system.web>
|
||||
<system.webServer>
|
||||
<staticContent>
|
||||
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
|
||||
</staticContent>
|
||||
|
||||
<handlers accessPolicy="Script,Read">
|
||||
<!--
|
||||
iis7 - for any request to a file exists on disk, return it via native http module.
|
||||
accessPolicy 'Script' is to allow for a managed 404 page.
|
||||
-->
|
||||
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.AntiSpam.Services;
|
||||
using Orchard.AntiSpam.ViewModels;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.DisplayManagement;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Settings;
|
||||
using Orchard.UI.Navigation;
|
||||
|
||||
namespace Orchard.AntiSpam.Controllers {
|
||||
[ValidateInput(false)]
|
||||
public class AdminController : Controller {
|
||||
private readonly ISiteService _siteService;
|
||||
private readonly ISpamService _spamService;
|
||||
|
||||
public AdminController(
|
||||
IOrchardServices services,
|
||||
IShapeFactory shapeFactory,
|
||||
ISiteService siteService,
|
||||
ISpamService spamService) {
|
||||
_siteService = siteService;
|
||||
_spamService = spamService;
|
||||
Services = services;
|
||||
|
||||
T = NullLocalizer.Instance;
|
||||
Shape = shapeFactory;
|
||||
}
|
||||
|
||||
dynamic Shape { get; set; }
|
||||
public IOrchardServices Services { get; set; }
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public ActionResult Index(SpamIndexOptions options, PagerParameters pagerParameters) {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageAntiSpam, T("Not authorized to manage spam")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
|
||||
|
||||
// default options
|
||||
if (options == null)
|
||||
options = new SpamIndexOptions();
|
||||
|
||||
var query = Services.ContentManager.Query().ForPart<SpamFilterPart>();
|
||||
|
||||
switch(options.Filter) {
|
||||
case SpamFilter.Spam:
|
||||
query = query.Where<SpamFilterPartRecord>(x => x.Status == SpamStatus.Spam);
|
||||
break;
|
||||
case SpamFilter.Ham:
|
||||
query = query.Where<SpamFilterPartRecord>(x => x.Status == SpamStatus.Ham);
|
||||
break;
|
||||
case SpamFilter.All:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var pagerShape = Shape.Pager(pager).TotalItemCount(query.Count());
|
||||
|
||||
switch (options.Order) {
|
||||
case SpamOrder.Creation:
|
||||
query = query.Join<CommonPartRecord>().OrderByDescending(u => u.CreatedUtc);
|
||||
break;
|
||||
}
|
||||
|
||||
var results = query
|
||||
.Slice(pager.GetStartIndex(), pager.PageSize);
|
||||
|
||||
var model = new SpamIndexViewModel {
|
||||
Spams = results.Select(x => new SpamEntry {
|
||||
Spam = x.As<SpamFilterPart>(),
|
||||
Shape = Services.ContentManager.BuildDisplay(x, "SummaryAdmin")
|
||||
}).ToList(),
|
||||
|
||||
Options = options,
|
||||
Pager = pagerShape
|
||||
};
|
||||
|
||||
// maintain previous route data when generating page links
|
||||
var routeData = new RouteData();
|
||||
routeData.Values.Add("Options.Filter", options.Filter);
|
||||
routeData.Values.Add("Options.Search", options.Search);
|
||||
routeData.Values.Add("Options.Order", options.Order);
|
||||
|
||||
pagerShape.RouteData(routeData);
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Core.Contents.Controllers.FormValueRequired("submit.BulkEdit")]
|
||||
public ActionResult Index(SpamIndexOptions options, IEnumerable<int> itemIds) {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageAntiSpam, T("Not authorized to manage spam")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
switch (options.BulkAction) {
|
||||
case SpamBulkAction.None:
|
||||
break;
|
||||
case SpamBulkAction.Spam:
|
||||
foreach (var checkedId in itemIds) {
|
||||
var spam = Services.ContentManager.Get(checkedId);
|
||||
if(spam != null) {
|
||||
spam.As<SpamFilterPart>().Status = SpamStatus.Spam;
|
||||
_spamService.ReportSpam(spam.As<SpamFilterPart>());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SpamBulkAction.Ham:
|
||||
foreach (var checkedId in itemIds) {
|
||||
var ham = Services.ContentManager.Get(checkedId);
|
||||
if (ham != null) {
|
||||
ham.As<SpamFilterPart>().Status = SpamStatus.Ham;
|
||||
_spamService.ReportHam(ham.As<SpamFilterPart>());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SpamBulkAction.Delete:
|
||||
foreach (var checkedId in itemIds) {
|
||||
Services.ContentManager.Remove(Services.ContentManager.Get(checkedId));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Services.ContentManager.Flush();
|
||||
|
||||
return Index(options, new PagerParameters());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
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;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.AntiSpam.Drivers {
|
||||
public class ReCaptchaPartDriver : ContentPartDriver<ReCaptchaPart> {
|
||||
private readonly IWorkContextAccessor _workContextAccessor;
|
||||
private const string ReCaptchaUrl = "http://www.google.com/recaptcha/api";
|
||||
private const string ReCaptchaSecureUrl = "https://www.google.com/recaptcha/api";
|
||||
|
||||
public ReCaptchaPartDriver(IWorkContextAccessor workContextAccessor) {
|
||||
_workContextAccessor = workContextAccessor;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
protected override DriverResult Editor(ReCaptchaPart part, dynamic shapeHelper) {
|
||||
return ContentShape("Parts_ReCaptcha_Fields", () => {
|
||||
var settings = part.TypePartDefinition.Settings.GetModel<ReCaptchaPartSettings>();
|
||||
var viewModel = new ReCaptchaPartEditViewModel {
|
||||
PublicKey = settings.PublicKey
|
||||
};
|
||||
|
||||
return shapeHelper.EditorTemplate(TemplateName: "Parts.ReCaptcha.Fields", Model: viewModel, Prefix: Prefix);
|
||||
});
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(ReCaptchaPart part, IUpdateModel updater, dynamic shapeHelper) {
|
||||
|
||||
var submitViewModel = new ReCaptchaPartSubmitViewModel();
|
||||
|
||||
if(updater.TryUpdateModel(submitViewModel, String.Empty, null, null)) {
|
||||
var settings = part.TypePartDefinition.Settings.GetModel<ReCaptchaPartSettings>();
|
||||
var context = _workContextAccessor.GetContext().HttpContext;
|
||||
|
||||
var result = ExecuteValidateRequest(
|
||||
settings.PrivateKey,
|
||||
context.Request.ServerVariables["REMOTE_ADDR"],
|
||||
submitViewModel.recaptcha_challenge_field,
|
||||
submitViewModel.recaptcha_response_field
|
||||
);
|
||||
|
||||
if(!HandleValidateResponse(context, result)) {
|
||||
updater.AddModelError("Parts_ReCaptcha_Fields", T("Incorrect word"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Editor(part, shapeHelper);
|
||||
}
|
||||
|
||||
private static string ExecuteValidateRequest(string privateKey, string remoteip, string challenge, string response) {
|
||||
WebRequest request = WebRequest.Create(ReCaptchaUrl + "/verify");
|
||||
request.Method = "POST";
|
||||
request.Timeout = 5000; //milliseconds
|
||||
request.ContentType = "application/x-www-form-urlencoded";
|
||||
|
||||
var postData = String.Format(CultureInfo.InvariantCulture,
|
||||
"privatekey={0}&remoteip={1}&challenge={2}&response={3}",
|
||||
privateKey,
|
||||
remoteip,
|
||||
challenge,
|
||||
response
|
||||
);
|
||||
|
||||
byte[] content = Encoding.UTF8.GetBytes(postData);
|
||||
using (Stream stream = request.GetRequestStream()) {
|
||||
stream.Write(content, 0, content.Length);
|
||||
}
|
||||
using (WebResponse webResponse = request.GetResponse()) {
|
||||
using (var reader = new StreamReader(webResponse.GetResponseStream())) {
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool HandleValidateResponse(HttpContextBase context, string response) {
|
||||
if (!String.IsNullOrEmpty(response)) {
|
||||
string[] results = response.Split('\n');
|
||||
if (results.Length > 0) {
|
||||
bool rval = Convert.ToBoolean(results[0], CultureInfo.InvariantCulture);
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.AntiSpam.Drivers {
|
||||
public class SpamFilterPartDriver : ContentPartDriver<SpamFilterPart> {
|
||||
private const string TemplateName = "Parts/SpamFilter";
|
||||
|
||||
public SpamFilterPartDriver(IOrchardServices services) {
|
||||
T = NullLocalizer.Instance;
|
||||
Services = services;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
public IOrchardServices Services { get; set; }
|
||||
|
||||
protected override string Prefix {
|
||||
get { return "SpamFilter"; }
|
||||
}
|
||||
|
||||
protected override DriverResult Display(SpamFilterPart part, string displayType, dynamic shapeHelper) {
|
||||
return ContentShape("Parts_SpamFilter_Metadata_SummaryAdmin",
|
||||
() => shapeHelper.Parts_SpamFilter_Metadata_SummaryAdmin());
|
||||
}
|
||||
|
||||
protected override void Importing(SpamFilterPart part, ImportContentContext context) {
|
||||
var status = context.Attribute(part.PartDefinition.Name, "Status");
|
||||
|
||||
if (status != null) {
|
||||
SpamStatus value;
|
||||
if(Enum.TryParse(status, out value)) {
|
||||
part.Status = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Exporting(SpamFilterPart part, ExportContentContext context) {
|
||||
context.Element(part.PartDefinition.Name).SetAttributeValue("Status", part.Status.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.AntiSpam.Settings;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Drivers;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Services;
|
||||
|
||||
namespace Orchard.AntiSpam.Drivers {
|
||||
public class SubmissionLimitPartDriver : ContentPartDriver<SubmissionLimitPart> {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IClock _clock;
|
||||
|
||||
public SubmissionLimitPartDriver(IContentManager contentManager, IClock clock) {
|
||||
_contentManager = contentManager;
|
||||
_clock = clock;
|
||||
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
protected override DriverResult Editor(SubmissionLimitPart part, dynamic shapeHelper) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override DriverResult Editor(SubmissionLimitPart part, IUpdateModel updater, dynamic shapeHelper) {
|
||||
var settings = part.TypePartDefinition.Settings.GetModel<SubmissionLimitPartSettings>();
|
||||
|
||||
DateTime min = DateTime.MinValue, max = DateTime.MinValue;
|
||||
var now = _clock.UtcNow;
|
||||
|
||||
switch(settings.Unit) {
|
||||
case (int)SubmissionLimitUnit.Hour:
|
||||
min = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0, DateTimeKind.Utc);
|
||||
max = min.AddHours(1);
|
||||
break;
|
||||
case (int)SubmissionLimitUnit.Day:
|
||||
min = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
|
||||
max = min.AddDays(1);
|
||||
break;
|
||||
case (int)SubmissionLimitUnit.Month:
|
||||
min = new DateTime(now.Year, now.Month, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
max = min.AddMonths(1);
|
||||
break;
|
||||
case (int)SubmissionLimitUnit.Year:
|
||||
min = new DateTime(now.Year, 0, 0, now.Hour, 0, 0, DateTimeKind.Utc);
|
||||
max = min.AddYears(1);
|
||||
break;
|
||||
case (int)SubmissionLimitUnit.Overall:
|
||||
break;
|
||||
}
|
||||
|
||||
var query = _contentManager.Query()
|
||||
.ForVersion(VersionOptions.AllVersions)
|
||||
.ForType(part.ContentItem.ContentType);
|
||||
|
||||
if (min != DateTime.MinValue) {
|
||||
query = query.Join<CommonPartRecord>().Where(x => x.CreatedUtc > min && x.CreatedUtc < max);
|
||||
}
|
||||
|
||||
var result = query.Count();
|
||||
|
||||
if(result > settings.Limit) {
|
||||
updater.AddModelError("Limit", T("The limit of submissions has been reached."));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Data;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.AntiSpam.Handlers {
|
||||
[UsedImplicitly]
|
||||
[OrchardFeature("Akismet.Filter")]
|
||||
public class AkismetSettingsPartHandler : ContentHandler {
|
||||
public AkismetSettingsPartHandler(IRepository<AkismetSettingsPartRecord> repository) {
|
||||
T = NullLocalizer.Instance;
|
||||
Filters.Add(new ActivatingFilter<AkismetSettingsPart>("Site"));
|
||||
Filters.Add(StorageFilter.For(repository));
|
||||
Filters.Add(new TemplateFilterForRecord<AkismetSettingsPartRecord>("AkismetSettings", "Parts/AntiSpam.AkismetSettings", "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")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.AntiSpam.Services;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Data;
|
||||
|
||||
namespace Orchard.AntiSpam.Handlers {
|
||||
public class SpamFilterPartHandler : ContentHandler {
|
||||
public SpamFilterPartHandler(ISpamService spamService, IRepository<SpamFilterPartRecord> repository) {
|
||||
Filters.Add(StorageFilter.For(repository));
|
||||
|
||||
OnUpdated<SpamFilterPart>( (context, part) => {
|
||||
part.Status = spamService.CheckForSpam(part);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Data;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.AntiSpam.Handlers {
|
||||
[UsedImplicitly]
|
||||
[OrchardFeature("TypePad.Filter")]
|
||||
public class TypePadSettingsPartHandler : ContentHandler {
|
||||
public TypePadSettingsPartHandler(IRepository<TypePadSettingsPartRecord> repository) {
|
||||
T = NullLocalizer.Instance;
|
||||
Filters.Add(new ActivatingFilter<TypePadSettingsPart>("Site"));
|
||||
Filters.Add(StorageFilter.For(repository));
|
||||
Filters.Add(new TemplateFilterForRecord<TypePadSettingsPartRecord>("TypePadSettings", "Parts/AntiSpam.TypePadSettings", "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")));
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/Orchard.Web/Modules/Orchard.AntiSpam/Migrations.cs
Normal file
62
src/Orchard.Web/Modules/Orchard.AntiSpam/Migrations.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Core.Contents.Extensions;
|
||||
using Orchard.Data.Migration;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.AntiSpam {
|
||||
public class Migrations : DataMigrationImpl {
|
||||
|
||||
public int Create() {
|
||||
|
||||
ContentDefinitionManager.AlterPartDefinition("SubmissionLimitPart", cfg => cfg
|
||||
.Attachable()
|
||||
);
|
||||
|
||||
ContentDefinitionManager.AlterPartDefinition("ReCaptchaPart", cfg => cfg
|
||||
.Attachable()
|
||||
);
|
||||
|
||||
ContentDefinitionManager.AlterPartDefinition("SpamFilterPart", cfg => cfg
|
||||
.Attachable()
|
||||
);
|
||||
|
||||
SchemaBuilder.CreateTable("SpamFilterPartRecord",
|
||||
table => table.ContentPartVersionRecord()
|
||||
.Column<string>("Status", c => c.WithLength(64))
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
[OrchardFeature("Akismet.Filter")]
|
||||
public class AkismetMigrations : DataMigrationImpl {
|
||||
|
||||
public int Create() {
|
||||
|
||||
SchemaBuilder.CreateTable("AkismetSettingsPartRecord",
|
||||
table => table.ContentPartVersionRecord()
|
||||
.Column<bool>("TrustAuthenticatedUsers")
|
||||
.Column<string>("ApiKey")
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[OrchardFeature("TypePad.Filter")]
|
||||
public class TypePadMigrations : DataMigrationImpl {
|
||||
|
||||
public int Create() {
|
||||
|
||||
SchemaBuilder.CreateTable("TypePadSettingsPartRecord",
|
||||
table => table.ContentPartVersionRecord()
|
||||
.Column<bool>("TrustAuthenticatedUsers")
|
||||
.Column<string>("ApiKey")
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class AkismetSettingsPart : ContentPart<AkismetSettingsPartRecord> {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Orchard.ContentManagement.Records;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class AkismetSettingsPartRecord : ContentPartRecord {
|
||||
|
||||
public virtual bool TrustAuthenticatedUsers { get; set; }
|
||||
|
||||
// API key for the Akismet (http://akismet.com/personal/)
|
||||
public virtual string ApiKey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class ReCaptchaPart : ContentPart {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement.Records;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class SpamFilterPartRecord : ContentPartRecord {
|
||||
public virtual SpamStatus Status { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Records;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class SpamFilterPart : ContentPart<SpamFilterPartRecord> {
|
||||
|
||||
public SpamStatus Status {
|
||||
get { return Record.Status; }
|
||||
set { Record.Status = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public enum SpamStatus {
|
||||
Spam,
|
||||
Ham
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class SubmissionLimitPart : ContentPart {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class TypePadSettingsPart : ContentPart<TypePadSettingsPartRecord> {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Orchard.ContentManagement.Records;
|
||||
|
||||
namespace Orchard.AntiSpam.Models {
|
||||
public class TypePadSettingsPartRecord : ContentPartRecord {
|
||||
|
||||
public virtual bool TrustAuthenticatedUsers { get; set; }
|
||||
|
||||
// API key for TypePad AntiSpam (http://antispam.typepad.com/info/get-api-key.html).
|
||||
public virtual string ApiKey { get; set; }
|
||||
}
|
||||
}
|
||||
23
src/Orchard.Web/Modules/Orchard.AntiSpam/Module.txt
Normal file
23
src/Orchard.Web/Modules/Orchard.AntiSpam/Module.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Name: AntiSpam
|
||||
AntiForgery: enabled
|
||||
Author: The Orchard Team
|
||||
Website: http://orchardproject.net
|
||||
Version: 1.5
|
||||
OrchardVersion: 1.0
|
||||
Description: Provides anti-spam services to protect your content from malicious submissions.
|
||||
Features:
|
||||
Orchard.AntiSpam:
|
||||
Name: Anti-Spam
|
||||
Description: Provides anti-spam services to protect your content from malicious submissions.
|
||||
Category: Security
|
||||
Dependencies: Orchard.Tokens
|
||||
Akismet.Filter:
|
||||
Name: Akismet Anti-Spam Filter
|
||||
Description: Provides an anti-spam filter based on Akismet.
|
||||
Category: Security
|
||||
Dependencies: Orchard.AntiSpam
|
||||
TypePad.Filter:
|
||||
Name: TypePad Anti-Spam Filter
|
||||
Description: Provides an anti-spam filter based on TypePad.
|
||||
Category: Security
|
||||
Dependencies: Orchard.AntiSpam
|
||||
204
src/Orchard.Web/Modules/Orchard.AntiSpam/Orchard.AntiSpam.csproj
Normal file
204
src/Orchard.Web/Modules/Orchard.AntiSpam/Orchard.AntiSpam.csproj
Normal file
@@ -0,0 +1,204 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{91BC2E7F-DA04-421C-98EF-76D37CEC130C}</ProjectGuid>
|
||||
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Orchard.AntiSpam</RootNamespace>
|
||||
<AssemblyName>Orchard.AntiSpam</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<MvcBuildViews>false</MvcBuildViews>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<OldToolsVersion>4.0</OldToolsVersion>
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<TargetFrameworkProfile />
|
||||
<UseIISExpress>false</UseIISExpress>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.DynamicData" />
|
||||
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Abstractions" />
|
||||
<Reference Include="System.Web.Routing" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Content\Admin\Images\spam.gif" />
|
||||
<Content Include="Web.config" />
|
||||
<Content Include="Views\Web.config" />
|
||||
<Content Include="Scripts\Web.config" />
|
||||
<Content Include="Styles\Web.config" />
|
||||
<Content Include="Properties\AssemblyInfo.cs" />
|
||||
<Content Include="Module.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
|
||||
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
|
||||
<Name>Orchard.Framework</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
|
||||
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
|
||||
<Name>Orchard.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Tokens\Orchard.Tokens.csproj">
|
||||
<Project>{6F759635-13D7-4E94-BCC9-80445D63F117}</Project>
|
||||
<Name>Orchard.Tokens</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="Drivers\SpamFilterPartDriver.cs" />
|
||||
<Compile Include="Drivers\ReCaptchaPartDriver.cs" />
|
||||
<Compile Include="Drivers\SubmissionLimitPartDriver.cs" />
|
||||
<Compile Include="Handlers\AkismetSettingsPartHandler.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\TypePadSettingsPart.cs" />
|
||||
<Compile Include="Models\TypePadSettingsPartRecord.cs" />
|
||||
<Compile Include="Models\SpamFilterPart.cs" />
|
||||
<Compile Include="Models\ReCaptchaPart.cs" />
|
||||
<Compile Include="Models\SpamFilterPartRecord.cs" />
|
||||
<Compile Include="Models\SpamStatus.cs" />
|
||||
<Compile Include="Models\SubmissionLimitPart.cs" />
|
||||
<Compile Include="Permissions.cs" />
|
||||
<Compile Include="Rules\AntiSpamEvents.cs" />
|
||||
<Compile Include="Rules\IRulesManager.cs" />
|
||||
<Compile Include="Services\AkismetApiSpamFilter.cs" />
|
||||
<Compile Include="Services\AkismetSpamFilterProvider.cs" />
|
||||
<Compile Include="Services\ISpamEventHandler.cs" />
|
||||
<Compile Include="Services\TypePadSpamFilterProvider.cs" />
|
||||
<Compile Include="Services\DefaultSpamFilterProvider.cs" />
|
||||
<Compile Include="Services\NullSpamFilterProvider.cs" />
|
||||
<Compile Include="Services\ISpamFilterProvider.cs" />
|
||||
<Compile Include="Services\DefaultSpamService.cs" />
|
||||
<Compile Include="Services\ISpamFilter.cs" />
|
||||
<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" />
|
||||
<Compile Include="ViewModels\ReCaptchaPartEditViewModel.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Admin\Index.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\DefinitionTemplates\ReCaptchaPartSettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\DefinitionTemplates\SubmissionLimitPartSettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\EditorTemplates\Parts.ReCaptcha.Fields.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\DefinitionTemplates\SpamFilterPartSettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Parts\SpamFilter.Metadata.SummaryAdmin.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Placement.info">
|
||||
<SubType>Designer</SubType>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Content\Web.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\EditorTemplates\Parts\AntiSpam.AkismetSettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\EditorTemplates\Parts\AntiSpam.TypePadSettings.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target> -->
|
||||
<Target Name="AfterBuild" DependsOnTargets="AfterBuildCompiler">
|
||||
<PropertyGroup>
|
||||
<AreasManifestDir>$(ProjectDir)\..\Manifests</AreasManifestDir>
|
||||
</PropertyGroup>
|
||||
<!-- If this is an area child project, uncomment the following line:
|
||||
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Child" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
|
||||
-->
|
||||
<!-- If this is an area parent project, uncomment the following lines:
|
||||
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Parent" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
|
||||
<CopyAreaManifests ManifestPath="$(AreasManifestDir)" CrossCopy="false" RenameViews="true" />
|
||||
-->
|
||||
</Target>
|
||||
<Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
|
||||
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
|
||||
</Target>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>45979</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>
|
||||
</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>True</UseCustomServer>
|
||||
<CustomServerUrl>http://orchard.codeplex.com</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
43
src/Orchard.Web/Modules/Orchard.AntiSpam/Permissions.cs
Normal file
43
src/Orchard.Web/Modules/Orchard.AntiSpam/Permissions.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.Security.Permissions;
|
||||
|
||||
namespace Orchard.AntiSpam {
|
||||
public class Permissions : IPermissionProvider {
|
||||
public static readonly Permission ManageAntiSpam = new Permission { Description = "Manage anti-spam", Name = "ManageAntiSpam" };
|
||||
|
||||
public virtual Feature Feature { get; set; }
|
||||
|
||||
public IEnumerable<Permission> GetPermissions() {
|
||||
return new[] {
|
||||
ManageAntiSpam,
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {
|
||||
return new[] {
|
||||
new PermissionStereotype {
|
||||
Name = "Administrator",
|
||||
Permissions = new[] {ManageAntiSpam}
|
||||
},
|
||||
new PermissionStereotype {
|
||||
Name = "Editor",
|
||||
Permissions = new[] {ManageAntiSpam}
|
||||
},
|
||||
new PermissionStereotype {
|
||||
Name = "Moderator",
|
||||
Permissions = new[] {ManageAntiSpam}
|
||||
},
|
||||
new PermissionStereotype {
|
||||
Name = "Author",
|
||||
Permissions = new Permission[0]
|
||||
},
|
||||
new PermissionStereotype {
|
||||
Name = "Contributor",
|
||||
Permissions = new Permission[0]
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
9
src/Orchard.Web/Modules/Orchard.AntiSpam/Placement.info
Normal file
9
src/Orchard.Web/Modules/Orchard.AntiSpam/Placement.info
Normal file
@@ -0,0 +1,9 @@
|
||||
<Placement>
|
||||
<Place Parts_ReCaptcha_Fields="Content:10"/>
|
||||
<Match DisplayType="SummaryAdmin">
|
||||
<Place Parts_SpamFilter_Metadata_SummaryAdmin="Meta:2"/>
|
||||
</Match>
|
||||
|
||||
<Place Parts_AntiSpam_AkismetSettings="Content:9"/>
|
||||
<Place Parts_AntiSpam_TypePadSettings="Content:10"/>
|
||||
</Placement>
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Orchard.AntiSpam")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyProduct("Orchard")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ba9fd801-bcde-400a-bcdc-6f40538678b8")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("1.5")]
|
||||
[assembly: AssemblyFileVersion("1.5")]
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Orchard.Events;
|
||||
using Orchard.Localization;
|
||||
|
||||
namespace Orchard.AntiSpam.Rules {
|
||||
public interface IEventProvider : IEventHandler {
|
||||
void Describe(dynamic describe);
|
||||
}
|
||||
|
||||
public class AntiSpamEvents : IEventProvider {
|
||||
public AntiSpamEvents() {
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public void Describe(dynamic describe) {
|
||||
Func<dynamic, bool> alwaysTrue = c => true;
|
||||
|
||||
describe.For("AntiSpam", T("Anti-Spam"), T("Anti-Spam"))
|
||||
.Element("Spam", T("Spam Reported"), T("Content is categorized as spam."), alwaysTrue, (Func<dynamic, LocalizedString>)(context => T("When content is categorized as spam.")), null)
|
||||
.Element("Ham", T("Ham Reported"), T("Content is categorized as ham."), alwaysTrue, (Func<dynamic, LocalizedString>)(context => T("When content is categorized as ham.")), null)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Events;
|
||||
|
||||
namespace Orchard.AntiSpam.Rules {
|
||||
public interface IRulesManager : IEventHandler {
|
||||
void TriggerEvent(string category, string type, Func<Dictionary<string, object>> tokensContext);
|
||||
}
|
||||
|
||||
}
|
||||
25
src/Orchard.Web/Modules/Orchard.AntiSpam/Scripts/Web.config
Normal file
25
src/Orchard.Web/Modules/Orchard.AntiSpam/Scripts/Web.config
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="webpages:Enabled" value="false" />
|
||||
</appSettings>
|
||||
<system.web>
|
||||
<httpHandlers>
|
||||
<!-- iis6 - for any request in this location, return via managed static file handler -->
|
||||
<add path="*" verb="*" type="System.Web.StaticFileHandler" />
|
||||
</httpHandlers>
|
||||
</system.web>
|
||||
<system.webServer>
|
||||
<staticContent>
|
||||
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
|
||||
</staticContent>
|
||||
|
||||
<handlers accessPolicy="Script,Read">
|
||||
<!--
|
||||
iis7 - for any request to a file exists on disk, return it via native http module.
|
||||
accessPolicy 'Script' is to allow for a managed 404 page.
|
||||
-->
|
||||
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Utility.Extensions;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
public class AkismetApiSpamFilter : ISpamFilter {
|
||||
private readonly string _endpoint;
|
||||
private readonly string _apiKey;
|
||||
private readonly HttpContextBase _context;
|
||||
|
||||
private const string AkismetApiPattern = "http://{0}.{1}/1.1/{2}";
|
||||
|
||||
public AkismetApiSpamFilter(string endpoint, string apiKey, HttpContextBase context) {
|
||||
_endpoint = endpoint;
|
||||
_apiKey = apiKey;
|
||||
_context = context;
|
||||
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public SpamStatus CheckForSpam(string text) {
|
||||
try {
|
||||
var result = ExecuteValidateRequest(text, "comment-check");
|
||||
|
||||
if (HandleValidateResponse(_context, result)) {
|
||||
return SpamStatus.Spam;
|
||||
}
|
||||
|
||||
return SpamStatus.Ham;
|
||||
}
|
||||
catch(Exception e) {
|
||||
Logger.Error(e, "An error occured while checking for spam");
|
||||
return SpamStatus.Spam;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReportSpam(string text) {
|
||||
try {
|
||||
var result = ExecuteValidateRequest(text, "submit-spam");
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.Error(e, "An error occured while reporting spam");
|
||||
}
|
||||
}
|
||||
|
||||
public void ReportHam(string text) {
|
||||
try {
|
||||
var result = ExecuteValidateRequest(text, "submit-ham");
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.Error(e, "An error occured while reporting ham");
|
||||
}
|
||||
}
|
||||
|
||||
private string ExecuteValidateRequest(string text, string action) {
|
||||
var uri = String.Format(AkismetApiPattern, _apiKey, _endpoint, action);
|
||||
|
||||
WebRequest request = WebRequest.Create(uri);
|
||||
request.Method = "POST";
|
||||
request.Timeout = 5000; //milliseconds
|
||||
request.ContentType = "application/x-www-form-urlencoded";
|
||||
|
||||
var postData = String.Format(CultureInfo.InvariantCulture, "blog={0}&user_ip={1}&user_agent={2}&referrer={3}&comment_content={4}",
|
||||
HttpUtility.UrlEncode(_context.Request.ToApplicationRootUrlString()),
|
||||
HttpUtility.UrlEncode(_context.Request.ServerVariables["REMOTE_ADDR"]),
|
||||
HttpUtility.UrlEncode(_context.Request.UserAgent),
|
||||
HttpUtility.UrlEncode(Convert.ToString(_context.Request.UrlReferrer)),
|
||||
HttpUtility.UrlEncode(text)
|
||||
);
|
||||
|
||||
byte[] content = Encoding.UTF8.GetBytes(postData);
|
||||
using (Stream stream = request.GetRequestStream()) {
|
||||
stream.Write(content, 0, content.Length);
|
||||
}
|
||||
using (WebResponse webResponse = request.GetResponse()) {
|
||||
using (var reader = new StreamReader(webResponse.GetResponseStream())) {
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool HandleValidateResponse(HttpContextBase context, string response) {
|
||||
if (!String.IsNullOrEmpty(response)) {
|
||||
string[] results = response.Split('\n');
|
||||
if (results.Length > 0) {
|
||||
bool rval = Convert.ToBoolean(results[0], CultureInfo.InvariantCulture);
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
/// <summary>
|
||||
/// Implements <see cref="ISpamFilterProvider"/> by returning an Akismet filter
|
||||
/// </summary>
|
||||
[OrchardFeature("Akismet.Filter")]
|
||||
public class AkismetSpamFilterProvider : ISpamFilterProvider {
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
|
||||
private const string AkismetServiceUrl = "rest.akismet.com";
|
||||
|
||||
public AkismetSpamFilterProvider(IOrchardServices orchardServices) {
|
||||
_orchardServices = orchardServices;
|
||||
}
|
||||
|
||||
public IEnumerable<ISpamFilter> GetSpamFilters() {
|
||||
|
||||
var settings = _orchardServices.WorkContext.CurrentSite.As<AkismetSettingsPart>().Record;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.ApiKey)) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
// don't return any filter if authenticated users are trusted, and current user authenticated
|
||||
if(_orchardServices.WorkContext.CurrentUser != null && settings.TrustAuthenticatedUsers) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
var filter = new AkismetApiSpamFilter(AkismetServiceUrl, settings.ApiKey, _orchardServices.WorkContext.HttpContext);
|
||||
|
||||
yield return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
/// <summary>
|
||||
/// Implements <see cref="ISpamFilterProvider"/> by returning Akismet and TypePad services if they are configured
|
||||
/// </summary>
|
||||
public class DefaultSpamFilterProvider : ISpamFilterProvider {
|
||||
public IEnumerable<ISpamFilter> GetSpamFilters() {
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.AntiSpam.Rules;
|
||||
using Orchard.AntiSpam.Settings;
|
||||
using Orchard.Tokens;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
public class DefaultSpamService : ISpamService {
|
||||
private readonly ITokenizer _tokenizer;
|
||||
private readonly IEnumerable<ISpamFilterProvider> _providers;
|
||||
private readonly ISpamEventHandler _spamEventHandler;
|
||||
private readonly IRulesManager _rulesManager;
|
||||
|
||||
public DefaultSpamService(
|
||||
ITokenizer tokenizer,
|
||||
IEnumerable<ISpamFilterProvider> providers,
|
||||
ISpamEventHandler spamEventHandler,
|
||||
IRulesManager rulesManager
|
||||
) {
|
||||
_tokenizer = tokenizer;
|
||||
_providers = providers;
|
||||
_spamEventHandler = spamEventHandler;
|
||||
_rulesManager = rulesManager;
|
||||
}
|
||||
|
||||
public SpamStatus CheckForSpam(string text, SpamFilterAction action) {
|
||||
var spamFilters = GetSpamFilters().ToList();
|
||||
|
||||
switch (action) {
|
||||
case SpamFilterAction.AllOrNothing:
|
||||
if (spamFilters.All(x => x.CheckForSpam(text) == SpamStatus.Spam)) {
|
||||
return SpamStatus.Spam;
|
||||
}
|
||||
|
||||
return SpamStatus.Ham;
|
||||
case SpamFilterAction.One:
|
||||
if (spamFilters.Any(x => x.CheckForSpam(text) == SpamStatus.Spam)) {
|
||||
return SpamStatus.Spam;
|
||||
}
|
||||
|
||||
return SpamStatus.Ham;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public SpamStatus CheckForSpam(SpamFilterPart part) {
|
||||
|
||||
var settings = part.TypePartDefinition.Settings.GetModel<SpamFilterPartSettings>();
|
||||
|
||||
// evaluate the text to submit to the spam filters
|
||||
var text = _tokenizer.Replace(settings.Pattern, new Dictionary<string, object> { { "Content", part.ContentItem } });
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void ReportSpam(string text) {
|
||||
var spamFilters = GetSpamFilters().ToList();
|
||||
|
||||
foreach(var filter in spamFilters) {
|
||||
filter.ReportSpam(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReportSpam(SpamFilterPart part) {
|
||||
var settings = part.TypePartDefinition.Settings.GetModel<SpamFilterPartSettings>();
|
||||
|
||||
// evaluate the text to submit to the spam filters
|
||||
var text = _tokenizer.Replace(settings.Pattern, new Dictionary<string, object> { { "Content", part.ContentItem } });
|
||||
|
||||
ReportSpam(text);
|
||||
}
|
||||
|
||||
public void ReportHam(string text) {
|
||||
var spamFilters = GetSpamFilters().ToList();
|
||||
|
||||
foreach (var filter in spamFilters) {
|
||||
filter.ReportHam(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReportHam(SpamFilterPart part) {
|
||||
var settings = part.TypePartDefinition.Settings.GetModel<SpamFilterPartSettings>();
|
||||
|
||||
// evaluate the text to submit to the spam filters
|
||||
var text = _tokenizer.Replace(settings.Pattern, new Dictionary<string, object> { { "Content", part.ContentItem } });
|
||||
|
||||
ReportHam(text);
|
||||
}
|
||||
|
||||
public IEnumerable<ISpamFilter> GetSpamFilters() {
|
||||
return _providers.SelectMany(x => x.GetSpamFilters()).Where(x => x != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Events;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
public interface ISpamEventHandler : IEventHandler {
|
||||
void SpamReported(IContent content);
|
||||
void HamReported(IContent content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Orchard.AntiSpam.Models;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
/// <summary>
|
||||
/// Implementations of <see cref="ISpamFilter"/> are used to filter some user submitted
|
||||
/// content using anti-spam services
|
||||
/// </summary>
|
||||
public interface ISpamFilter : IDependency {
|
||||
/// <summary>
|
||||
/// Checks if some content is spam.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to check.</param>
|
||||
/// <returns><value>SpamStatus.Spam</value> if the text has been categorized as spam, <value>SpamStatus.Ham</value> otherwise.</returns>
|
||||
SpamStatus CheckForSpam(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitely report some content as spam in order to improve the service.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to report as spam.</param>
|
||||
void ReportSpam(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitely report some content as ham in order to improve the service.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to report as ham (false positive).</param>
|
||||
void ReportHam(string text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
public interface ISpamFilterProvider : IDependency {
|
||||
IEnumerable<ISpamFilter> GetSpamFilters();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.AntiSpam.Settings;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
public interface ISpamService : IDependency {
|
||||
SpamStatus CheckForSpam(string text, SpamFilterAction action);
|
||||
SpamStatus CheckForSpam(SpamFilterPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitely report some content as spam in order to improve the service.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to report as spam.</param>
|
||||
void ReportSpam(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitely report some content as ham in order to improve the service.
|
||||
/// </summary>
|
||||
/// <param name="part">The content part to report as ham (false positive).</param>
|
||||
void ReportSpam(SpamFilterPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitely report some content as ham in order to improve the service.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to report as ham (false positive).</param>
|
||||
void ReportHam(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Explicitely report some content as ham in order to improve the service.
|
||||
/// </summary>
|
||||
/// <param name="part">The content part to report as ham (false positive).</param>
|
||||
void ReportHam(SpamFilterPart part);
|
||||
|
||||
IEnumerable<ISpamFilter> GetSpamFilters();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
/// <summary>
|
||||
/// Default implementation for <see cref="ISpamFilterProvider"/> in order to have at least one result
|
||||
/// when it is resolved, event if there is no other concrete provider.
|
||||
/// </summary>
|
||||
public class NullSpamFilterProvider : ISpamFilterProvider {
|
||||
public IEnumerable<ISpamFilter> GetSpamFilters() {
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.AntiSpam.Models;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.AntiSpam.Services {
|
||||
/// <summary>
|
||||
/// Implements <see cref="ISpamFilterProvider"/> by returning an TypePad filter
|
||||
/// </summary>
|
||||
[OrchardFeature("TypePad.Filter")]
|
||||
public class TypePadSpamFilterProvider : ISpamFilterProvider {
|
||||
private readonly IOrchardServices _orchardServices;
|
||||
|
||||
private const string TypePadServiceUrl = "api.antispam.typepad.com";
|
||||
|
||||
public TypePadSpamFilterProvider(IOrchardServices orchardServices) {
|
||||
_orchardServices = orchardServices;
|
||||
}
|
||||
|
||||
public IEnumerable<ISpamFilter> GetSpamFilters() {
|
||||
|
||||
var settings = _orchardServices.WorkContext.CurrentSite.As<TypePadSettingsPart>().Record;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(settings.ApiKey)) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
// don't return any filter if authenticated users are trusted, and current user authenticated
|
||||
if(_orchardServices.WorkContext.CurrentUser != null && settings.TrustAuthenticatedUsers) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
var filter = new AkismetApiSpamFilter(TypePadServiceUrl, settings.ApiKey, _orchardServices.WorkContext.HttpContext);
|
||||
|
||||
yield return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Orchard.AntiSpam.Settings {
|
||||
public class ReCaptchaPartSettings {
|
||||
public string PublicKey { get; set; }
|
||||
public string PrivateKey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
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);
|
||||
}
|
||||
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Orchard.AntiSpam.Settings {
|
||||
public class SpamFilterPartSettings {
|
||||
public SpamFilterAction Action { get; set; }
|
||||
public string Pattern { 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
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 SpamFilterPartSettingsEvents : ContentDefinitionEditorEventsBase {
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditor(ContentTypePartDefinition definition) {
|
||||
if (definition.PartDefinition.Name != "SpamFilterPart")
|
||||
yield break;
|
||||
|
||||
var settings = definition.Settings.GetModel<SpamFilterPartSettings>();
|
||||
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditorUpdate(ContentTypePartDefinitionBuilder builder, IUpdateModel updateModel) {
|
||||
if (builder.Name != "SpamFilterPart")
|
||||
yield break;
|
||||
|
||||
var settings = new SpamFilterPartSettings {
|
||||
};
|
||||
|
||||
if (updateModel.TryUpdateModel(settings, "SpamFilterPartSettings", null, null)) {
|
||||
builder.WithSetting("SpamFilterPartSettings.Action", settings.Action.ToString());
|
||||
builder.WithSetting("SpamFilterPartSettings.Pattern", settings.Pattern);
|
||||
}
|
||||
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Orchard.AntiSpam.Settings {
|
||||
public class SubmissionLimitPartSettings {
|
||||
public int Limit { get; set; }
|
||||
public int Unit { get; set; }
|
||||
}
|
||||
|
||||
public enum SubmissionLimitUnit {
|
||||
Hour,
|
||||
Day,
|
||||
Month,
|
||||
Year,
|
||||
Overall
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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 SubmissionLimitPartSettingsEvents : ContentDefinitionEditorEventsBase {
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditor(ContentTypePartDefinition definition) {
|
||||
if (definition.PartDefinition.Name != "SubmissionLimitPart")
|
||||
yield break;
|
||||
|
||||
var settings = definition.Settings.GetModel<SubmissionLimitPartSettings>();
|
||||
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
|
||||
public override IEnumerable<TemplateViewModel> TypePartEditorUpdate(ContentTypePartDefinitionBuilder builder, IUpdateModel updateModel) {
|
||||
if (builder.Name != "SubmissionLimitPart")
|
||||
yield break;
|
||||
|
||||
var settings = new SubmissionLimitPartSettings {
|
||||
};
|
||||
|
||||
if (updateModel.TryUpdateModel(settings, "SubmissionLimitPartSettings", null, null)) {
|
||||
|
||||
builder.WithSetting("SubmissionLimitPartSettings.Limit", settings.Limit.ToString(CultureInfo.InvariantCulture));
|
||||
builder.WithSetting("SubmissionLimitPartSettings.Unit", settings.Unit.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
yield return DefinitionTemplate(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/Orchard.Web/Modules/Orchard.AntiSpam/Styles/Web.config
Normal file
25
src/Orchard.Web/Modules/Orchard.AntiSpam/Styles/Web.config
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="webpages:Enabled" value="false" />
|
||||
</appSettings>
|
||||
<system.web>
|
||||
<httpHandlers>
|
||||
<!-- iis6 - for any request in this location, return via managed static file handler -->
|
||||
<add path="*" verb="*" type="System.Web.StaticFileHandler" />
|
||||
</httpHandlers>
|
||||
</system.web>
|
||||
<system.webServer>
|
||||
<staticContent>
|
||||
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
|
||||
</staticContent>
|
||||
|
||||
<handlers accessPolicy="Script,Read">
|
||||
<!--
|
||||
iis7 - for any request to a file exists on disk, return it via native http module.
|
||||
accessPolicy 'Script' is to allow for a managed 404 page.
|
||||
-->
|
||||
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Orchard.AntiSpam.ViewModels {
|
||||
public class ReCaptchaPartEditViewModel {
|
||||
public string PublicKey { get; set; }
|
||||
}
|
||||
|
||||
public class ReCaptchaPartSubmitViewModel {
|
||||
public string recaptcha_challenge_field { get; set; }
|
||||
public string recaptcha_response_field { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.AntiSpam.Models;
|
||||
|
||||
namespace Orchard.AntiSpam.ViewModels {
|
||||
|
||||
public class SpamIndexViewModel {
|
||||
public IList<SpamEntry> Spams { get; set; }
|
||||
public SpamIndexOptions Options { get; set; }
|
||||
public dynamic Pager { get; set; }
|
||||
}
|
||||
|
||||
public class SpamEntry {
|
||||
public SpamFilterPart Spam { get; set; }
|
||||
public dynamic Shape { get; set; }
|
||||
public bool IsChecked { get; set; }
|
||||
}
|
||||
|
||||
public class SpamIndexOptions {
|
||||
public string Search { get; set; }
|
||||
public SpamOrder Order { get; set; }
|
||||
public SpamFilter Filter { get; set; }
|
||||
public SpamBulkAction BulkAction { get; set; }
|
||||
}
|
||||
|
||||
public enum SpamOrder {
|
||||
Creation
|
||||
}
|
||||
|
||||
public enum SpamFilter {
|
||||
Spam,
|
||||
Ham,
|
||||
All,
|
||||
}
|
||||
|
||||
public enum SpamBulkAction {
|
||||
None,
|
||||
Spam,
|
||||
Ham,
|
||||
Delete
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
@model SpamIndexViewModel
|
||||
|
||||
@using Orchard.AntiSpam.ViewModels
|
||||
@using Orchard.Utility.Extensions
|
||||
|
||||
@{
|
||||
var spamIndex = 0;
|
||||
|
||||
var pageSizes = new List<int?>() { 10, 50, 100 };
|
||||
var defaultPageSize = WorkContext.CurrentSite.PageSize;
|
||||
if (!pageSizes.Contains(defaultPageSize)) {
|
||||
pageSizes.Add(defaultPageSize);
|
||||
}
|
||||
}
|
||||
|
||||
<h1>@Html.TitleForPage(T("Manage Spam").ToString()) </h1>
|
||||
@using (Html.BeginFormAntiForgeryPost()) {
|
||||
@Html.ValidationSummary()
|
||||
<div class="manage"></div>
|
||||
|
||||
<fieldset class="bulk-actions">
|
||||
<label for="publishActions">@T("Actions:")</label>
|
||||
<select id="publishActions" name="@Html.NameOf(m => m.Options.BulkAction)">
|
||||
@Html.SelectOption(Model.Options.BulkAction, SpamBulkAction.None, T("Choose action...").ToString())
|
||||
@Html.SelectOption(Model.Options.BulkAction, SpamBulkAction.Spam, T("Mark as Spam").ToString())
|
||||
@Html.SelectOption(Model.Options.BulkAction, SpamBulkAction.Ham, T("Mark as Ham").ToString())
|
||||
@Html.SelectOption(Model.Options.BulkAction, SpamBulkAction.Delete, T("Delete").ToString())
|
||||
</select>
|
||||
<button type="submit" name="submit.BulkEdit" value="@T("Apply")">@T("Apply")</button>
|
||||
</fieldset>
|
||||
<fieldset class="bulk-actions">
|
||||
<label for="filterResults">@T("Filter:")</label>
|
||||
<select id="filterResults" name="@Html.NameOf(m => m.Options.Filter)">
|
||||
@Html.SelectOption(Model.Options.Filter, SpamFilter.All, T("any (show all)").ToString())
|
||||
@Html.SelectOption(Model.Options.Filter, SpamFilter.Spam, T("Spam").ToString())
|
||||
@Html.SelectOption(Model.Options.Filter, SpamFilter.Ham, T("Ham").ToString())
|
||||
</select>
|
||||
</fieldset>
|
||||
<fieldset class="bulk-actions">
|
||||
<label for="sortResults">@T("Sort by:")</label>
|
||||
<select id="sortResults" name="@Html.NameOf(m => m.Options.Order)">
|
||||
@Html.SelectOption(Model.Options.Order, SpamOrder.Creation, T("Creation date").ToString())
|
||||
</select>
|
||||
<input type="hidden" name="Page" value="1" />
|
||||
<label for="pageSize">@T("Show:")</label>
|
||||
<select id="pageSize" name="PageSize">
|
||||
@Html.SelectOption((int)Model.Pager.PageSize, 0, T("All").ToString())
|
||||
@foreach (int size in pageSizes.OrderBy(p => p)) {
|
||||
@Html.SelectOption((int)Model.Pager.PageSize, size, size.ToString())
|
||||
}
|
||||
</select>
|
||||
<button type="submit" name="submit.Filter" value="@T("Filter")">@T("Filter")</button>
|
||||
</fieldset>
|
||||
<fieldset class="contentItems bulk-items">
|
||||
@foreach (var entry in Model.Spams) {
|
||||
@Display(entry.Shape)
|
||||
spamIndex++;
|
||||
}
|
||||
@Display(Model.Pager)
|
||||
</fieldset>
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
@model ReCaptchaPartSettings
|
||||
@using Orchard.AntiSpam.Settings;
|
||||
|
||||
<fieldset>
|
||||
<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>
|
||||
@@ -0,0 +1,24 @@
|
||||
@model SpamFilterPartSettings
|
||||
@using Orchard.AntiSpam.Settings;
|
||||
|
||||
@Display.TokenHint()
|
||||
|
||||
<fieldset>
|
||||
<label>@T("When should items for this content type be categorized as Spam ?")</label>
|
||||
<div>
|
||||
<input type="radio" name="@Html.FieldNameFor(m => m.Action)" id="radio-1" value="@SpamFilterAction.One" @if (Model.Action == SpamFilterAction.One) { <text>checked="checked"</text> } />
|
||||
<label for="radio-1" class="forcheckbox">@T("At least one filter reports spam")</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" name="@Html.FieldNameFor(m => m.Action)" id="radio-2" value="@SpamFilterAction.AllOrNothing" @if (Model.Action == SpamFilterAction.AllOrNothing) { <text>checked="checked"</text> } />
|
||||
<label for="radio-2" class="forcheckbox">@T("All filters report spam")</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="@Html.FieldIdFor(m => m.Pattern)">@T("Analyzed text")</label>
|
||||
<div>
|
||||
@Html.TextBoxFor(m => m.Pattern, new { @class = "text large tokenized" })
|
||||
<span class="hint">@T("The tokenized pattern generating the text to submit to spam filters.")</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,21 @@
|
||||
@model SubmissionLimitPartSettings
|
||||
@using Orchard.AntiSpam.Settings;
|
||||
|
||||
<fieldset>
|
||||
<div>
|
||||
@Html.TextBoxFor(m => m.Limit, new { @class = "text-small"})
|
||||
<span class="hint">@T("The number of allowed submissions.")</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div>
|
||||
<select name="@Html.FieldNameFor(m => m.Unit)" size="1">
|
||||
@Html.SelectOption(Model.Unit, (int)SubmissionLimitUnit.Day, T("Day").Text)
|
||||
@Html.SelectOption(Model.Unit, (int)SubmissionLimitUnit.Hour, T("Hour").Text)
|
||||
@Html.SelectOption(Model.Unit, (int)SubmissionLimitUnit.Month, T("Month").Text)
|
||||
@Html.SelectOption(Model.Unit, (int)SubmissionLimitUnit.Year, T("Year").Text)
|
||||
@Html.SelectOption(Model.Unit, (int)SubmissionLimitUnit.Overall, T("Overall").Text)
|
||||
</select>
|
||||
<span class="hint">@T("The period to take into account.")</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,8 @@
|
||||
@model Orchard.AntiSpam.ViewModels.ReCaptchaPartEditViewModel
|
||||
<script type="text/javascript" src="//www.google.com/recaptcha/api/challenge?k=@Model.PublicKey"></script>
|
||||
<noscript>
|
||||
<iframe src="//www.google.com/recaptcha/api/noscript?k=@Model.PublicKey" height="300" width="500" frameborder="0"></iframe><br>
|
||||
<textarea name="recaptcha_challenge_field" rows="3" cols="40">
|
||||
</textarea>
|
||||
<input type="hidden" name="recaptcha_response_field" value="manual_challenge">
|
||||
</noscript>
|
||||
@@ -0,0 +1,17 @@
|
||||
@model Orchard.AntiSpam.Models.AkismetSettingsPartRecord
|
||||
@using Orchard.Utility.Extensions;
|
||||
|
||||
<fieldset>
|
||||
<legend>@T("Akismet")</legend>
|
||||
<div>
|
||||
@Html.EditorFor(m => m.TrustAuthenticatedUsers)
|
||||
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.TrustAuthenticatedUsers)">@T("Trust content submitted by authenticated users")</label>
|
||||
<span class="hint">@T("Check if authenticated users should have their content automatically approved.")</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="@Html.FieldIdFor(m => m.ApiKey)">@T("Api key")</label>
|
||||
@Html.TextBoxFor(m => m.ApiKey, new { @class = "textMedium" })
|
||||
@Html.ValidationMessage("ApiKey", "*")
|
||||
<span class="hint">@T("API key for <a href=\"http://akismet.com/personal/\">Akismet</a> service.")</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,17 @@
|
||||
@model Orchard.AntiSpam.Models.TypePadSettingsPartRecord
|
||||
@using Orchard.Utility.Extensions;
|
||||
|
||||
<fieldset>
|
||||
<legend>@T("TypePad")</legend>
|
||||
<div>
|
||||
@Html.EditorFor(m => m.TrustAuthenticatedUsers)
|
||||
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.TrustAuthenticatedUsers)">@T("Trust content submitted by authenticated users")</label>
|
||||
<span class="hint">@T("Check if authenticated users should have their content automatically approved.")</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="@Html.FieldIdFor(m => m.ApiKey)">@T("Api key")</label>
|
||||
@Html.TextBoxFor(m => m.ApiKey, new { @class = "textMedium" })
|
||||
@Html.ValidationMessage("ApiKey", "*")
|
||||
<span class="hint">@T("API key for <a href=\"http://antispam.typepad.com/info/get-api-key.html\">TypePad AntiSpam</a> service.")</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,9 @@
|
||||
@using Orchard.AntiSpam.Models
|
||||
@if (Model.ContentPart.Status == SpamStatus.Spam) {
|
||||
<ul class="pageStatus">
|
||||
<li>
|
||||
<img class="icon" src="@Href("~/Modules/Orchard.AntiSpam/Content/Admin/Images/spam.gif")" alt="@T("Spam")" title="@T("The content has been categorized as spam")" />
|
||||
</li>
|
||||
<li> @T("Spam") | </li>
|
||||
</ul>
|
||||
}
|
||||
41
src/Orchard.Web/Modules/Orchard.AntiSpam/Views/Web.config
Normal file
41
src/Orchard.Web/Modules/Orchard.AntiSpam/Views/Web.config
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="webpages:Enabled" value="false" />
|
||||
</appSettings>
|
||||
<system.web>
|
||||
<httpHandlers>
|
||||
</httpHandlers>
|
||||
|
||||
<!--
|
||||
Enabling request validation in view pages would cause validation to occur
|
||||
after the input has already been processed by the controller. By default
|
||||
MVC performs request validation before a controller processes the input.
|
||||
To change this behavior apply the ValidateInputAttribute to a
|
||||
controller or action.
|
||||
-->
|
||||
<pages
|
||||
validateRequest="false"
|
||||
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
|
||||
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
|
||||
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<controls>
|
||||
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" namespace="System.Web.Mvc" tagPrefix="mvc" />
|
||||
</controls>
|
||||
</pages>
|
||||
</system.web>
|
||||
|
||||
<system.webServer>
|
||||
<validation validateIntegratedModeConfiguration="false"/>
|
||||
<handlers>
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="2.0.0.0" newVersion="3.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
41
src/Orchard.Web/Modules/Orchard.AntiSpam/Web.config
Normal file
41
src/Orchard.Web/Modules/Orchard.AntiSpam/Web.config
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
|
||||
<configSections>
|
||||
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
|
||||
<remove name="host" />
|
||||
<remove name="pages" />
|
||||
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
|
||||
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
|
||||
<system.web.webPages.razor>
|
||||
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
|
||||
<pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
|
||||
<namespaces>
|
||||
<add namespace="System.Web.Mvc" />
|
||||
<add namespace="System.Web.Mvc.Ajax" />
|
||||
<add namespace="System.Web.Mvc.Html" />
|
||||
<add namespace="System.Web.Routing" />
|
||||
<add namespace="System.Web.WebPages" />
|
||||
<add namespace="System.Linq"/>
|
||||
<add namespace="System.Collections.Generic"/>
|
||||
<add namespace="Orchard.Mvc.Html"/>
|
||||
</namespaces>
|
||||
</pages>
|
||||
</system.web.webPages.razor>
|
||||
|
||||
<system.web>
|
||||
<compilation targetFramework="4.0">
|
||||
<assemblies>
|
||||
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
|
||||
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
|
||||
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
|
||||
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
|
||||
<add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
|
||||
</assemblies>
|
||||
</compilation>
|
||||
</system.web>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user