From 75340abdccd41b57db5f9af231bd0d9fe2147be2 Mon Sep 17 00:00:00 2001 From: suhacan Date: Sat, 21 Nov 2009 23:55:11 +0000 Subject: [PATCH] - Comments: Moderation. Editing/Deleting/Marking as Spam/Closing comments per content item. --HG-- extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4041721 --- .../Controllers/AdminController.cs | 125 ++++++++++++++++++ .../Orchard.Comments/Models/Comment.cs | 5 + .../Models/CommentsHandler.cs | 10 +- .../Orchard.Comments/Orchard.Comments.csproj | 4 + .../Services/CommentService.cs | 27 ++++ .../ViewModels/CommentsDetailsViewModel.cs | 28 ++++ .../ViewModels/CommentsEditViewModel.cs | 11 ++ .../Orchard.Comments/Views/Admin/Details.aspx | 89 +++++++++++++ .../Orchard.Comments/Views/Admin/Edit.aspx | 36 +++++ .../Orchard.Comments/Views/Admin/Index.aspx | 4 +- .../Models/DisplayTemplates/HasComments.ascx | 5 + 11 files changed, 340 insertions(+), 4 deletions(-) create mode 100644 src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsDetailsViewModel.cs create mode 100644 src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsEditViewModel.cs create mode 100644 src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Details.aspx create mode 100644 src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Edit.aspx diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs b/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs index 9be1577e3..a0e6bdade 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs +++ b/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs @@ -136,6 +136,131 @@ namespace Orchard.Comments.Controllers { } } + public ActionResult Details(int id, CommentDetailsOptions options) { + // Default options + if (options == null) + options = new CommentDetailsOptions(); + + // Filtering + IEnumerable comments; + try { + switch (options.Filter) { + case CommentDetailsFilter.All: + comments = _commentService.GetCommentsForCommentedContent(id); + break; + case CommentDetailsFilter.Approved: + comments = _commentService.GetCommentsForCommentedContent(id, CommentStatus.Approved); + break; + case CommentDetailsFilter.Spam: + comments = _commentService.GetCommentsForCommentedContent(id, CommentStatus.Spam); + break; + default: + throw new ArgumentOutOfRangeException(); + } + var entries = comments.Select(comment => CreateCommentEntry(comment)).ToList(); + var model = new CommentsDetailsViewModel { + Comments = entries, + Options = options, + DisplayNameForCommentedItem = _commentService.GetDisplayForCommentedContent(id).DisplayText, + CommentedItemId = id, + }; + return View(model); + } + catch (Exception exception) { + _notifier.Error(T("Listing comments failed: " + exception.Message)); + return Index(new CommentIndexOptions()); + } + } + + [HttpPost] + [FormValueRequired("submit.BulkEdit")] + public ActionResult Details(FormCollection input) { + var viewModel = new CommentsDetailsViewModel { Comments = new List(), Options = new CommentDetailsOptions() }; + UpdateModel(viewModel, input.ToValueProvider()); + + try { + IEnumerable checkedEntries = viewModel.Comments.Where(c => c.IsChecked); + switch (viewModel.Options.BulkAction) { + case CommentDetailsBulkAction.None: + break; + case CommentDetailsBulkAction.MarkAsSpam: + if (!_authorizer.Authorize(Permissions.ModerateComment, T("Couldn't moderate comment"))) + return new HttpUnauthorizedResult(); + //TODO: Transaction + foreach (CommentEntry entry in checkedEntries) { + _commentService.MarkCommentAsSpam(entry.Comment.Id); + } + break; + case CommentDetailsBulkAction.Delete: + if (!_authorizer.Authorize(Permissions.ModerateComment, T("Couldn't delete comment"))) + return new HttpUnauthorizedResult(); + + foreach (CommentEntry entry in checkedEntries) { + _commentService.DeleteComment(entry.Comment.Id); + } + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + catch (Exception exception) { + _notifier.Error(T("Editing comments failed: " + exception.Message)); + return Details(viewModel.CommentedItemId, viewModel.Options); + } + + return RedirectToAction("Details", new { viewModel.CommentedItemId, viewModel.Options }); + } + + public ActionResult Close(int commentedItemId) { + try { + if (!_authorizer.Authorize(Permissions.CloseComment, T("Couldn't close comments"))) + return new HttpUnauthorizedResult(); + _commentService.CloseCommentsForCommentedContent(commentedItemId); + return RedirectToAction("Index"); + } + catch (Exception exception) { + _notifier.Error(T("Closing Comments failed: " + exception.Message)); + return RedirectToAction("Index"); + } + } + + public ActionResult Edit(int id) { + try { + Comment comment = _commentService.GetComment(id); + var viewModel = new CommentsEditViewModel { + CommentText = comment.CommentText, + Email = comment.Email, + Id = comment.Id, + Name = comment.Author, + SiteName = comment.SiteName + }; + return View(viewModel); + + } + catch (Exception exception) { + _notifier.Error(T("Editing comment failed: " + exception.Message)); + return Index(new CommentIndexOptions()); + } + } + + [AcceptVerbs(HttpVerbs.Post)] + public ActionResult Edit(FormCollection input) { + var viewModel = new CommentsEditViewModel(); + try { + UpdateModel(viewModel, input.ToValueProvider()); + if (!_authorizer.Authorize(Permissions.ModerateComment, T("Couldn't edit comment"))) + return new HttpUnauthorizedResult(); + + _commentService.UpdateComment(viewModel.Id, viewModel.Name, viewModel.Email, viewModel.SiteName, viewModel.CommentText); + return RedirectToAction("Index"); + } + catch (Exception exception) { + _notifier.Error(T("Editing Comment failed: " + exception.Message)); + return View(viewModel); + } + } + private CommentEntry CreateCommentEntry(Comment comment) { return new CommentEntry { Comment = comment, diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs b/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs index e4100864d..99ecb15af 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs +++ b/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs @@ -13,6 +13,11 @@ namespace Orchard.Comments.Models { public virtual int CommentedOn { get; set; } } + public class ClosedComments { + public virtual int Id { get; set; } + public virtual int ContentItemId { get; set; } + } + public enum CommentStatus { Approved, Spam diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Models/CommentsHandler.cs b/src/Orchard.Web/Packages/Orchard.Comments/Models/CommentsHandler.cs index ffb2be70e..07afc05e6 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Models/CommentsHandler.cs +++ b/src/Orchard.Web/Packages/Orchard.Comments/Models/CommentsHandler.cs @@ -12,13 +12,16 @@ namespace Orchard.Comments.Models { } public IEnumerable Comments { get; set; } + public bool Closed { get; set; } } public class HasCommentsProvider : ContentProvider { private readonly IRepository _commentsRepository; + private readonly IRepository _closedCommentsRepository; - public HasCommentsProvider(IRepository commentsRepository) { + public HasCommentsProvider(IRepository commentsRepository, IRepository closedCommentsRepository) { _commentsRepository = commentsRepository; + _closedCommentsRepository = closedCommentsRepository; Filters.Add(new ActivatingFilter("wikipage")); } @@ -35,7 +38,10 @@ namespace Orchard.Comments.Models { } HasComments comments = context.ContentItem.Get(); - comments.Comments = _commentsRepository.Fetch(x => x.CommentedOn == context.ContentItem.Id); + comments.Comments = _commentsRepository.Fetch(x => x.CommentedOn == context.ContentItem.Id && x.Status == CommentStatus.Approved); + if (_closedCommentsRepository.Get(x => x.ContentItemId == context.ContentItem.Id) != null) { + comments.Closed = true; + } } } } diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj b/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj index 3abd4db21..bb3194cfd 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj +++ b/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj @@ -75,10 +75,14 @@ + + + + diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs b/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs index eca0bdff1..d8c7fbc24 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs +++ b/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs @@ -12,26 +12,33 @@ namespace Orchard.Comments.Services { public interface ICommentService : IDependency { IEnumerable GetComments(); IEnumerable GetComments(CommentStatus status); + IEnumerable GetCommentsForCommentedContent(int id); + IEnumerable GetCommentsForCommentedContent(int id, CommentStatus status); Comment GetComment(int id); IContentDisplayInfo GetDisplayForCommentedContent(int id); void CreateComment(Comment comment); + void UpdateComment(int id, string name, string email, string siteName, string commentText); void MarkCommentAsSpam(int commentId); void DeleteComment(int commentId); + void CloseCommentsForCommentedContent(int id); } public class CommentService : ICommentService { private readonly IRepository _commentRepository; + private readonly IRepository _closedCommentsRepository; private readonly ICommentValidator _commentValidator; private readonly IContentManager _contentManager; private readonly IAuthorizer _authorizer; private readonly INotifier _notifier; public CommentService(IRepository commentRepository, + IRepository closedCommentsRepository, ICommentValidator commentValidator, IContentManager contentManager, IAuthorizer authorizer, INotifier notifier) { _commentRepository = commentRepository; + _closedCommentsRepository = closedCommentsRepository; _commentValidator = commentValidator; _contentManager = contentManager; _authorizer = authorizer; @@ -52,6 +59,14 @@ namespace Orchard.Comments.Services { return from comment in _commentRepository.Table.ToList() where comment.Status == status select comment; } + public IEnumerable GetCommentsForCommentedContent(int id) { + return from comment in _commentRepository.Table.ToList() where comment.CommentedOn == id select comment; + } + + public IEnumerable GetCommentsForCommentedContent(int id, CommentStatus status) { + return from comment in _commentRepository.Table.ToList() where comment.CommentedOn == id && comment.Status == status select comment; + } + public Comment GetComment(int id) { return _commentRepository.Get(id); } @@ -65,6 +80,14 @@ namespace Orchard.Comments.Services { _commentRepository.Create(comment); } + public void UpdateComment(int id, string name, string email, string siteName, string commentText) { + Comment comment = GetComment(id); + comment.Author = name; + comment.Email = email; + comment.SiteName = siteName; + comment.CommentText = commentText; + } + public void MarkCommentAsSpam(int commentId) { Comment comment = GetComment(commentId); comment.Status = CommentStatus.Spam; @@ -74,6 +97,10 @@ namespace Orchard.Comments.Services { _commentRepository.Delete(GetComment(commentId)); } + public void CloseCommentsForCommentedContent(int id) { + _closedCommentsRepository.Create(new ClosedComments { ContentItemId = id }); + } + #endregion } } diff --git a/src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsDetailsViewModel.cs b/src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsDetailsViewModel.cs new file mode 100644 index 000000000..43545bea0 --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsDetailsViewModel.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Comments.ViewModels { + public class CommentsDetailsViewModel : AdminViewModel { + public IList Comments { get; set; } + public CommentDetailsOptions Options { get; set; } + public string DisplayNameForCommentedItem { get; set; } + public int CommentedItemId { get; set; } + } + + public class CommentDetailsOptions { + public CommentDetailsFilter Filter { get; set; } + public CommentDetailsBulkAction BulkAction { get; set; } + } + + public enum CommentDetailsBulkAction { + None, + Delete, + MarkAsSpam, + } + + public enum CommentDetailsFilter { + All, + Approved, + Spam, + } +} diff --git a/src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsEditViewModel.cs b/src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsEditViewModel.cs new file mode 100644 index 000000000..3a7698f65 --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Comments/ViewModels/CommentsEditViewModel.cs @@ -0,0 +1,11 @@ +using Orchard.Mvc.ViewModels; + +namespace Orchard.Comments.ViewModels { + public class CommentsEditViewModel : AdminViewModel { + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public string SiteName { get; set; } + public string CommentText { get; set; } + } +} diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Details.aspx b/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Details.aspx new file mode 100644 index 000000000..69cc1194b --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Details.aspx @@ -0,0 +1,89 @@ +<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %> +<%@ Import Namespace="Orchard.Comments.Models"%> +<%@ Import Namespace="Orchard.Comments.ViewModels"%> +<%@ Import Namespace="Orchard.Mvc.Html"%> +<% Html.Include("Header"); %> + <% Html.BeginForm(); %> +
+

Comments for <%= Model.DisplayNameForCommentedItem %>

+ <%=Html.ValidationSummary() %> +
    +
  1. + + +
  2. +
  3. + +
  4. +
+
    +
  1. + + +
  2. +
  3. + +
  4. +
+ + + + + + + + + + + + + + + + + + + + <% + int commentIndex = 0; + foreach (var commentEntry in Model.Comments) { + %> + + + + + + + + + <% + commentIndex++; + } %> +
<%----%>StatusAuthorCommentDate
+ + + + + <% if (commentEntry.Comment.Status == CommentStatus.Spam) {%> Spam <% } %> + <% else {%> Approved <% } %> + <%= commentEntry.Comment.UserName %> + <% if (commentEntry.Comment.CommentText != null) {%> + <%= commentEntry.Comment.CommentText.Length > 23 ? commentEntry.Comment.CommentText.Substring(0, 24) : commentEntry.Comment.CommentText %> ... + <% } %> + <%= commentEntry.Comment.CommentDate %><%=Html.ActionLink("Edit", "Edit", new {commentEntry.Comment.Id}, new {@class="floatRight topSpacer"}) %> +
+
  • + <%=Html.ActionLink("Close Comments", "Close", new {commentedItemId = Model.CommentedItemId}, new {@class="floatRight topSpacer"}) %> +
  • +
    + + <% Html.EndForm(); %> +<% Html.Include("Footer"); %> \ No newline at end of file diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Edit.aspx b/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Edit.aspx new file mode 100644 index 000000000..a9c60fc8a --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Edit.aspx @@ -0,0 +1,36 @@ +<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %> +<%@ Import Namespace="Orchard.Comments.ViewModels"%> +<%@ Import Namespace="Orchard.Mvc.Html" %> +<% Html.Include("Header"); %> + <% Html.BeginForm(); %> + <%= Html.ValidationSummary() %> +
    +

    Add a Comment

    +

    Information

    +
      +
    1. + + + +
    2. +
    3. + + +
    4. +
    5. + + +
    6. +
    7. + + +
    8. +
    9. + +
    10. +
    +
    + <% Html.EndForm(); %> +<% Html.Include("Footer"); %> \ No newline at end of file diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Index.aspx b/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Index.aspx index ab93c86bc..1e26c2f34 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Index.aspx +++ b/src/Orchard.Web/Packages/Orchard.Comments/Views/Admin/Index.aspx @@ -67,11 +67,11 @@ <%= commentEntry.Comment.UserName %> <% if (commentEntry.Comment.CommentText != null) {%> - <%= commentEntry.Comment.CommentText.Length > 23 ? commentEntry.Comment.CommentText.Substring(0, 24) : commentEntry.Comment.CommentText %> + <%= commentEntry.Comment.CommentText.Length > 23 ? commentEntry.Comment.CommentText.Substring(0, 24) : commentEntry.Comment.CommentText %> ... <% } %> <%= commentEntry.Comment.CommentDate %> - <%=Html.ActionLink(commentEntry.CommentedOn, "Details", new {}, new {@class="floatRight topSpacer"}) %> + <%=Html.ActionLink(commentEntry.CommentedOn, "Details", new {id = commentEntry.Comment.CommentedOn}, new {@class="floatRight topSpacer"}) %> <% commentIndex++; diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Views/Models/DisplayTemplates/HasComments.ascx b/src/Orchard.Web/Packages/Orchard.Comments/Views/Models/DisplayTemplates/HasComments.ascx index ff1b1d4dd..8de5307ab 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Views/Models/DisplayTemplates/HasComments.ascx +++ b/src/Orchard.Web/Packages/Orchard.Comments/Views/Models/DisplayTemplates/HasComments.ascx @@ -12,6 +12,10 @@
    <% } %> +<% if (Model.Closed) { %> +

    Comments have been disabled for this content.

    +<% } %> +<% else { %> <% Html.BeginForm("Create", "Admin", new { area = "Orchard.Comments" }); %> <%= Html.ValidationSummary() %>
    @@ -43,3 +47,4 @@
    <% Html.EndForm(); %> +<% } %> \ No newline at end of file