Make "Comment" a content type and enable comment count at blog container level

* Fix bug where closing comments multiple times would result in inconsistent database.
* Rename a few classes/fields in Comments module to follow coding convention (Record suffix, Utc suffix for dates)
* Move classes to their own file
* Create a HasCommentContainer handler to enable displaying comment counts (approved/pending) for container (i.e. blog)

--HG--
extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4045899
This commit is contained in:
rpaquay
2010-01-23 23:21:08 +00:00
parent 9eb3ae81f2
commit 308f4f089b
31 changed files with 262 additions and 148 deletions

View File

@@ -3,5 +3,5 @@
<%@ Import Namespace="Orchard.Blogs.Extensions"%> <%@ Import Namespace="Orchard.Blogs.Extensions"%>
<%@ Import Namespace="Orchard.Blogs.Models"%> <%@ Import Namespace="Orchard.Blogs.Models"%>
<h2><%=Html.Link(Html.Encode(Model.Item.Name), Url.Blog(Model.Item.Slug)) %></h2> <h2><%=Html.Link(Html.Encode(Model.Item.Name), Url.Blog(Model.Item.Slug)) %></h2>
<div class="blog metadata"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%=_Encoded("{0} post{1}", Model.Item.PostCount, Model.Item.PostCount == 1 ? "" : "s")%></a></div> <div class="blog metadata"><a href="<%=Url.Blog(Model.Item.Slug) %>"><%Html.Zone("meta");%><%--<%=_Encoded("{0} post{1}", Model.Item.PostCount, Model.Item.PostCount == 1 ? "" : "s")%>--%></a></div>
<p><%=Html.Encode(Model.Item.Description) %></p> <p><%=Html.Encode(Model.Item.Description) %></p>

View File

@@ -18,11 +18,13 @@ namespace Orchard.Comments.Controllers {
[ValidateInput(false)] [ValidateInput(false)]
public class AdminController : Controller { public class AdminController : Controller {
private readonly ICommentService _commentService; private readonly ICommentService _commentService;
private readonly IContentManager _contentManager;
private readonly IAuthorizer _authorizer; private readonly IAuthorizer _authorizer;
private readonly INotifier _notifier; private readonly INotifier _notifier;
public AdminController(ICommentService commentService, INotifier notifier, IAuthorizer authorizer) { public AdminController(ICommentService commentService, IContentManager contentManager, INotifier notifier, IAuthorizer authorizer) {
_commentService = commentService; _commentService = commentService;
_contentManager = contentManager;
_authorizer = authorizer; _authorizer = authorizer;
_notifier = notifier; _notifier = notifier;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
@@ -59,7 +61,7 @@ namespace Orchard.Comments.Controllers {
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
var entries = comments.Select(comment => CreateCommentEntry(comment)).ToList(); var entries = comments.Select(comment => CreateCommentEntry(comment.Record)).ToList();
var model = new CommentsIndexViewModel {Comments = entries, Options = options}; var model = new CommentsIndexViewModel {Comments = entries, Options = options};
return View(model); return View(model);
} }
@@ -138,16 +140,19 @@ namespace Orchard.Comments.Controllers {
if (!_authorizer.Authorize(Permissions.AddComment, T("Couldn't add comment"))) if (!_authorizer.Authorize(Permissions.AddComment, T("Couldn't add comment")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
} }
Comment comment = new Comment {
CommentRecord commentRecord = new CommentRecord {
Author = viewModel.Name, Author = viewModel.Name,
CommentDate = DateTime.UtcNow, CommentDateUtc = DateTime.UtcNow,
CommentText = viewModel.CommentText, CommentText = viewModel.CommentText,
Email = viewModel.Email, Email = viewModel.Email,
SiteName = viewModel.SiteName, SiteName = viewModel.SiteName,
UserName = CurrentUser == null ? "Anonymous" : CurrentUser.UserName, UserName = CurrentUser == null ? "Anonymous" : CurrentUser.UserName,
CommentedOn = viewModel.CommentedOn, CommentedOn = viewModel.CommentedOn,
}; };
_commentService.CreateComment(comment);
var comment = _commentService.CreateComment(commentRecord);
if (!String.IsNullOrEmpty(returnUrl)) { if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl); return Redirect(returnUrl);
} }
@@ -183,7 +188,7 @@ namespace Orchard.Comments.Controllers {
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
var entries = comments.Select(comment => CreateCommentEntry(comment)).ToList(); var entries = comments.Select(comment => CreateCommentEntry(comment.Record)).ToList();
var model = new CommentsDetailsViewModel { var model = new CommentsDetailsViewModel {
Comments = entries, Comments = entries,
Options = options, Options = options,
@@ -299,12 +304,12 @@ namespace Orchard.Comments.Controllers {
try { try {
Comment comment = _commentService.GetComment(id); Comment comment = _commentService.GetComment(id);
var viewModel = new CommentsEditViewModel { var viewModel = new CommentsEditViewModel {
CommentText = comment.CommentText, CommentText = comment.Record.CommentText,
Email = comment.Email, Email = comment.Record.Email,
Id = comment.Id, Id = comment.Record.Id,
Name = comment.Author, Name = comment.Record.Author,
SiteName = comment.SiteName, SiteName = comment.Record.SiteName,
Status = comment.Status, Status = comment.Record.Status,
}; };
return View(viewModel); return View(viewModel);
@@ -337,8 +342,10 @@ namespace Orchard.Comments.Controllers {
try { try {
if (!_authorizer.Authorize(Permissions.ManageComments, T("Couldn't delete comment"))) if (!_authorizer.Authorize(Permissions.ManageComments, T("Couldn't delete comment")))
return new HttpUnauthorizedResult(); return new HttpUnauthorizedResult();
int commentedOn = _commentService.GetComment(id).CommentedOn;
int commentedOn = _commentService.GetComment(id).Record.CommentedOn;
_commentService.DeleteComment(id); _commentService.DeleteComment(id);
if (!String.IsNullOrEmpty(returnUrl)) { if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl); return Redirect(returnUrl);
} }
@@ -353,7 +360,7 @@ namespace Orchard.Comments.Controllers {
} }
} }
private CommentEntry CreateCommentEntry(Comment comment) { private CommentEntry CreateCommentEntry(CommentRecord comment) {
return new CommentEntry { return new CommentEntry {
Comment = comment, Comment = comment,
CommentedOn = _commentService.GetDisplayForCommentedContent(comment.CommentedOn).DisplayText, CommentedOn = _commentService.GetDisplayForCommentedContent(comment.CommentedOn).DisplayText,

View File

@@ -2,7 +2,6 @@ using System.Web.Mvc;
using System.Web.Mvc.Html; using System.Web.Mvc.Html;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Mvc.Html;
namespace Orchard.Comments.Extensions { namespace Orchard.Comments.Extensions {
public static class HtmlHelperExtensions { public static class HtmlHelperExtensions {

View File

@@ -24,7 +24,7 @@ namespace Orchard.Comments.Feeds {
Localizer T { get; set; } Localizer T { get; set; }
public void Populate(FeedContext context) { public void Populate(FeedContext context) {
foreach (var feedItem in context.Response.Items.OfType<FeedItem<Comment>>()) { foreach (var feedItem in context.Response.Items.OfType<FeedItem<CommentRecord>>()) {
var comment = feedItem.Item; var comment = feedItem.Item;
var commentedOn = _contentManager.Get(feedItem.Item.CommentedOn); var commentedOn = _contentManager.Get(feedItem.Item.CommentedOn);
var commentedOnInspector = new ItemInspector( var commentedOnInspector = new ItemInspector(
@@ -47,7 +47,7 @@ namespace Orchard.Comments.Feeds {
feedItem.Element.SetElementValue("title", title); feedItem.Element.SetElementValue("title", title);
feedItem.Element.Add(link); feedItem.Element.Add(link);
feedItem.Element.SetElementValue("description", comment.CommentText); feedItem.Element.SetElementValue("description", comment.CommentText);
feedItem.Element.SetElementValue("pubDate", comment.CommentDate);//TODO: format feedItem.Element.SetElementValue("pubDate", comment.CommentDateUtc);//TODO: format
feedItem.Element.Add(guid); feedItem.Element.Add(guid);
} }
else { else {
@@ -59,7 +59,7 @@ namespace Orchard.Comments.Feeds {
context.Builder.AddProperty(context, feedItem, "title", title.ToString()); context.Builder.AddProperty(context, feedItem, "title", title.ToString());
context.Builder.AddProperty(context, feedItem, "description", comment.CommentText); context.Builder.AddProperty(context, feedItem, "description", comment.CommentText);
context.Builder.AddProperty(context, feedItem, "published-date", Convert.ToString(comment.CommentDate)); // format? cvt to generic T? context.Builder.AddProperty(context, feedItem, "published-date", Convert.ToString(comment.CommentDateUtc)); // format? cvt to generic T?
} }
} }
} }

View File

@@ -1,5 +1,6 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Orchard.Comments.Models; using Orchard.Comments.Models;
using Orchard.ContentManagement;
using Orchard.Core.Feeds; using Orchard.Core.Feeds;
using Orchard.Core.Feeds.Models; using Orchard.Core.Feeds.Models;
using Orchard.Data; using Orchard.Data;
@@ -7,11 +8,10 @@ using Orchard.Data;
namespace Orchard.Comments.Feeds { namespace Orchard.Comments.Feeds {
[UsedImplicitly] [UsedImplicitly]
public class CommentedOnContainerFeedQuery : IFeedQueryProvider, IFeedQuery { public class CommentedOnContainerFeedQuery : IFeedQueryProvider, IFeedQuery {
private readonly IRepository<Comment> _commentRepository; private readonly IContentManager _contentManager;
public CommentedOnContainerFeedQuery( public CommentedOnContainerFeedQuery(IContentManager contentManager) {
IRepository<Comment> commentRepository) { _contentManager = contentManager;
_commentRepository = commentRepository;
} }
public FeedQueryMatch Match(FeedContext context) { public FeedQueryMatch Match(FeedContext context) {
@@ -29,10 +29,11 @@ namespace Orchard.Comments.Feeds {
if (limitValue != null) if (limitValue != null)
limit = (int)limitValue.ConvertTo(typeof(int)); limit = (int)limitValue.ConvertTo(typeof(int));
var comments = _commentRepository.Fetch( var comments = _contentManager
x => x.CommentedOnContainer == commentedOnContainer && x.Status == CommentStatus.Approved, .Query<Comment, CommentRecord>()
o => o.Desc(x => x.CommentDate), .Where(x => x.CommentedOnContainer == commentedOnContainer && x.Status == CommentStatus.Approved)
0, limit); .OrderByDescending(x => x.CommentDateUtc)
.Slice(0, limit);
foreach (var comment in comments) { foreach (var comment in comments) {
context.Builder.AddItem(context, comment); context.Builder.AddItem(context, comment);

View File

@@ -7,10 +7,10 @@ using Orchard.Data;
namespace Orchard.Comments.Feeds { namespace Orchard.Comments.Feeds {
[UsedImplicitly] [UsedImplicitly]
public class CommentedOnFeedQuery : IFeedQueryProvider, IFeedQuery { public class CommentedOnFeedQuery : IFeedQueryProvider, IFeedQuery {
private readonly IRepository<Comment> _commentRepository; private readonly IRepository<CommentRecord> _commentRepository;
public CommentedOnFeedQuery( public CommentedOnFeedQuery(
IRepository<Comment> commentRepository) { IRepository<CommentRecord> commentRepository) {
_commentRepository = commentRepository; _commentRepository = commentRepository;
} }
@@ -31,7 +31,7 @@ namespace Orchard.Comments.Feeds {
var comments = _commentRepository.Fetch( var comments = _commentRepository.Fetch(
x => x.CommentedOn == commentedOn && x.Status == CommentStatus.Approved, x => x.CommentedOn == commentedOn && x.Status == CommentStatus.Approved,
o => o.Desc(x => x.CommentDate), o => o.Desc(x => x.CommentDateUtc),
0, limit); 0, limit);
foreach (var comment in comments) { foreach (var comment in comments) {

View File

@@ -0,0 +1,6 @@
namespace Orchard.Comments.Models {
public class ClosedCommentsRecord {
public virtual int Id { get; set; }
public virtual int ContentItemId { get; set; }
}
}

View File

@@ -1,27 +1,6 @@
using System; using Orchard.ContentManagement;
namespace Orchard.Comments.Models { namespace Orchard.Comments.Models {
public class Comment { public class Comment : ContentPart<CommentRecord> {
public virtual int Id { get; set; }
public virtual string Author { get; set; }
public virtual string SiteName { get; set; }
public virtual string UserName { get; set; }
public virtual string Email { get; set; }
public virtual CommentStatus Status { get; set; }
public virtual DateTime CommentDate { get; set; }
public virtual string CommentText { get; set; }
public virtual int CommentedOn { get; set; }
public virtual int CommentedOnContainer { get; set; }
} }
}
public class ClosedComments {
public virtual int Id { get; set; }
public virtual int ContentItemId { get; set; }
}
public enum CommentStatus {
Pending,
Approved,
Spam
}
}

View File

@@ -0,0 +1,19 @@
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
namespace Orchard.Comments.Models {
[UsedImplicitly]
public class CommentDriver : ContentItemDriver<Comment> {
public readonly static ContentType ContentType = new ContentType {
Name = "comment",
DisplayName = "Comment"
};
protected override ContentType GetContentType() {
return ContentType;
}
protected override string Prefix { get { return ""; } }
}
}

View File

@@ -0,0 +1,15 @@
using JetBrains.Annotations;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Common.Models;
using Orchard.Data;
namespace Orchard.Comments.Models {
[UsedImplicitly]
public class CommentHandler : ContentHandler {
public CommentHandler(IRepository<CommentRecord> commentsRepository) {
Filters.Add(new ActivatingFilter<Comment>(CommentDriver.ContentType.Name));
Filters.Add(new ActivatingFilter<CommonAspect>(CommentDriver.ContentType.Name));
Filters.Add(new StorageFilter<CommentRecord>(commentsRepository));
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using Orchard.ContentManagement.Records;
namespace Orchard.Comments.Models {
public class CommentRecord : ContentPartRecord {
public virtual string Author { get; set; }
public virtual string SiteName { get; set; }
public virtual string UserName { get; set; }
public virtual string Email { get; set; }
public virtual CommentStatus Status { get; set; }
public virtual DateTime CommentDateUtc { get; set; }
public virtual string CommentText { get; set; }
public virtual int CommentedOn { get; set; }
public virtual int CommentedOnContainer { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace Orchard.Comments.Models {
public enum CommentStatus {
Pending,
Approved,
Spam
}
}

View File

@@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace Orchard.Comments.Models { namespace Orchard.Comments.Models {
public class HasComments : ContentPart<HasCommentsRecord> { public class HasComments : ContentPart<HasCommentsRecord> {
@@ -11,8 +10,6 @@ namespace Orchard.Comments.Models {
PendingComments = new List<Comment>(); PendingComments = new List<Comment>();
} }
public int CommentCount { get { return Comments.Count; } }
public IList<Comment> Comments { get; set; } public IList<Comment> Comments { get; set; }
public IList<Comment> PendingComments { get; set; } public IList<Comment> PendingComments { get; set; }
@@ -26,9 +23,4 @@ namespace Orchard.Comments.Models {
set { Record.CommentsActive = value; } set { Record.CommentsActive = value; }
} }
} }
public class HasCommentsRecord : ContentPartRecord {
public virtual bool CommentsShown { get; set; }
public virtual bool CommentsActive { get; set; }
}
} }

View File

@@ -0,0 +1,6 @@
using Orchard.ContentManagement;
namespace Orchard.Comments.Models {
public class HasCommentsContainer : ContentPart {
}
}

View File

@@ -0,0 +1,34 @@
using System.Linq;
using JetBrains.Annotations;
using Orchard.Comments.ViewModels;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Common.Records;
namespace Orchard.Comments.Models {
[UsedImplicitly]
public class HasCommentsContainerDriver : ContentPartDriver<HasCommentsContainer> {
protected override DriverResult Display(HasCommentsContainer part, string displayType) {
if (displayType == "SummaryAdmin") {
return ContentPartTemplate(CreateViewModel(part.ContentItem), "Parts/Comments.CountAdmin").Location("meta");
}
else if (displayType.Contains("Summary")) {
return ContentPartTemplate(CreateViewModel(part.ContentItem), "Parts/Comments.Count").Location("meta");
}
return null;
}
private CommentCountViewModel CreateViewModel(ContentItem contentItem) {
// Find all contents item with this part as the container
var parts = contentItem.ContentManager.Query()
.Where<CommonRecord>(rec => rec.Container == contentItem.Record).List();
// Count comments and create template
int count = parts.Aggregate(0, (seed, item) => seed + (ContentExtensions.Has<HasComments>(item) ? ContentExtensions.As<HasComments>(item).Comments.Count : 0));
int pendingCount = parts.Aggregate(0, (seed, item) => seed + (item.Has<HasComments>() ? item.As<HasComments>().PendingComments.Count : 0));
return new CommentCountViewModel { Item = contentItem, CommentCount = count, PendingCount = pendingCount};
}
}
}

View File

@@ -0,0 +1,11 @@
using JetBrains.Annotations;
using Orchard.ContentManagement.Handlers;
namespace Orchard.Comments.Models {
[UsedImplicitly]
public class HasCommentsContainerHandler : ContentHandler {
public HasCommentsContainerHandler() {
Filters.Add(new ActivatingFilter<HasCommentsContainer>("blog"));
}
}
}

View File

@@ -42,4 +42,4 @@ namespace Orchard.Comments.Models {
return ContentPartTemplate(part, "Parts/Comments.HasComments").Location("primary", "99"); return ContentPartTemplate(part, "Parts/Comments.HasComments").Location("primary", "99");
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Orchard.Comments.Services; using Orchard.Comments.Services;
using Orchard.ContentManagement;
using Orchard.Data; using Orchard.Data;
using Orchard.ContentManagement.Handlers; using Orchard.ContentManagement.Handlers;
@@ -8,7 +9,7 @@ namespace Orchard.Comments.Models {
[UsedImplicitly] [UsedImplicitly]
public class HasCommentsHandler : ContentHandler { public class HasCommentsHandler : ContentHandler {
public HasCommentsHandler( public HasCommentsHandler(
IRepository<Comment> commentsRepository, IContentManager contentManager,
IRepository<HasCommentsRecord> hasCommentsRepository, IRepository<HasCommentsRecord> hasCommentsRepository,
ICommentService commentService) { ICommentService commentService) {
@@ -22,43 +23,25 @@ namespace Orchard.Comments.Models {
}); });
OnLoading<HasComments>((context, comments) => { OnLoading<HasComments>((context, comments) => {
comments.Comments = commentsRepository.Fetch(x => x.CommentedOn == context.ContentItem.Id && x.Status == CommentStatus.Approved).ToList(); //TODO: lazy loading?
comments.PendingComments = commentsRepository.Fetch(x => x.CommentedOn == context.ContentItem.Id && x.Status == CommentStatus.Pending).ToList(); comments.Comments = contentManager
.Query<Comment, CommentRecord>()
.Where(x => x.CommentedOn == context.ContentItem.Id && x.Status == CommentStatus.Approved)
.List().ToList();
//TODO: lazy loading?
comments.PendingComments = contentManager
.Query<Comment, CommentRecord>()
.Where(x => x.CommentedOn == context.ContentItem.Id && x.Status == CommentStatus.Pending)
.List().ToList();
}); });
OnRemoved<HasComments>( OnRemoved<HasComments>(
(context, c) => (context, c) => {
commentService.GetCommentsForCommentedContent(context.ContentItem.Id).ToList().ForEach( foreach (var comment in commentService.GetCommentsForCommentedContent(context.ContentItem.Id)) {
commentsRepository.Delete)); contentManager.Remove(comment.ContentItem);
}
});
} }
} }
#if false
[UsedImplicitly]
public class HasCommentsContainerHandler : ContentHandler {
public HasCommentsContainerHandler() {
Filters.Add(new ActivatingFilter<HasCommentsContainer>("blog"));
}
}
public class HasCommentsContainer : ContentPart {
}
public class HasCommentsContainerDriver : ContentPartDriver<HasCommentsContainer> {
protected override DriverResult Display(HasCommentsContainer part, string displayType) {
if (displayType.Contains("Summary")) {
// Find all contents item with this part as the container
var parts = part.ContentItem.ContentManager.Query()
.Where<CommonRecord>(rec => rec.Container == part.ContentItem.Record).List();
// Count comments and create template
int count = parts.Aggregate(0, (seed, item) => item.Has<HasComments>() ? item.As<HasComments>().CommentCount : 0);
var model = new CommentCountViewModel { Item = part.ContentItem, CommentCount = count };
return ContentPartTemplate(model, "Parts/Comments.Count").Location("meta");
}
return null;
}
}
#endif
} }

View File

@@ -0,0 +1,8 @@
using Orchard.ContentManagement.Records;
namespace Orchard.Comments.Models {
public class HasCommentsRecord : ContentPartRecord {
public virtual bool CommentsShown { get; set; }
public virtual bool CommentsActive { get; set; }
}
}

View File

@@ -68,11 +68,20 @@
<Compile Include="AdminMenu.cs" /> <Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminController.cs" /> <Compile Include="Controllers\AdminController.cs" />
<Compile Include="Extensions\HtmlHelperExtensions.cs" /> <Compile Include="Extensions\HtmlHelperExtensions.cs" />
<Compile Include="Models\ClosedCommentsRecord.cs" />
<Compile Include="Models\Comment.cs" />
<Compile Include="Models\CommentDriver.cs" />
<Compile Include="Models\CommentHandler.cs" />
<Compile Include="Models\CommentStatus.cs" />
<Compile Include="Models\HasCommentsContainer.cs" />
<Compile Include="Models\HasCommentsContainerDriver.cs" />
<Compile Include="Models\HasCommentsContainerHandler.cs" />
<Compile Include="Models\HasCommentsDriver.cs" /> <Compile Include="Models\HasCommentsDriver.cs" />
<Compile Include="Feeds\CommentedOnContainerFeedQuery.cs" /> <Compile Include="Feeds\CommentedOnContainerFeedQuery.cs" />
<Compile Include="Feeds\CommentedOnFeedQuery.cs" /> <Compile Include="Feeds\CommentedOnFeedQuery.cs" />
<Compile Include="Feeds\CommentFeedItemBuilder.cs" /> <Compile Include="Feeds\CommentFeedItemBuilder.cs" />
<Compile Include="Models\Comment.cs" /> <Compile Include="Models\CommentRecord.cs" />
<Compile Include="Models\HasCommentsRecord.cs" />
<Compile Include="ViewModels\CommentCountViewModel.cs" /> <Compile Include="ViewModels\CommentCountViewModel.cs" />
<Compile Include="Models\CommentSettings.cs" /> <Compile Include="Models\CommentSettings.cs" />
<Compile Include="Models\CommentSettingsHandler.cs" /> <Compile Include="Models\CommentSettingsHandler.cs" />

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Castle.Core;
using JetBrains.Annotations; using JetBrains.Annotations;
using Orchard.Comments.Models; using Orchard.Comments.Models;
using Orchard.ContentManagement.Aspects; using Orchard.ContentManagement.Aspects;
@@ -18,7 +19,7 @@ namespace Orchard.Comments.Services {
IEnumerable<Comment> GetCommentsForCommentedContent(int id, CommentStatus status); IEnumerable<Comment> GetCommentsForCommentedContent(int id, CommentStatus status);
Comment GetComment(int id); Comment GetComment(int id);
ContentItemMetadata GetDisplayForCommentedContent(int id); ContentItemMetadata GetDisplayForCommentedContent(int id);
void CreateComment(Comment comment); Comment CreateComment(CommentRecord commentRecord);
void UpdateComment(int id, string name, string email, string siteName, string commentText, CommentStatus status); void UpdateComment(int id, string name, string email, string siteName, string commentText, CommentStatus status);
void ApproveComment(int commentId); void ApproveComment(int commentId);
void PendComment(int commentId); void PendComment(int commentId);
@@ -29,29 +30,22 @@ namespace Orchard.Comments.Services {
void EnableCommentsForCommentedContent(int id); void EnableCommentsForCommentedContent(int id);
} }
[UsedImplicitly]
public class CommentService : ICommentService { public class CommentService : ICommentService {
private readonly IRepository<Comment> _commentRepository; private readonly IRepository<ClosedCommentsRecord> _closedCommentsRepository;
private readonly IRepository<ClosedComments> _closedCommentsRepository;
private readonly IRepository<HasCommentsRecord> _hasCommentsRepository;
private readonly ICommentValidator _commentValidator; private readonly ICommentValidator _commentValidator;
private readonly IContentManager _contentManager; private readonly IContentManager _contentManager;
private readonly IAuthorizer _authorizer;
private readonly INotifier _notifier;
public CommentService(IRepository<Comment> commentRepository, public CommentService(IRepository<CommentRecord> commentRepository,
IRepository<ClosedComments> closedCommentsRepository, IRepository<ClosedCommentsRecord> closedCommentsRepository,
IRepository<HasCommentsRecord> hasCommentsRepository, IRepository<HasCommentsRecord> hasCommentsRepository,
ICommentValidator commentValidator, ICommentValidator commentValidator,
IContentManager contentManager, IContentManager contentManager,
IAuthorizer authorizer, IAuthorizer authorizer,
INotifier notifier) { INotifier notifier) {
_commentRepository = commentRepository;
_closedCommentsRepository = closedCommentsRepository; _closedCommentsRepository = closedCommentsRepository;
_hasCommentsRepository = hasCommentsRepository;
_commentValidator = commentValidator; _commentValidator = commentValidator;
_contentManager = contentManager; _contentManager = contentManager;
_authorizer = authorizer;
_notifier = notifier;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
} }
@@ -62,23 +56,35 @@ namespace Orchard.Comments.Services {
#region Implementation of ICommentService #region Implementation of ICommentService
public IEnumerable<Comment> GetComments() { public IEnumerable<Comment> GetComments() {
return from comment in _commentRepository.Table.ToList() select comment; return _contentManager
.Query<Comment, CommentRecord>()
.List();
} }
public IEnumerable<Comment> GetComments(CommentStatus status) { public IEnumerable<Comment> GetComments(CommentStatus status) {
return from comment in _commentRepository.Table.ToList() where comment.Status == status select comment; return _contentManager
.Query<Comment, CommentRecord>()
.Where(c => c.Status == status)
.List();
} }
public IEnumerable<Comment> GetCommentsForCommentedContent(int id) { public IEnumerable<Comment> GetCommentsForCommentedContent(int id) {
return from comment in _commentRepository.Table.ToList() where comment.CommentedOn == id select comment; return _contentManager
.Query<Comment, CommentRecord>()
.Where(c => c.CommentedOn == id || c.CommentedOnContainer == id)
.List();
} }
public IEnumerable<Comment> GetCommentsForCommentedContent(int id, CommentStatus status) { public IEnumerable<Comment> GetCommentsForCommentedContent(int id, CommentStatus status) {
return from comment in _commentRepository.Table.ToList() where comment.CommentedOn == id && comment.Status == status select comment; return _contentManager
.Query<Comment, CommentRecord>()
.Where(c => c.CommentedOn == id || c.CommentedOnContainer == id)
.Where(ctx => ctx.Status == status)
.List();
} }
public Comment GetComment(int id) { public Comment GetComment(int id) {
return _commentRepository.Get(id); return _contentManager.Get<Comment>(id);
} }
public ContentItemMetadata GetDisplayForCommentedContent(int id) { public ContentItemMetadata GetDisplayForCommentedContent(int id) {
@@ -88,59 +94,71 @@ namespace Orchard.Comments.Services {
return _contentManager.GetItemMetadata(content); return _contentManager.GetItemMetadata(content);
} }
public void CreateComment(Comment comment) { public Comment CreateComment(CommentRecord commentRecord) {
comment.Status = _commentValidator.ValidateComment(comment) ? CommentStatus.Pending : CommentStatus.Spam; var comment = _contentManager.Create<Comment>("comment");
//TODO:(rpaquay) CommentRecord should never be used as "data object"
comment.Record.Author = commentRecord.Author;
comment.Record.CommentDateUtc = commentRecord.CommentDateUtc;
comment.Record.CommentText = commentRecord.CommentText;
comment.Record.Email = commentRecord.Email;
comment.Record.SiteName = commentRecord.SiteName;
comment.Record.UserName = commentRecord.UserName;
comment.Record.CommentedOn = commentRecord.CommentedOn;
comment.Record.Status = _commentValidator.ValidateComment(commentRecord) ? CommentStatus.Pending : CommentStatus.Spam;
// store id of the next layer for large-grained operations, e.g. rss on blog // store id of the next layer for large-grained operations, e.g. rss on blog
var commentedOn = _contentManager.Get<ICommonAspect>(comment.CommentedOn); //TODO:(rpaquay) Get rid of this (comment aspect takes care of container)
var commentedOn = _contentManager.Get<ICommonAspect>(comment.Record.CommentedOn);
if (commentedOn != null && commentedOn.Container != null) { if (commentedOn != null && commentedOn.Container != null) {
comment.CommentedOnContainer = commentedOn.Container.ContentItem.Id; comment.Record.CommentedOnContainer = commentedOn.Container.ContentItem.Id;
} }
_commentRepository.Create(comment); return comment;
} }
public void UpdateComment(int id, string name, string email, string siteName, string commentText, CommentStatus status) { public void UpdateComment(int id, string name, string email, string siteName, string commentText, CommentStatus status) {
Comment comment = GetComment(id); Comment comment = GetComment(id);
comment.Author = name; comment.Record.Author = name;
comment.Email = email; comment.Record.Email = email;
comment.SiteName = siteName; comment.Record.SiteName = siteName;
comment.CommentText = commentText; comment.Record.CommentText = commentText;
comment.Status = status; comment.Record.Status = status;
} }
public void ApproveComment(int commentId) { public void ApproveComment(int commentId) {
Comment comment = GetComment(commentId); Comment comment = GetComment(commentId);
comment.Status = CommentStatus.Approved; comment.Record.Status = CommentStatus.Approved;
} }
public void PendComment(int commentId) { public void PendComment(int commentId) {
Comment comment = GetComment(commentId); Comment comment = GetComment(commentId);
comment.Status = CommentStatus.Pending; comment.Record.Status = CommentStatus.Pending;
} }
public void MarkCommentAsSpam(int commentId) { public void MarkCommentAsSpam(int commentId) {
Comment comment = GetComment(commentId); Comment comment = GetComment(commentId);
comment.Status = CommentStatus.Spam; comment.Record.Status = CommentStatus.Spam;
} }
public void DeleteComment(int commentId) { public void DeleteComment(int commentId) {
_commentRepository.Delete(GetComment(commentId)); _contentManager.Remove(_contentManager.Get(commentId));
} }
public bool CommentsClosedForCommentedContent(int id) { public bool CommentsClosedForCommentedContent(int id) {
return _closedCommentsRepository.Get(x => x.ContentItemId == id) == null ? false : true; return _closedCommentsRepository.Fetch(x => x.ContentItemId == id).Count() >= 1;
} }
public void CloseCommentsForCommentedContent(int id) { public void CloseCommentsForCommentedContent(int id) {
_closedCommentsRepository.Create(new ClosedComments { ContentItemId = id }); if (CommentsClosedForCommentedContent(id))
return;
_closedCommentsRepository.Create(new ClosedCommentsRecord { ContentItemId = id });
} }
public void EnableCommentsForCommentedContent(int id) { public void EnableCommentsForCommentedContent(int id) {
ClosedComments closedComments = _closedCommentsRepository.Get(x => x.ContentItemId == id); var closedComments = _closedCommentsRepository.Fetch(x => x.ContentItemId == id);
if (closedComments != null) { closedComments.ForEach(c => _closedCommentsRepository.Delete(c));
_closedCommentsRepository.Delete(closedComments);
}
} }
#endregion #endregion

View File

@@ -27,7 +27,7 @@ namespace Orchard.Comments.Services {
#region Implementation of ICommentValidator #region Implementation of ICommentValidator
public bool ValidateComment(Comment comment) { public bool ValidateComment(CommentRecord comment) {
CommentSettingsRecord commentSettingsRecord = CurrentSite.As<CommentSettings>().Record; CommentSettingsRecord commentSettingsRecord = CurrentSite.As<CommentSettings>().Record;
string akismetKey = commentSettingsRecord.AkismetKey; string akismetKey = commentSettingsRecord.AkismetKey;
string akismetUrl = commentSettingsRecord.AkismetUrl; string akismetUrl = commentSettingsRecord.AkismetUrl;

View File

@@ -2,6 +2,6 @@ using Orchard.Comments.Models;
namespace Orchard.Comments.Services { namespace Orchard.Comments.Services {
public interface ICommentValidator : IDependency { public interface ICommentValidator : IDependency {
bool ValidateComment(Comment comment); bool ValidateComment(CommentRecord comment);
} }
} }

View File

@@ -3,6 +3,9 @@ using Orchard.ContentManagement;
namespace Orchard.Comments.ViewModels { namespace Orchard.Comments.ViewModels {
public class CommentCountViewModel { public class CommentCountViewModel {
public CommentCountViewModel() {
}
public CommentCountViewModel(HasComments part) { public CommentCountViewModel(HasComments part) {
Item = part.ContentItem; Item = part.ContentItem;
CommentCount = part.Comments.Count; CommentCount = part.Comments.Count;

View File

@@ -9,7 +9,7 @@ namespace Orchard.Comments.ViewModels {
} }
public class CommentEntry { public class CommentEntry {
public Comment Comment { get; set; } public CommentRecord Comment { get; set; }
public string CommentedOn { get; set; } public string CommentedOn { get; set; }
public bool IsChecked { get; set; } public bool IsChecked { get; set; }
} }

View File

@@ -83,7 +83,7 @@
<%=Html.Encode(commentEntry.Comment.CommentText.Length > 23 ? commentEntry.Comment.CommentText.Substring(0, 24) : commentEntry.Comment.CommentText) %><%=_Encoded(" ...") %> <%=Html.Encode(commentEntry.Comment.CommentText.Length > 23 ? commentEntry.Comment.CommentText.Substring(0, 24) : commentEntry.Comment.CommentText) %><%=_Encoded(" ...") %>
<% } %> <% } %>
</td> </td>
<td><%=commentEntry.Comment.CommentDate.ToLocalTime() %></td> <td><%=commentEntry.Comment.CommentDateUtc.ToLocalTime() %></td>
<td> <td>
<ul class="actions"> <ul class="actions">
<li class="construct"> <li class="construct">

View File

@@ -67,7 +67,7 @@
<%=Html.Encode(commentEntry.Comment.CommentText.Length > 23 ? commentEntry.Comment.CommentText.Substring(0, 24) : commentEntry.Comment.CommentText) %><%=_Encoded(" ...") %> <%=Html.Encode(commentEntry.Comment.CommentText.Length > 23 ? commentEntry.Comment.CommentText.Substring(0, 24) : commentEntry.Comment.CommentText) %><%=_Encoded(" ...") %>
<% } %> <% } %>
</td> </td>
<td><%=commentEntry.Comment.CommentDate.ToLocalTime() %></td> <td><%=commentEntry.Comment.CommentDateUtc.ToLocalTime() %></td>
<td><%=Html.ActionLink(commentEntry.CommentedOn, "Details", new { id = commentEntry.Comment.CommentedOn }) %></td> <td><%=Html.ActionLink(commentEntry.CommentedOn, "Details", new { id = commentEntry.Comment.CommentedOn }) %></td>
<td> <td>
<ul class="actions"> <ul class="actions">

View File

@@ -4,13 +4,13 @@
foreach (var comment in Model) { %> foreach (var comment in Model) { %>
<li> <li>
<div class="comment"> <div class="comment">
<span class="who"><%=Html.LinkOrDefault(Html.Encode(comment.UserName), Html.Encode(comment.SiteName), new { rel = "nofollow" })%></span> <span class="who"><%=Html.LinkOrDefault(Html.Encode(comment.Record.UserName), Html.Encode(comment.Record.SiteName), new { rel = "nofollow" })%></span>
<%-- todo: (heskew) need comment permalink --%> <%-- todo: (heskew) need comment permalink --%>
<span>said <%=Html.Link(Html.DateTimeRelative(comment.CommentDate), "#")%></span> <span>said <%=Html.Link(Html.DateTimeRelative(comment.Record.CommentDateUtc), "#")%></span>
</div> </div>
<div class="text"> <div class="text">
<%-- todo: (heskew) comment text needs processing depending on comment markup style --%> <%-- todo: (heskew) comment text needs processing depending on comment markup style --%>
<p><%=Html.Encode(comment.CommentText) %></p> <p><%=Html.Encode(comment.Record.CommentText)%></p>
</div> </div>
</li><% </li><%
} %> } %>

View File

@@ -5,11 +5,11 @@ foreach (var comment in Model) { %>
<li> <li>
<div class="comment"> <div class="comment">
<p><%=Html.Encode(comment.CommentText) %></p> <p><%=Html.Encode(comment.Record.CommentText) %></p>
</div> </div>
<div class="commentauthor"> <div class="commentauthor">
<span class="who"><%=Html.LinkOrDefault(Html.Encode(comment.UserName), Html.Encode(comment.SiteName), new { rel = "nofollow" })%></span>&nbsp;<span>said <%=Html.Link(Html.DateTimeRelative(comment.CommentDate), "#")%></span> <span class="who"><%=Html.LinkOrDefault(Html.Encode(comment.Record.UserName), Html.Encode(comment.Record.SiteName), new { rel = "nofollow" })%></span>&nbsp;<span>said <%=Html.Link(Html.DateTimeRelative(comment.Record.CommentDateUtc), "#")%></span>
</div> </div>
</li><% </li><%

View File

@@ -2,6 +2,9 @@ using System;
using System.Linq; using System.Linq;
namespace Orchard.ContentManagement.Handlers { namespace Orchard.ContentManagement.Handlers {
/// <summary>
/// Filter reponsible for adding a part to a content item when content items are activated
/// </summary>
public class ActivatingFilter<TPart> : IContentActivatingFilter where TPart : ContentPart, new() { public class ActivatingFilter<TPart> : IContentActivatingFilter where TPart : ContentPart, new() {
private readonly Func<string, bool> _predicate; private readonly Func<string, bool> _predicate;
@@ -18,5 +21,4 @@ namespace Orchard.ContentManagement.Handlers {
context.Builder.Weld<TPart>(); context.Builder.Weld<TPart>();
} }
} }
} }

View File

@@ -20,7 +20,6 @@ namespace Orchard.ContentManagement.Handlers {
_repository = repository; _repository = repository;
} }
protected override void Activated(ActivatedContentContext context, ContentPart<TRecord> instance) { protected override void Activated(ActivatedContentContext context, ContentPart<TRecord> instance) {
instance.Record = new TRecord(); instance.Record = new TRecord();
} }