Wiring up comments module support for feeds... Adding rss urls to headers in some blogs module actions...

--HG--
extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4045678
This commit is contained in:
loudej
2010-01-19 06:29:58 +00:00
parent 55ec31be45
commit 0df2ac484c
21 changed files with 346 additions and 34 deletions

View File

@@ -7,7 +7,7 @@ namespace Orchard.Core.Feeds {
public interface IFeedFormatter {
ActionResult Process(FeedContext context, Action populate);
FeedItem AddItem(FeedContext context, ContentItem contentItem);
FeedItem<TItem> AddItem<TItem>(FeedContext context, TItem contentItem);
void AddProperty(FeedContext context, FeedItem feedItem, string name, string value);
}
}

View File

@@ -0,0 +1,12 @@
using System.Web.Mvc;
using System.Web.Routing;
namespace Orchard.Core.Feeds {
public interface IFeedManager : IDependency {
void Register(string title, string format, RouteValueDictionary values);
MvcHtmlString GetRegisteredLinks(HtmlHelper html);
// Currently implemented in FeedController action... tbd
//ActionResult Execute(string format, IValueProvider values);
}
}

View File

@@ -7,16 +7,12 @@ namespace Orchard.Core.Feeds.Models {
ValueProvider = valueProvider;
Format = format;
Response = new FeedResponse();
FeedData = new Dictionary<string, object>();
}
public IValueProvider ValueProvider { get; set; }
public string Format { get; set; }
public FeedResponse Response { get; set; }
public IFeedFormatter FeedFormatter { get; set; }
public IDictionary<string, object> FeedData { get; set; }
public FeedResponse Response { get; set; }
}
}

View File

@@ -3,7 +3,22 @@ using Orchard.ContentManagement;
namespace Orchard.Core.Feeds.Models {
public class FeedItem {
public ContentItem ContentItem { get; set; }
private object _item;
public object Item { get { return _item; } set { SetItem(value); } }
public XElement Element { get; set; }
protected virtual void SetItem(object item) {
_item = item;
}
}
public class FeedItem<TItem> : FeedItem {
private TItem _item;
public new TItem Item { get { return _item; } set { SetItem(value); } }
protected override void SetItem(object item) {
_item = (TItem) item;
base.SetItem(item);
}
}
}

View File

@@ -7,20 +7,21 @@ namespace Orchard.Core.Feeds.Rss {
public class Routes : IRouteProvider {
public IEnumerable<RouteDescriptor> GetRoutes() {
return new[] {
new RouteDescriptor {Priority =-10,
Route = new Route(
"rss",
new RouteValueDictionary {
{"area", "Feeds"},
{"controller", "Feed"},
{"action", "Index"},
{"format", "rss"},
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "Feeds"}
},
new MvcRouteHandler())
new RouteDescriptor {
Priority = -5,
Route = new Route(
"rss",
new RouteValueDictionary {
{"area", "Feeds"},
{"controller", "Feed"},
{"action", "Index"},
{"format", "rss"},
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "Feeds"}
},
new MvcRouteHandler())
}
};
}

View File

@@ -32,9 +32,9 @@ namespace Orchard.Core.Feeds.Rss {
return new RssResult(new XDocument(rss));
}
public FeedItem AddItem(FeedContext context, ContentItem contentItem) {
var feedItem = new FeedItem {
ContentItem = contentItem,
public FeedItem<TItem> AddItem<TItem>(FeedContext context, TItem item) {
var feedItem = new FeedItem<TItem> {
Item = item,
Element = new XElement("item"),
};
context.Response.Items.Add(feedItem);

View File

@@ -0,0 +1,27 @@
using System;
using System.Web.Mvc;
using Orchard.Mvc.Filters;
using Orchard.Mvc.ViewModels;
namespace Orchard.Core.Feeds.Services {
public class FeedFilter : FilterProvider, IResultFilter {
private readonly IFeedManager _feedManager;
public FeedFilter(IFeedManager feedManager) {
_feedManager = feedManager;
}
public void OnResultExecuting(ResultExecutingContext filterContext) {
var model = filterContext.Controller.ViewData.Model as BaseViewModel;
if (model == null) {
return;
}
model.Zones.AddAction("head:after", html => html.ViewContext.Writer.Write(_feedManager.GetRegisteredLinks(html)));
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using JetBrains.Annotations;
namespace Orchard.Core.Feeds.Services {
[UsedImplicitly]
public class FeedManager : IFeedManager {
private readonly IList<Link> _links = new List<Link>();
class Link {
public string Title { get; set; }
public RouteValueDictionary RouteValues { get; set; }
}
public void Register(string title, string format, RouteValueDictionary values) {
var link = new RouteValueDictionary(values) { { "format", format } };
if (!link.ContainsKey("area")) {
link["area"] = "Feeds";
}
if (!link.ContainsKey("controller")) {
link["controller"] = "Feed";
}
if (!link.ContainsKey("action")) {
link["action"] = "Index";
}
_links.Add(new Link { Title = title, RouteValues = link });
}
public MvcHtmlString GetRegisteredLinks(HtmlHelper html) {
var urlHelper = new UrlHelper(html.ViewContext.RequestContext, html.RouteCollection);
var sb = new StringBuilder();
foreach (var link in _links) {
var linkUrl = urlHelper.RouteUrl(link.RouteValues);
sb.Append(@"<link rel=""alternate"" type=""application/rss+xml""");
if (!string.IsNullOrEmpty(link.Title)) {
sb
.Append(@" title=""")
.Append(html.AttributeEncode(link.Title))
.Append(@"""");
}
sb.Append(@" href=""")
.Append(html.AttributeEncode(linkUrl))
.AppendLine(@""" />");
}
return MvcHtmlString.Create(sb.ToString());
}
//public ActionResult Execute(string format, IValueProvider values) {
//}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;
@@ -16,11 +17,11 @@ namespace Orchard.Core.Feeds.Services {
}
public void Populate(FeedContext context) {
foreach (var feedItem in context.Response.Items) {
// locate parts
foreach (var feedItem in context.Response.Items.OfType<FeedItem<ContentItem>>()) {
var inspector = new ItemInspector(
feedItem.ContentItem,
_contentManager.GetItemMetadata(feedItem.ContentItem));
feedItem.Item,
_contentManager.GetItemMetadata(feedItem.Item));
// TODO: author

View File

@@ -84,7 +84,10 @@
<Compile Include="Common\ViewModels\RoutableEditorViewModel.cs" />
<Compile Include="Common\ViewModels\OwnerEditorViewModel.cs" />
<Compile Include="Feeds\Controllers\FeedController.cs" />
<Compile Include="Feeds\IFeedManager.cs" />
<Compile Include="Feeds\Rss\Routes.cs" />
<Compile Include="Feeds\Services\FeedFilter.cs" />
<Compile Include="Feeds\Services\FeedManager.cs" />
<Compile Include="Feeds\StandardBuilders\ItemInspector.cs" />
<Compile Include="Feeds\StandardQueries\ContainerFeedQuery.cs" />
<Compile Include="Feeds\StandardBuilders\CorePartsFeedItemBuilder.cs" />

View File

@@ -1,21 +1,25 @@
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Blogs.Models;
using Orchard.Blogs.Services;
using Orchard.Blogs.ViewModels;
using Orchard.Data;
using Orchard.Core.Feeds;
using Orchard.Mvc.Results;
using Orchard.Security;
using Orchard.UI.Notify;
namespace Orchard.Blogs.Controllers {
public class BlogController : Controller {
private readonly IOrchardServices _services;
private readonly IBlogService _blogService;
private readonly IFeedManager _feedManager;
public BlogController(IOrchardServices services, IBlogService blogService) {
public BlogController(
IOrchardServices services,
IBlogService blogService,
IFeedManager feedManager) {
_services = services;
_blogService = blogService;
_feedManager = feedManager;
}
public ActionResult List() {
@@ -37,6 +41,8 @@ namespace Orchard.Blogs.Controllers {
Blog = _services.ContentManager.BuildDisplayModel(blog, "Detail")
};
_feedManager.Register(blog);
return View(model);
}
}

View File

@@ -1,9 +1,11 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Blogs.Models;
using Orchard.Blogs.Services;
using Orchard.Blogs.ViewModels;
using Orchard.Core.Feeds;
using Orchard.Localization;
using Orchard.ContentManagement;
using Orchard.Mvc.Results;
@@ -13,11 +15,17 @@ namespace Orchard.Blogs.Controllers {
private readonly IOrchardServices _services;
private readonly IBlogService _blogService;
private readonly IBlogPostService _blogPostService;
private readonly IFeedManager _feedManager;
public BlogPostController(IOrchardServices services, IBlogService blogService, IBlogPostService blogPostService) {
public BlogPostController(
IOrchardServices services,
IBlogService blogService,
IBlogPostService blogPostService,
IFeedManager feedManager) {
_services = services;
_blogService = blogService;
_blogPostService = blogPostService;
_feedManager = feedManager;
T = NullLocalizer.Instance;
}
@@ -46,6 +54,8 @@ namespace Orchard.Blogs.Controllers {
BlogPost = _services.ContentManager.BuildDisplayModel(post, "Detail")
};
_feedManager.Register(blog);
return View(model);
}
@@ -63,6 +73,8 @@ namespace Orchard.Blogs.Controllers {
BlogPosts = _blogPostService.Get(blog, archive).Select(b => _services.ContentManager.BuildDisplayModel(b, "Summary"))
};
_feedManager.Register(blog);
return View(model);
}
}

View File

@@ -0,0 +1,12 @@
using System.Web.Routing;
using Orchard.Blogs.Models;
using Orchard.Core.Feeds;
namespace Orchard.Blogs.Controllers {
public static class FeedExtensions {
public static void Register(this IFeedManager feedManager, Blog blog) {
feedManager.Register(blog.Name, "rss", new RouteValueDictionary { { "containerid", blog.Id } });
feedManager.Register(blog.Name + " - Comments", "rss", new RouteValueDictionary { { "commentedoncontainer", blog.Id } });
}
}
}

View File

@@ -79,6 +79,7 @@
<Compile Include="Controllers\BlogPostAdminController.cs" />
<Compile Include="Controllers\BlogPostController.cs" />
<Compile Include="Controllers\BlogPostDriver.cs" />
<Compile Include="Controllers\FeedExtensions.cs" />
<Compile Include="Extensions\HtmlHelperExtensions.cs" />
<Compile Include="Extensions\UriExtensions.cs" />
<Compile Include="Extensions\UrlHelperExtensions.cs" />

View File

@@ -145,7 +145,7 @@ namespace Orchard.Comments.Controllers {
Email = viewModel.Email,
SiteName = viewModel.SiteName,
UserName = CurrentUser == null ? "Anonymous" : CurrentUser.UserName,
CommentedOn = viewModel.CommentedOn
CommentedOn = viewModel.CommentedOn,
};
_commentService.CreateComment(comment);
if (!String.IsNullOrEmpty(returnUrl)) {

View File

@@ -0,0 +1,68 @@
using System;
using System.Linq;
using System.Web.Mvc;
using System.Xml.Linq;
using Orchard.Comments.Models;
using Orchard.ContentManagement;
using Orchard.Core.Feeds;
using Orchard.Core.Feeds.Models;
using Orchard.Core.Feeds.Services;
using Orchard.Localization;
namespace Orchard.Comments.Feeds {
public class CommentFeedItemBuilder : IFeedItemBuilder {
private readonly IContentManager _contentManager;
public CommentFeedItemBuilder(
IContentManager contentManager) {
_contentManager = contentManager;
T = NullLocalizer.Instance;
}
Localizer T { get; set; }
public void Populate(FeedContext context) {
foreach (var feedItem in context.Response.Items.OfType<FeedItem<Comment>>()) {
var comment = feedItem.Item;
var commentedOn = _contentManager.Get(feedItem.Item.CommentedOn);
var commentedOnInspector = new ItemInspector(
commentedOn,
_contentManager.GetItemMetadata(commentedOn));
var title = T("Comment on {0} by {1}", commentedOnInspector.Title, comment.Author);
//var inspector = new CommentInspector(
// feedItem.Item,
// _contentManager.GetItemMetadata(feedItem.Item));
// add to known formats
if (context.Format == "rss") {
var link = new XElement("link");
var guid = new XElement("guid", new XAttribute("isPermaLink", "false"));
context.Response.Contextualize(requestContext => {
var urlHelper = new UrlHelper(requestContext);
link.Add(urlHelper.RouteUrl(commentedOnInspector.Link) + "#comment-" + comment.Id);
guid.Add("urn:comment:" + comment.Id);
});
feedItem.Element.SetElementValue("title", title);
feedItem.Element.Add(link);
feedItem.Element.SetElementValue("description", comment.CommentText);
feedItem.Element.SetElementValue("pubDate", comment.CommentDate);//TODO: format
feedItem.Element.Add(guid);
}
else {
var feedItem1 = feedItem;
context.Response.Contextualize(requestContext => {
var urlHelper = new UrlHelper(requestContext);
context.FeedFormatter.AddProperty(context, feedItem1, "published-date", urlHelper.RouteUrl(commentedOnInspector.Link));
});
context.FeedFormatter.AddProperty(context, feedItem, "title", title.ToString());
context.FeedFormatter.AddProperty(context, feedItem, "description", comment.CommentText);
context.FeedFormatter.AddProperty(context, feedItem, "published-date", Convert.ToString(comment.CommentDate)); // format? cvt to generic T?
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
using JetBrains.Annotations;
using Orchard.Comments.Models;
using Orchard.Core.Feeds;
using Orchard.Core.Feeds.Models;
using Orchard.Data;
namespace Orchard.Comments.Feeds {
[UsedImplicitly]
public class CommentedOnContainerFeedQuery : IFeedQueryProvider, IFeedQuery {
private readonly IRepository<Comment> _commentRepository;
public CommentedOnContainerFeedQuery(
IRepository<Comment> commentRepository) {
_commentRepository = commentRepository;
}
public FeedQueryMatch Match(FeedContext context) {
if (context.ValueProvider.ContainsPrefix("commentedoncontainer")) {
return new FeedQueryMatch { Priority = -1, FeedQuery = this };
}
return null;
}
public void Execute(FeedContext context) {
var commentedOnContainer = (int)context.ValueProvider.GetValue("commentedoncontainer").ConvertTo(typeof(int));
var limit = 20;
var limitValue = context.ValueProvider.GetValue("limit");
if (limitValue != null)
limit = (int)limitValue.ConvertTo(typeof(int));
var comments = _commentRepository.Fetch(
x => x.CommentedOnContainer == commentedOnContainer && x.Status == CommentStatus.Approved,
o => o.Desc(x => x.CommentDate),
0, limit);
foreach (var comment in comments) {
context.FeedFormatter.AddItem(context, comment);
}
}
}
}

View File

@@ -0,0 +1,42 @@
using JetBrains.Annotations;
using Orchard.Comments.Models;
using Orchard.Core.Feeds;
using Orchard.Core.Feeds.Models;
using Orchard.Data;
namespace Orchard.Comments.Feeds {
[UsedImplicitly]
public class CommentedOnFeedQuery : IFeedQueryProvider, IFeedQuery {
private readonly IRepository<Comment> _commentRepository;
public CommentedOnFeedQuery(
IRepository<Comment> commentRepository) {
_commentRepository = commentRepository;
}
public FeedQueryMatch Match(FeedContext context) {
if (context.ValueProvider.ContainsPrefix("commentedon")) {
return new FeedQueryMatch { Priority = -1, FeedQuery = this };
}
return null;
}
public void Execute(FeedContext context) {
var commentedOn = (int)context.ValueProvider.GetValue("commentedon").ConvertTo(typeof(int));
var limit = 20;
var limitValue = context.ValueProvider.GetValue("limit");
if (limitValue != null)
limit = (int)limitValue.ConvertTo(typeof(int));
var comments = _commentRepository.Fetch(
x => x.CommentedOn == commentedOn && x.Status == CommentStatus.Approved,
o => o.Desc(x => x.CommentDate),
0, limit);
foreach (var comment in comments) {
context.FeedFormatter.AddItem(context, comment);
}
}
}
}

View File

@@ -11,6 +11,7 @@ namespace Orchard.Comments.Models {
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 {

View File

@@ -68,6 +68,9 @@
<Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\HasCommentsDriver.cs" />
<Compile Include="Feeds\CommentedOnContainerFeedQuery.cs" />
<Compile Include="Feeds\CommentedOnFeedQuery.cs" />
<Compile Include="Feeds\CommentFeedItemBuilder.cs" />
<Compile Include="Models\Comment.cs" />
<Compile Include="Models\CommentSettings.cs" />
<Compile Include="Models\CommentSettingsHandler.cs" />
@@ -103,6 +106,10 @@
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />

View File

@@ -2,6 +2,7 @@
using System.Linq;
using JetBrains.Annotations;
using Orchard.Comments.Models;
using Orchard.ContentManagement.Aspects;
using Orchard.Data;
using Orchard.Logging;
using Orchard.ContentManagement;
@@ -89,6 +90,13 @@ namespace Orchard.Comments.Services {
public void CreateComment(Comment comment) {
comment.Status = _commentValidator.ValidateComment(comment) ? CommentStatus.Pending : CommentStatus.Spam;
// store id of the next layer for large-grained operations, e.g. rss on blog
var commentedOn = _contentManager.Get<ICommonAspect>(comment.CommentedOn);
if (commentedOn != null && commentedOn.Container != null) {
comment.CommentedOnContainer = commentedOn.Container.ContentItem.Id;
}
_commentRepository.Create(comment);
}