Browse Source

Merge pull request #15039 from abpframework/Cmskit-deleting-blogpost-v12514

Cmskit deleting blogpost and adding captcha to the comment component
pull/15090/head
malik masis 3 years ago
committed by GitHub
parent
commit
ccc8281fcd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs
  2. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Comments/CommentWithAuthorDto.cs
  3. 6
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CmsKitCommentOptions.cs
  4. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs
  5. 0
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/EntityTypeDefinition.cs
  6. 5
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  7. 5
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json
  8. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/IBlogPostPublicAppService.cs
  9. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs
  10. 26
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParameteresInput.cs
  11. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj
  12. 17
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs
  13. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs
  14. 8
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/BlogPostPublicClientProxy.Generated.cs
  15. 37
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json
  16. 11
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs
  17. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs
  18. 42
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicCommentsController.cs
  19. 51
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs
  20. 55
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml
  21. 26
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.css
  22. 83
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js
  23. 30
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.scss
  24. 61
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml
  25. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs
  26. 16
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/blogPost.js
  27. 8
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.js
  28. 15
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs
  29. 66
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOptions.cs
  30. 32
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOutput.cs
  31. 7
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/EncoderTypes.cs
  32. 73
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/RandomTextGenerator.cs
  33. 175
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs
  34. 5
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj
  35. 8
      modules/cms-kit/src/Volo.CmsKit.Public.Web/wwwroot/client-proxies/cms-kit-proxy.js

1
modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs

@ -167,6 +167,7 @@ public class CmsKitWebUnifiedModule : AbpModule
Configure<CmsKitCommentOptions>(options =>
{
options.EntityTypes.Add(new CommentEntityTypeDefinition("quote"));
options.IsRecaptchaEnabled = true;
});
Configure<CmsKitMediaOptions>(options =>

2
modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/Comments/CommentWithAuthorDto.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using Volo.CmsKit.Users;
namespace Volo.CmsKit.Admin.Comments;

6
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CmsKitCommentOptions.cs → modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CmsKitCommentOptions.cs

@ -7,4 +7,10 @@ public class CmsKitCommentOptions
{
[NotNull]
public List<CommentEntityTypeDefinition> EntityTypes { get; } = new List<CommentEntityTypeDefinition>();
/// <summary>
/// Flag to enable/disable ReCaptcha for comment component.
/// Default: false
/// </summary>
public bool IsRecaptchaEnabled { get; set; }
}

1
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs → modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentEntityTypeDefinition.cs

@ -1,5 +1,4 @@
using JetBrains.Annotations;
using System;
using Volo.Abp;
namespace Volo.CmsKit.Comments;

0
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/EntityTypeDefinition.cs → modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/EntityTypeDefinition.cs

5
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -209,6 +209,9 @@
"Feature:ReactionEnable": "Reaction",
"Feature:ReactionEnableDescription": "CMS Kit's reaction system that allows users to send reactions to entities such as BlogPost, Comments, etc.",
"Feature:TagEnable": "Taging",
"Feature:TagEnableDescription": "CMS Kit's tag system that allows tagging entities such as BlogPost."
"Feature:TagEnableDescription": "CMS Kit's tag system that allows tagging entities such as BlogPost.",
"DeleteBlogPostMessage": "The blog will be deleted. Are you sure?",
"CaptchaCode": "Captcha code",
"CommentTextRequired": "Comment is required"
}
}

5
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json

@ -209,6 +209,9 @@
"Feature:ReactionEnable": "Reaksiyon",
"Feature:ReactionEnableDescription": "Cmskit reaksiyon sistemi",
"Feature:TagEnable": "Etkiket",
"Feature:TagEnableDescription": "Cmskit etiket sistemi"
"Feature:TagEnableDescription": "Cmskit etiket sistemi",
"DeleteBlogPostMessage": "Blog silinecek. Emin misiniz?",
"CaptchaCode": "Captcha kodu",
"CommentTextRequired": "Yorum zorunlu"
}
}

1
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/IBlogPostPublicAppService.cs

@ -17,4 +17,5 @@ public interface IBlogPostPublicAppService : IApplicationService
Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input);
Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id);
Task DeleteAsync(Guid id);
}

4
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs

@ -13,4 +13,8 @@ public class CreateCommentInput
public string Text { get; set; }
public Guid? RepliedCommentId { get; set; }
public Guid? CaptchaToken { get; set; }
public int CaptchaAnswer { get; set; }
}

26
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParameteresInput.cs

@ -0,0 +1,26 @@
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
using Volo.CmsKit.Comments;
namespace Volo.CmsKit.Public.Comments;
[Serializable]
public class CreateCommentWithParameteresInput
{
[Required]
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))]
public string Text { get; set; }
[Required]
public string EntityType { get; set; }
[Required]
public string EntityId { get; set; }
public Guid? RepliedCommentId { get; set; }
public Guid? CaptchaToken { get; set; }
public int CaptchaAnswer { get; set; }
}

2
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\common.props" />
<Import Project="..\..\..\..\configureawait.props" />

17
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs

@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization;
using Volo.Abp.Features;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Users;
using Volo.CmsKit.Blogs;
using Volo.CmsKit.Contents;
using Volo.CmsKit.Features;
using Volo.CmsKit.GlobalFeatures;
@ -70,4 +72,17 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub
return ObjectMapper.Map<CmsUser, CmsUserDto>(author);
}
[Authorize]
public async Task DeleteAsync(Guid id)
{
var rating = await BlogPostRepository.GetAsync(id);
if (rating.CreatorId != CurrentUser.GetId())
{
throw new AbpAuthorizationException();
}
await BlogPostRepository.DeleteAsync(id);
}
}

1
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs

@ -68,7 +68,6 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli
)
);
await UnitOfWorkManager.Current.SaveChangesAsync();
await DistributedEventBus.PublishAsync(new CreatedCommentEvent

8
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/BlogPostPublicClientProxy.Generated.cs

@ -50,4 +50,12 @@ public partial class BlogPostPublicClientProxy : ClientProxyBase<IBlogPostPublic
{ typeof(Guid), id }
});
}
public virtual async Task DeleteAsync(Guid id)
{
await RequestAsync(nameof(DeleteAsync), new ClientProxyRequestTypeValue
{
{ typeof(Guid), id }
});
}
}

37
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json

@ -1169,6 +1169,43 @@
},
"allowAnonymous": null,
"implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService"
},
"DeleteAsyncById": {
"uniqueName": "DeleteAsyncById",
"name": "DeleteAsync",
"httpMethod": "DELETE",
"url": "api/cms-kit-public/blog-posts/{id}",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "id",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "id",
"name": "id",
"jsonName": null,
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": [],
"bindingSourceId": "Path",
"descriptorName": ""
}
],
"returnValue": {
"type": "System.Void",
"typeSimple": "System.Void"
},
"allowAnonymous": null,
"implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService"
}
}
}

11
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs

@ -42,15 +42,22 @@ public class BlogPostPublicController : CmsKitPublicControllerBase, IBlogPostPub
[HttpGet]
[Route("authors")]
public Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input)
public virtual Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input)
{
return BlogPostPublicAppService.GetAuthorsHasBlogPostsAsync(input);
}
[HttpGet]
[Route("authors/{id}")]
public Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id)
public virtual Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id)
{
return BlogPostPublicAppService.GetAuthorHasBlogPostAsync(id);
}
[HttpDelete]
[Route("{id}")]
public virtual Task DeleteAsync(Guid id)
{
return BlogPostPublicAppService.DeleteAsync(id);
}
}

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs

@ -1,4 +1,6 @@
using AutoMapper;
using Volo.CmsKit.Menus;
using Volo.CmsKit.Public.Comments;
namespace Volo.CmsKit.Public.Web;
@ -6,6 +8,6 @@ public class CmsKitPublicWebAutoMapperProfile : Profile
{
public CmsKitPublicWebAutoMapperProfile()
{
CreateMap<CreateCommentWithParameteresInput, CreateCommentInput>();
}
}

42
modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicCommentsController.cs

@ -0,0 +1,42 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.ObjectMapping;
using Volo.CmsKit.Comments;
using Volo.CmsKit.Public.Comments;
using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting;
using Volo.CmsKit.Public.Web.Security.Captcha;
namespace Volo.CmsKit.Public.Web.Controllers;
//[Route("cms-kit/public-comments")]
public class CmsKitPublicCommentsController : AbpController
{
public ICommentPublicAppService CommentPublicAppService { get; }
protected CmsKitCommentOptions CmsKitCommentOptions { get; }
public SimpleMathsCaptchaGenerator SimpleMathsCaptchaGenerator { get; }
public CmsKitPublicCommentsController(
ICommentPublicAppService commentPublicAppService,
IOptions<CmsKitCommentOptions> cmsKitCommentOptions,
SimpleMathsCaptchaGenerator simpleMathsCaptchaGenerator)
{
CommentPublicAppService = commentPublicAppService;
CmsKitCommentOptions = cmsKitCommentOptions.Value;
SimpleMathsCaptchaGenerator = simpleMathsCaptchaGenerator;
}
[HttpPost]
public async Task ValidateAsync([FromBody] CreateCommentWithParameteresInput input)
{
if (CmsKitCommentOptions.IsRecaptchaEnabled && input.CaptchaToken.HasValue)
{
SimpleMathsCaptchaGenerator.Validate(input.CaptchaToken.Value, input.CaptchaAnswer);
}
var dto = ObjectMapper.Map<CreateCommentWithParameteresInput, CreateCommentInput> (input);
await CommentPublicAppService.CreateAsync(input.EntityType, input.EntityId, dto);
}
}

51
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
@ -7,7 +8,9 @@ using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.CmsKit.Comments;
using Volo.CmsKit.Public.Comments;
using Volo.CmsKit.Public.Web.Security.Captcha;
using Volo.CmsKit.Web.Renderers;
namespace Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting;
@ -24,15 +27,34 @@ public class CommentingViewComponent : AbpViewComponent
public ICommentPublicAppService CommentPublicAppService { get; }
public IMarkdownToHtmlRenderer MarkdownToHtmlRenderer { get; }
public AbpMvcUiOptions AbpMvcUiOptions { get; }
public CmsKitCommentOptions CmsKitCommentOptions { get; }
public SimpleMathsCaptchaGenerator SimpleMathsCaptchaGenerator { get; }
[HiddenInput]
[BindProperty]
public string RecaptchaToken { get; set; }
[HiddenInput]
[BindProperty]
public Guid CaptchaId { get; set; }
[BindProperty]
public CommentingViewModel Input { get; set; }
public CaptchaOutput CaptchaOutput { get; set; }
public CommentingViewComponent(
ICommentPublicAppService commentPublicAppService,
IOptions<AbpMvcUiOptions> options,
IMarkdownToHtmlRenderer markdownToHtmlRenderer)
IMarkdownToHtmlRenderer markdownToHtmlRenderer,
IOptions<CmsKitCommentOptions> cmsKitCommentOptions,
SimpleMathsCaptchaGenerator simpleMathsCaptchaGenerator)
{
CommentPublicAppService = commentPublicAppService;
MarkdownToHtmlRenderer = markdownToHtmlRenderer;
AbpMvcUiOptions = options.Value;
CmsKitCommentOptions = cmsKitCommentOptions.Value;
SimpleMathsCaptchaGenerator = simpleMathsCaptchaGenerator;
}
public virtual async Task<IViewComponentResult> InvokeAsync(
@ -42,7 +64,6 @@ public class CommentingViewComponent : AbpViewComponent
var comments = (await CommentPublicAppService
.GetListAsync(entityType, entityId)).Items;
var loginUrl = $"{AbpMvcUiOptions.LoginUrl}?returnUrl={HttpContext.Request.Path.ToString()}&returnUrlHash=#cms-comment_{entityType}_{entityId}";
var viewModel = new CommentingViewModel
@ -52,10 +73,26 @@ public class CommentingViewComponent : AbpViewComponent
LoginUrl = loginUrl,
Comments = comments.OrderByDescending(i => i.CreationTime).ToList()
};
await ConvertMarkdownTextsToHtml(viewModel);
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", viewModel);
if (CmsKitCommentOptions.IsRecaptchaEnabled)
{
CaptchaOutput = SimpleMathsCaptchaGenerator.Generate(new CaptchaOptions(
number1MinValue: 1,
number1MaxValue: 10,
number2MinValue: 5,
number2MaxValue: 15)
);
viewModel.CaptchaImageBase64 = GetCaptchaImageBase64(CaptchaOutput.ImageBytes);
}
this.Input = viewModel;
return View("~/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml", this);
}
private string GetCaptchaImageBase64(byte[] bytes)
{
return $"data:image/jpg;base64,{Convert.ToBase64String(bytes)}";
}
private async Task ConvertMarkdownTextsToHtml(CommentingViewModel viewModel)
@ -86,6 +123,12 @@ public class CommentingViewComponent : AbpViewComponent
public IReadOnlyList<CommentWithDetailsDto> Comments { get; set; }
public Dictionary<Guid, string> RawCommentTexts { get; set; }
[Required]
[StringLength(100, MinimumLength = 1)]
public string Captcha { get; set; }
public string CaptchaImageBase64 { get; set; }
}
}

55
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml

@ -3,6 +3,7 @@
@using Microsoft.Extensions.Options
@using Volo.Abp.GlobalFeatures
@using Volo.Abp.Users
@using Volo.CmsKit.Comments;
@using Volo.CmsKit.GlobalFeatures
@using Volo.CmsKit.Localization
@using Volo.CmsKit.Public.Comments
@ -10,8 +11,9 @@
@using Volo.CmsKit.Web
@inject ICurrentUser CurrentUser
@inject IOptionsSnapshot<CmsKitUiOptions> cmsKitUiOptions;
@inject IOptions<CmsKitCommentOptions> CmsKitCommentOptions
@inject IHtmlLocalizer<CmsKitResource> L
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting.CommentingViewComponent.CommentingViewModel
@model Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting.CommentingViewComponent
@{
Func<dynamic, IHtmlContent> GetCommentTitle(CmsUserDto author, DateTime creationTime) =>
@ -36,9 +38,29 @@
<textarea class="form-control" name="commentText" placeholder="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? L["YourComment"] : L["YourReply"])..." rows="3"></textarea>
</div>
</div>
<div class="mt-0">
<small class="text-muted float-start float-end">@L["MarkdownSupported"]</small>
</div>
@if (CmsKitCommentOptions.Value.IsRecaptchaEnabled)
{
<div class="volo-captcha">
<label class="form-label" for="Input_Captcha">@L["CaptchaCode"]</label>
<div class="d-flex">
<div class="bd-highlight">
<img src="@Model.Input.CaptchaImageBase64" />
</div>
<div class="flex-grow-1 bd-highlight">
<abp-input asp-for="@Model.Input.Captcha" suppress-label="true" class="d-inline-block" autocomplete="off" />
</div>
<abp-input asp-for="@Model.CaptchaId" value="@Model.CaptchaOutput.Id" />
</div>
</div>
}
<div class="col-auto">
<div class="text-end">
<abp-button type="submit" button-type="Primary" size="Block">
<abp-button id="submit-button" type="submit" button-type="Primary" size="Block">
<i class="fa fa-comment-alt me-1"></i> @L["Send"]
</abp-button>
@if (cancelButton)
@ -49,9 +71,6 @@
}
</div>
</div>
<div class="mt-0">
<small class="text-muted float-start" >@L["MarkdownSupported"]</small>
</div>
</div>
</form>
</div>;
@ -71,13 +90,13 @@
{
@if (CurrentUser.IsAuthenticated)
{
<a href="#" class="comment-links comment-reply-link btn btn-sm shadow-sm btn-primary" data-reply-id="@id.ToString()" id="@($"cms-comment_{Model.EntityType}_{Model.EntityId}_{id}_link")">
<a href="#" class="comment-links comment-reply-link btn btn-sm shadow-sm btn-primary" data-reply-id="@id.ToString()" id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}_{id}_link")">
<i class="fa fa-reply mr -1"></i> @L["Reply"]
</a>
}
else
{
<a href="@(Model.LoginUrl + "_" + id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a>
<a href="@(Model.Input.LoginUrl + "_" + id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a>
}
}
@if (authorId == CurrentUser.Id)
@ -93,9 +112,10 @@
}
@{
Func<dynamic, IHtmlContent> GetEditArea(Guid id, string text, string concurrencyStamp) =>
@<div class="cms-comment-edit-area" id="@($"cms-comment_{Model.EntityType}_{Model.EntityId}_{id}")" data-id="@id.ToString()" style="display:none">
@<div class="cms-comment-edit-area" id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}_{id}")" data-id="@id.ToString()" style="display:none">
<div class="card bg-light p-3 mx-0 mt-3">
<form class="cms-comment-update-form">
<form id="ContactForm" class="cms-comment-update-form">
<abp-input asp-for="@Model.RecaptchaToken" />
<input hidden value="@id.ToString()" name="id" />
<div class="row">
<div class="col">
@ -119,20 +139,20 @@
</div>;
}
<div class="cms-comment-area mb-5" data-entity-type="@Model.EntityType" data-entity-id="@Model.EntityId">
<div class="cms-comment-area mb-5" data-entity-type="@Model.Input.EntityType" data-entity-id="@Model.Input.EntityId">
@if (CurrentUser.IsAuthenticated)
{
<div id="@($"cms-comment_{Model.EntityType}_{Model.EntityId}")">
<div id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}")">
@GetCommentArea(null).Invoke(null)
</div>
}
else if (!string.IsNullOrWhiteSpace(Model.LoginUrl))
else if (!string.IsNullOrWhiteSpace(Model.Input.LoginUrl))
{
<div class="text-end">
<a href="@Model.LoginUrl" class="btn btn-primary"> @L["LoginToAddComment"]</a>
<a href="@Model.Input.LoginUrl" class="btn btn-primary"> @L["LoginToAddComment"]</a>
</div>
}
@foreach (var comment in Model.Comments)
@foreach (var comment in Model.Input.Comments)
{
<div class="comment">
<div class="card p-3 mx-0 my-3">
@ -157,7 +177,7 @@
</div>
</div>
</div>
@GetEditArea(comment.Id, Model.RawCommentTexts[comment.Id], comment.ConcurrencyStamp).Invoke(null)
@GetEditArea(comment.Id, Model.Input.RawCommentTexts[comment.Id], comment.ConcurrencyStamp).Invoke(null)
@if (comment.Replies.Any())
{
@ -187,7 +207,7 @@
</div>
</div>
@GetEditArea(reply.Id, Model.RawCommentTexts[reply.Id], reply.ConcurrencyStamp).Invoke(null)
@GetEditArea(reply.Id, Model.Input.RawCommentTexts[reply.Id], reply.ConcurrencyStamp).Invoke(null)
</div>
</div>
}
@ -202,7 +222,7 @@
}
else
{
<a href="@(Model.LoginUrl + "_" + comment.Id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a>
<a href="@(Model.Input.LoginUrl + "_" + comment.Id)" class="btn btn-sm btn-light shadow-sm"> @L["LoginToReply"]</a>
}
</div>
}
@ -216,3 +236,4 @@
</div>
}
</div>

26
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.css

@ -3,4 +3,28 @@
}
.comment-links:hover {
text-decoration: none;
}
}
body .volo-captcha img {
border: 1px outset #dddcdc;
border-radius: 4px;
height: 45px;
}
body .volo-captcha .d-flex .form-group {
width: 100%;
}
@media (max-width: 800px) {
body .volo-captcha #Input_Captcha {
margin-left: 0px;
}
}
body .form-control {
border: 1px outset #e3e3e3 !important;
padding: 10px !important;
}
body #submit-button {
margin-top: 10px;
}

83
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js

@ -1,10 +1,10 @@
(function ($) {
var l = abp.localization.getResource('CmsKit');
let l = abp.localization.getResource('CmsKit');
abp.widgets.CmsCommenting = function ($widget) {
var widgetManager = $widget.data('abp-widget-manager');
var $commentArea = $widget.find('.cms-comment-area');
let widgetManager = $widget.data('abp-widget-manager');
let $commentArea = $widget.find('.cms-comment-area');
function getFilters() {
return {
@ -15,14 +15,14 @@
function registerEditLinks($container) {
$container.find('.comment-edit-link').each(function () {
var $link = $(this);
let $link = $(this);
$link.on('click', function (e) {
e.preventDefault();
var commentId = $link.data('id');
let commentId = $link.data('id');
var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']');
var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']');
let $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']');
let $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']');
$relatedCommentContentArea.hide();
$relatedCommentEditFormArea.show();
@ -30,15 +30,15 @@
});
});
$container.find('.comment-edit-cancel-button').each(function () {
var $button = $(this);
let $button = $(this);
$button.on('click', function (e) {
e.preventDefault();
var commentId = $button.data('id');
let commentId = $button.data('id');
var $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']');
var $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']');
var $link = $container.find('.comment-edit-link[data-id=' + commentId + ']');
let $relatedCommentContentArea = $container.find('.cms-comment-content-area[data-id=' + commentId + ']');
let $relatedCommentEditFormArea = $container.find('.cms-comment-edit-area[data-id=' + commentId + ']');
let $link = $container.find('.comment-edit-link[data-id=' + commentId + ']');
$relatedCommentContentArea.show();
$relatedCommentEditFormArea.hide();
@ -49,14 +49,14 @@
function registerReplyLinks($container) {
$container.find('.comment-reply-link').each(function () {
var $link = $(this);
let $link = $(this);
$link.on('click', function (e) {
e.preventDefault();
var replyCommentId = $link.data('reply-id');
let replyCommentId = $link.data('reply-id');
var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']');
var $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']');
let $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']');
let $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']');
$relatedCommentArea.show();
$relatedCommentArea.find('textarea').focus();
@ -64,14 +64,14 @@
});
});
$container.find('.reply-cancel-button').each(function () {
var $button = $(this);
let $button = $(this);
$button.on('click', function (e) {
e.preventDefault();
var replyCommentId = $button.data('reply-id');
let replyCommentId = $button.data('reply-id');
var $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']');
var $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']');
let $relatedCommentArea = $container.find('.cms-comment-form-area[data-reply-id=' + replyCommentId + ']');
let $links = $container.find('.comment-reply-link[data-reply-id=' + replyCommentId + ']');
$relatedCommentArea.hide();
$links.removeClass('disabled');
@ -81,7 +81,7 @@
function registerDeleteLinks($container) {
$container.find('.comment-delete-link').each(function () {
var $link = $(this);
let $link = $(this);
$link.on('click', '', function (e) {
e.preventDefault();
@ -99,10 +99,10 @@
function registerUpdateOfNewComment($container) {
$container.find('.cms-comment-update-form').each(function () {
var $form = $(this);
let $form = $(this);
$form.submit(function (e) {
e.preventDefault();
var formAsObject = $form.serializeFormToObject();
let formAsObject = $form.serializeFormToObject();
volo.cmsKit.public.comments.commentPublic.update(
formAsObject.id,
{
@ -118,24 +118,39 @@
function registerSubmissionOfNewComment($container) {
$container.find('.cms-comment-form').each(function () {
var $form = $(this);
let $form = $(this);
$form.submit(function (e) {
e.preventDefault();
var formAsObject = $form.serializeFormToObject();
let formAsObject = $form.serializeFormToObject();
if (formAsObject.repliedCommentId == ''){
if (formAsObject.repliedCommentId == '') {
formAsObject.repliedCommentId = null;
}
volo.cmsKit.public.comments.commentPublic.create(
$commentArea.attr('data-entity-type'),
$commentArea.attr('data-entity-id'),
{
if (formAsObject.commentText == '') {
abp.message.error(l("CommentTextRequired"));
return;
}
$.ajax({
type: 'POST',
url: '/CmsKitPublicComments/Validate',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify({
entityId: $commentArea.attr('data-entity-id'),
entityType: $commentArea.attr('data-entity-type'),
repliedCommentId: formAsObject.repliedCommentId,
text: formAsObject.commentText
text: formAsObject.commentText,
captchaToken: formAsObject.captchaId,
captchaAnswer: formAsObject.input?.captcha
}),
success: function () {
widgetManager.refresh($widget);
},
error: function (data) {
abp.message.error(data.responseJSON.error.message);
}
).then(function () {
widgetManager.refresh($widget);
});
});
});
@ -146,7 +161,7 @@
return;
}
var $link = $(location.hash + '_link');
let $link = $(location.hash + '_link');
if ($link.length > 0) {
$link.click();

30
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.scss

@ -0,0 +1,30 @@
body {
.volo-captcha {
img {
border: 1px outset #dddcdc;
border-radius: 4px;
height: 45px;
}
.d-flex {
.form-group {
width: 100%;
}
}
@media (max-width: 800px) {
#Input_Captcha {
margin-left: 0px;
}
}
}
.form-control {
border: 1px outset #e3e3e3 !important;
padding: 10px !important;
}
#submit-button {
margin-top: 10px;
}
}

61
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml

@ -1,6 +1,7 @@
@page
@using Volo.Abp.Data
@using Volo.Abp.Users;
@using Volo.CmsKit.Public.Blogs
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Blogs.BlogPostComment
@using Volo.CmsKit.Public.Web.Pages
@ -17,9 +18,9 @@
@model Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs.BlogPostModel
@inject ICurrentUser CurrentUser
@inject IMarkdownToHtmlRenderer MarkdownRenderer
@{
string dummyImageSource = "https://dummyimage.com/1280x720/a3a3a3/fff.png?text=" + Model.ViewModel.Title;
var isScrollIndexEnabled = GlobalFeatureManager.Instance.IsEnabled<BlogPostScrollIndexFeature>() && Model.BlogPostScrollIndexFeature?.IsEnabled == true;
@ -50,14 +51,15 @@
<abp-script-bundle>
<abp-script type="typeof(HighlightJsScriptContributor)" />
<abp-script src="/Pages/Public/CmsKit/highlightOnLoad.js" />
<abp-script src="/Pages/Public/CmsKit/Blogs/blogPost.js" />
</abp-script-bundle>
}
<div class="row">
<div @Html.Raw(isScrollIndexEnabled ? "class=\"col-md-10 col-sm-12\"" : "class=\"col-md-12\"")>
<abp-card class="mb-4">
<img src="/api/cms-kit/media/@Model.ViewModel.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" />
<input hidden id="BlogId" value="@Model.ViewModel.Id" />
<abp-card-body>
<abp-row>
<div class="col-lg-8 col-md-10 mx-auto pb-4">
@ -109,10 +111,10 @@
if (Model.ReactionsFeature?.IsEnabled == true)
{
@await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new
{
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,
entityId = Model.ViewModel.Id.ToString()
})
{
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,
entityId = Model.ViewModel.Id.ToString()
})
}
}
</abp-column>
@ -122,10 +124,10 @@
if (Model.RatingsFeature?.IsEnabled == true)
{
@await Component.InvokeAsync(typeof(RatingViewComponent), new
{
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,
entityId = Model.ViewModel.Id.ToString()
})
{
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,
entityId = Model.ViewModel.Id.ToString()
})
}
}
</abp-column>
@ -133,24 +135,33 @@
</abp-card-body>
</abp-card>
</div>
@if (isScrollIndexEnabled)
{
<div class="col-md-2 d-sm-none d-md-block">
<div id="scroll-index" class="docs-inner-anchors mt-2">
<h5>@L["InThisDocument"]</h5>
<nav id="blog-post-sticky-index" class="navbar index-scroll pt-0">
</nav>
<div class="row">
<div class="col p-0 py-3">
<a href="#" class="scroll-top-btn">
<i class="fa fa-chevron-up"></i> @L["GoToTop"]
</a>
<div class="col-md-2 d-sm-none d-md-block">
@if (Model.ViewModel.Author.Id == CurrentUser.Id)
{
<button id="deleteBlogPost" class="btn btn-danger">
<i class="fa fa-remove">
@L["Delete"]
</i>
</button>
}
@if (isScrollIndexEnabled)
{
<div id="scroll-index" class="docs-inner-anchors mt-2">
<h5>@L["InThisDocument"]</h5>
<nav id="blog-post-sticky-index" class="navbar index-scroll pt-0">
</nav>
<div class="row">
<div class="col p-0 py-3">
<a href="#" class="scroll-top-btn">
<i class="fa fa-chevron-up"></i> @L["GoToTop"]
</a>
</div>
</div>
</div>
</div>
</div>
}
}
</div>
</div>

1
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml.cs

@ -1,7 +1,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.ObjectMapping;
using Volo.CmsKit.Blogs;
using Volo.CmsKit.Contents;
using Volo.CmsKit.GlobalFeatures;

16
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/blogPost.js

@ -0,0 +1,16 @@
$(function () {
let l = abp.localization.getResource("CmsKit");
$('#deleteBlogPost').on('click', '', function (e) {
abp.message.confirm(l("DeleteBlogPostMessage"), function (ok) {
if (ok) {
volo.cmsKit.public.blogs.blogPostPublic.delete(
$('#BlogId').val()
).then(function () {
document.location.href = "/";
});
}
})
});
});

8
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/index.js

@ -1,14 +1,14 @@
$(function () {
var $selectAuthor = $('#AuthorSelect');
var $authorNameSpan = $('.author-name-span');
let $selectAuthor = $('#AuthorSelect');
let $authorNameSpan = $('.author-name-span');
$selectAuthor.on('change', function () {
var authorId = $selectAuthor.val();
let authorId = $selectAuthor.val();
reloadPageWithQueryString({'authorId': authorId});
});
$authorNameSpan.click(function () {
var authorId = $(this).data('author-id');
let authorId = $(this).data('author-id');
reloadPageWithQueryString({'authorId': authorId});
});

15
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs

@ -0,0 +1,15 @@
using System.Runtime.Serialization;
using Volo.Abp;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaException : UserFriendlyException
{
public CaptchaException(string message) : base(message)
{
}
public CaptchaException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context)
{
}
}

66
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOptions.cs

@ -0,0 +1,66 @@
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaOptions
{
/// <summary>
/// Default fonts are "Arial", "Verdana", "Times New Roman" in Windows. These fonts must exist in the target OS.
/// </summary>
public string[] FontFamilies { get; set; } = new string[] { "Arial", "Verdana", "Times New Roman" };
public Color[] TextColor { get; set; } = new Color[]
{
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green
};
public Color[] DrawLinesColor { get; set; } = new Color[]
{
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green
};
public float MinLineThickness { get; set; } = 0.7f;
public float MaxLineThickness { get; set; } = 2.0f;
public ushort Width { get; set; } = 180;
public ushort Height { get; set; } = 70;
public ushort NoiseRate { get; set; } = 500;
public Color[] NoiseRateColor { get; set; } = new Color[] { Color.Gray };
public byte FontSize { get; set; } = 32;
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
public EncoderTypes EncoderType { get; set; } = EncoderTypes.Png;
public IImageEncoder Encoder => RandomTextGenerator.GetEncoder(EncoderType);
public byte DrawLines { get; set; } = 2;
public byte MaxRotationDegrees { get; set; } = 4;
public int Number1MinValue { get; set; } = 1;
public int Number1MaxValue { get; set; } = 99;
public int Number2MinValue { get; set; } = 1;
public int Number2MaxValue { get; set; } = 99;
public CaptchaOptions()
{
}
public CaptchaOptions(int number1MinValue, int number1MaxValue, int number2MinValue, int number2MaxValue)
{
Number1MinValue = number1MinValue;
Number1MaxValue = number1MaxValue;
Number2MinValue = number2MinValue;
Number1MaxValue = number2MaxValue;
}
}

32
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOutput.cs

@ -0,0 +1,32 @@
using System;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaOutput
{
public Guid Id { get; set; }
public string Text { get; set; }
public byte[] ImageBytes { get; set; }
public int Result { get; set; }
}
public class CaptchaInput
{
public int Number1 { get; set; }
public int Number2 { get; set; }
}
public class CaptchaRequest
{
public CaptchaInput Input { get; set; }
public CaptchaOutput Output { get; set; }
public CaptchaRequest()
{
Input = new CaptchaInput();
Output = new CaptchaOutput
{
Id = Guid.NewGuid()
};
}
}

7
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/EncoderTypes.cs

@ -0,0 +1,7 @@
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public enum EncoderTypes
{
Jpeg,
Png,
}

73
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/RandomTextGenerator.cs

@ -0,0 +1,73 @@
using System;
using System.Security.Cryptography;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public static class RandomTextGenerator
{
private static readonly char[] AllowedChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVXYZW23456789".ToCharArray();
public static IImageEncoder GetEncoder(EncoderTypes encoderType)
{
IImageEncoder encoder = encoderType switch
{
EncoderTypes.Png => new PngEncoder(),
EncoderTypes.Jpeg => new JpegEncoder(),
_ => throw new ArgumentException($"Encoder '{encoderType}' not found!")
};
return encoder;
}
public static string GetRandomText(int size)
{
var data = new byte[4 * size];
using (var crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
var result = new StringBuilder(size);
for (var i = 0; i < size; i++)
{
var rnd = BitConverter.ToUInt32(data, i * 4);
var idx = rnd % AllowedChars.Length;
result.Append(AllowedChars[idx]);
}
return result.ToString();
}
public static string GetUniqueKey(int size, char[] chars)
{
var data = new byte[4 * size];
using (var crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
var result = new StringBuilder(size);
for (var i = 0; i < size; i++)
{
var rnd = BitConverter.ToUInt32(data, i * 4);
var idx = rnd % chars.Length;
result.Append(chars[idx]);
}
return result.ToString();
}
public static float GenerateNextFloat(double min = -3.40282347E+38, double max = 3.40282347E+38)
{
var random = new Random();
var range = max - min;
var sample = random.NextDouble();
var scaled = sample * range + min;
var result = (float)scaled;
return result;
}
}

175
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs

@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Volo.Abp.DependencyInjection;
using Color = SixLabors.ImageSharp.Color;
using PointF = SixLabors.ImageSharp.PointF;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class SimpleMathsCaptchaGenerator : ISingletonDependency
{
private static Dictionary<Guid, CaptchaRequest> Session { get; set; } = new Dictionary<Guid, CaptchaRequest>();
public CaptchaOutput Generate()
{
return Generate(options: null, number1: null, number2: null);
}
public CaptchaOutput Generate(CaptchaOptions options)
{
return Generate(options, number1: null, number2: null);
}
/// <summary>
/// Creates a simple captcha code.
/// </summary>
/// <param name="options">Options for captcha generation</param>
/// <param name="number1">First number for maths operation</param>
/// <param name="number2">Second number for maths operation</param>
/// <returns></returns>
public CaptchaOutput Generate(CaptchaOptions options, int? number1, int? number2)
{
var random = new Random();
options ??= new CaptchaOptions();
number1 ??= random.Next(options.Number1MinValue, options.Number1MaxValue);
number2 ??= random.Next(options.Number2MinValue, options.Number2MaxValue);
var text = number1 + "+" + number2;
var request = new CaptchaRequest
{
Input =
{
Number1 = number1.Value,
Number2 = number2.Value
},
Output =
{
Text = text,
Result = Calculate(number1.Value, number2.Value),
ImageBytes = GenerateInternal(text, options)
}
};
Session[request.Output.Id] = request;
return request.Output;
}
private static int Calculate(int number1, int number2)
{
return number1 + number2;
}
public void Validate(Guid requestId, int value)
{
var request = Session[requestId];
if (request.Output.Result != value)
{
throw new CaptchaException("The captcha code doesn't match text on the picture! Please try again.");
}
}
public void Validate(Guid requestId, string value)
{
if (int.TryParse(value, out var captchaInput))
{
Validate(requestId, captchaInput);
}
else
{
throw new CaptchaException("The captcha code is missing!");
}
}
private byte[] GenerateInternal(string stringText, CaptchaOptions options)
{
byte[] result;
using (var image = new Image<Rgba32>(options.Width, options.Height))
{
float position = 0;
var random = new Random();
var startWith = (byte)random.Next(5, 10);
image.Mutate(ctx => ctx.BackgroundColor(Color.Transparent));
var fontName = options.FontFamilies[random.Next(0, options.FontFamilies.Length)];
var font = SystemFonts.CreateFont(fontName, options.FontSize, options.FontStyle);
foreach (var character in stringText)
{
var text = character.ToString();
var color = options.TextColor[random.Next(0, options.TextColor.Length)];
var location = new PointF(startWith + position, random.Next(6, 13));
image.Mutate(ctx => ctx.DrawText(text, font, color, location));
position += TextMeasurer.Measure(character.ToString(), new RendererOptions(font, location)).Width;
}
//add rotation
var rotation = GetRotation(options);
image.Mutate(ctx => ctx.Transform(rotation));
// add the dynamic image to original image
var size = (ushort)TextMeasurer.Measure(stringText, new RendererOptions(font)).Width;
var img = new Image<Rgba32>(size + 15, options.Height);
img.Mutate(ctx => ctx.BackgroundColor(Color.White));
Parallel.For(0, options.DrawLines, i =>
{
var x0 = random.Next(0, random.Next(0, 30));
var y0 = random.Next(10, img.Height);
var x1 = random.Next(30, img.Width);
var y1 = random.Next(0, img.Height);
img.Mutate(ctx =>
ctx.DrawLines(options.TextColor[random.Next(0, options.TextColor.Length)],
RandomTextGenerator.GenerateNextFloat(options.MinLineThickness, options.MaxLineThickness),
new PointF[] { new PointF(x0, y0), new PointF(x1, y1) })
);
});
img.Mutate(ctx => ctx.DrawImage(image, 0.80f));
Parallel.For(0, options.NoiseRate, i =>
{
var x0 = random.Next(0, img.Width);
var y0 = random.Next(0, img.Height);
img.Mutate(
ctx => ctx
.DrawLines(options.NoiseRateColor[random.Next(0, options.NoiseRateColor.Length)],
RandomTextGenerator.GenerateNextFloat(0.5, 1.5), new PointF[] { new Vector2(x0, y0), new Vector2(x0, y0) })
);
});
img.Mutate(x =>
{
x.Resize(options.Width, options.Height);
});
using (var ms = new MemoryStream())
{
img.Save(ms, options.Encoder);
result = ms.ToArray();
}
}
return result;
}
private static AffineTransformBuilder GetRotation(CaptchaOptions options)
{
var random = new Random();
var width = random.Next(10, options.Width);
var height = random.Next(10, options.Height);
var pointF = new PointF(width, height);
var rotationDegrees = random.Next(0, options.MaxRotationDegrees);
return new AffineTransformBuilder().PrependRotationDegrees(rotationDegrees, pointF);
}
}

5
modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj

@ -14,10 +14,13 @@
<ItemGroup>
<ProjectReference Include="..\Volo.CmsKit.Common.Web\Volo.CmsKit.Common.Web.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Markdig.Signed" Version="0.26.0" />
<PackageReference Include="HtmlSanitizer" Version="5.0.331" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta13" />
</ItemGroup>
<ItemGroup>

8
modules/cms-kit/src/Volo.CmsKit.Public.Web/wwwroot/client-proxies/cms-kit-proxy.js

@ -214,6 +214,14 @@
}, ajaxParams));
};
volo.cmsKit.public.blogs.blogPostPublic['delete'] = function(id, ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/cms-kit-public/blog-posts/' + id + '',
type: 'DELETE',
dataType: null
}, ajaxParams));
};
})();
})();

Loading…
Cancel
Save