Adding Approve, Delete and Moderate GET actions driven by tokens

Allow comments tokens to be chained with Text ones (HtmlEncode, ...)

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2013-03-06 14:34:07 -08:00
parent 874bd7df9d
commit d945a8eaf1
6 changed files with 116 additions and 18 deletions

View File

@@ -123,6 +123,36 @@ namespace Orchard.Comments.Controllers {
return this.RedirectLocal(returnUrl, "~/");
}
public ActionResult Approve(string nonce) {
int id;
if (_commentService.DecryptNonce(nonce, out id)) {
_commentService.ApproveComment(id);
}
Services.Notifier.Information(T("Comment approved successfully"));
return Redirect("~/");
}
public ActionResult Delete(string nonce) {
int id;
if (_commentService.DecryptNonce(nonce, out id)) {
_commentService.DeleteComment(id);
}
Services.Notifier.Information(T("Comment deleted successfully"));
return Redirect("~/");
}
public ActionResult Moderate(string nonce) {
int id;
if (_commentService.DecryptNonce(nonce, out id)) {
_commentService.UnapproveComment(id);
}
Services.Notifier.Information(T("Comment moderated successfully"));
return Redirect("~/");
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}

View File

@@ -9,5 +9,5 @@ Features:
Orchard.Comments:
Name: Comments
Description: Standard content item comments.
Dependencies: Settings
Dependencies: Settings, Orchard.Tokens
Category: Social

View File

@@ -119,6 +119,10 @@
<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>
<Content Include="Views\Admin\Edit.cshtml" />

View File

@@ -1,15 +1,28 @@
using JetBrains.Annotations;
using System;
using System.Globalization;
using System.Text;
using System.Xml.Linq;
using JetBrains.Annotations;
using Orchard.Comments.Models;
using Orchard.Logging;
using Orchard.ContentManagement;
using Orchard.Security;
using Orchard.Services;
namespace Orchard.Comments.Services {
[UsedImplicitly]
public class CommentService : ICommentService {
private readonly IOrchardServices _orchardServices;
private readonly IClock _clock;
private readonly IEncryptionService _encryptionService;
public CommentService(IOrchardServices orchardServices) {
public CommentService(
IOrchardServices orchardServices,
IClock clock,
IEncryptionService encryptionService) {
_orchardServices = orchardServices;
_clock = clock;
_encryptionService = encryptionService;
Logger = NullLogger.Instance;
}
@@ -64,7 +77,7 @@ namespace Orchard.Comments.Services {
}
public void DeleteComment(int commentId) {
_orchardServices.ContentManager.Remove(_orchardServices.ContentManager.Get(commentId));
_orchardServices.ContentManager.Remove(_orchardServices.ContentManager.Get<CommentPart>(commentId).ContentItem);
}
public bool CommentsDisabledForCommentedContent(int id) {
@@ -79,6 +92,28 @@ namespace Orchard.Comments.Services {
_orchardServices.ContentManager.Get<CommentsPart>(id, VersionOptions.Latest).CommentsActive = true;
}
public string CreateNonce(CommentPart comment, TimeSpan delay) {
var challengeToken = new XElement("n", new XAttribute("c", comment.Id), new XAttribute("v", _clock.UtcNow.ToUniversalTime().Add(delay).ToString(CultureInfo.InvariantCulture))).ToString();
var data = Encoding.UTF8.GetBytes(challengeToken);
return Convert.ToBase64String(_encryptionService.Encode(data));
}
public bool DecryptNonce(string nonce, out int id) {
id = 0;
try {
var data = _encryptionService.Decode(Convert.FromBase64String(nonce));
var xml = Encoding.UTF8.GetString(data);
var element = XElement.Parse(xml);
id = Convert.ToInt32(element.Attribute("c").Value);
var validateByUtc = DateTime.Parse(element.Attribute("v").Value, CultureInfo.InvariantCulture);
return _clock.UtcNow <= validateByUtc;
}
catch {
return false;
}
}
private CommentPart GetCommentWithQueryHints(int id) {
return _orchardServices.ContentManager.Get<CommentPart>(id, VersionOptions.Latest, new QueryHints().ExpandParts<CommentPart>());
}

View File

@@ -1,3 +1,4 @@
using System;
using Orchard.Comments.Models;
using Orchard.ContentManagement;
@@ -16,5 +17,7 @@ namespace Orchard.Comments.Services {
bool CommentsDisabledForCommentedContent(int id);
void DisableCommentsForCommentedContent(int id);
void EnableCommentsForCommentedContent(int id);
bool DecryptNonce(string nonce, out int id);
string CreateNonce(CommentPart comment, TimeSpan delay);
}
}

View File

@@ -1,46 +1,72 @@
using System;
using System.Web.Mvc;
using Orchard.Comments.Models;
using Orchard.Comments.Services;
using Orchard.ContentManagement;
using Orchard.Events;
using Orchard.Localization;
using Orchard.Mvc.Extensions;
using Orchard.Tokens;
namespace Orchard.Comments.Tokens {
public interface ITokenProvider : IEventHandler {
void Describe(dynamic context);
void Evaluate(dynamic context);
}
public class CommentTokens : ITokenProvider {
private readonly IContentManager _contentManager;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly ICommentService _commentService;
public CommentTokens(IContentManager contentManager) {
public CommentTokens(
IContentManager contentManager,
IWorkContextAccessor workContextAccessor,
ICommentService commentService) {
_contentManager = contentManager;
_workContextAccessor = workContextAccessor;
_commentService = commentService;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public void Describe(dynamic context) {
public void Describe(DescribeContext context) {
context.For("Content", T("Comments"), T("Comments"))
.Token("CommentedOn", T("Commented On"), T("The content item this comment was created on."))
.Token("CommentMessage", T("Comment Message"), T("The text of the comment itself"))
.Token("CommentAuthor", T("Comment Author"), T("The author of the comment."))
.Token("CommentAuthorUrl", T("Comment Author Url"), T("The url provided by the author of the comment."))
.Token("CommentAuthorEmail", T("Comment Author Email"), T("The email provided by the author of the comment."))
.Token("CommentApproveUrl", T("Comment approval Url"), T("The absolute url to follow in order to approve this comment."))
.Token("CommentModerateUrl", T("Comment moderation Url"), T("The absolute url to follow in order to moderate this comment."))
.Token("CommentDeleteUrl", T("Comment deletion Url"), T("The absolute url to follow in order to delete this comment."))
;
}
public void Evaluate(dynamic context) {
public void Evaluate(EvaluateContext context) {
context.For<IContent>("Content")
.Token("CommentedOn", (Func<IContent, object>)(content => content.As<CommentPart>().CommentedOn))
.Chain("CommentedOn", "Content", (Func<IContent, object>)(content => _contentManager.Get(content.As<CommentPart>().CommentedOn)))
.Token("CommentMessage", (Func<IContent, object>)(content => content.As<CommentPart>().CommentText))
.Token("CommentAuthor", (Func<IContent, object>)CommentAuthor)
.Token("CommentAuthorUrl", (Func<IContent, object>)(content => content.As<CommentPart>().SiteName))
.Token("CommentAuthorEmail", (Func<IContent, object>)(content => content.As<CommentPart>().Email))
.Token("CommentedOn", content => content.As<CommentPart>().CommentedOn)
.Chain("CommentedOn", "Content", content => _contentManager.Get(content.As<CommentPart>().CommentedOn))
.Token("CommentMessage", content => content.As<CommentPart>().CommentText)
.Chain("CommentMessage", "Text", content => content.As<CommentPart>().CommentText)
.Token("CommentAuthor", CommentAuthor)
.Chain("CommentAuthor", "Text", CommentAuthor)
.Token("CommentAuthorUrl", content => content.As<CommentPart>().SiteName)
.Chain("CommentAuthorUrl", "Text", content => content.As<CommentPart>().SiteName)
.Token("CommentAuthorEmail", content => content.As<CommentPart>().Email)
.Chain("CommentAuthorEmail", "Text", content => content.As<CommentPart>().Email)
.Token("CommentApproveUrl", content => CreateProtectedUrl("Approve", content.As<CommentPart>()))
.Token("CommentModerateUrl", content => CreateProtectedUrl("Moderate", content.As<CommentPart>()))
.Token("CommentDeleteUrl", content => CreateProtectedUrl("Delete", content.As<CommentPart>()))
;
}
private string CreateProtectedUrl(string action, CommentPart part) {
var workContext = _workContextAccessor.GetContext();
if (workContext.HttpContext != null) {
var url = new UrlHelper(workContext.HttpContext.Request.RequestContext);
return url.AbsoluteAction(action, "Comment", new {area = "Orchard.Comments", nonce = _commentService.CreateNonce(part, TimeSpan.FromDays(7))});
}
return null;
}
private static string CommentAuthor(IContent comment) {
var commentPart = comment.As<CommentPart>();
return String.IsNullOrWhiteSpace(commentPart.UserName) ? commentPart.Author : commentPart.UserName;