diff --git a/src/Orchard.Web/Core/Feeds/IFeedFormatter.cs b/src/Orchard.Web/Core/Feeds/IFeedFormatter.cs index 4aaf0d0c4..860be1122 100644 --- a/src/Orchard.Web/Core/Feeds/IFeedFormatter.cs +++ b/src/Orchard.Web/Core/Feeds/IFeedFormatter.cs @@ -7,7 +7,7 @@ namespace Orchard.Core.Feeds { public interface IFeedFormatter { ActionResult Process(FeedContext context, Action populate); - FeedItem AddItem(FeedContext context, ContentItem contentItem); + FeedItem AddItem(FeedContext context, TItem contentItem); void AddProperty(FeedContext context, FeedItem feedItem, string name, string value); } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Feeds/IFeedManager.cs b/src/Orchard.Web/Core/Feeds/IFeedManager.cs new file mode 100644 index 000000000..90d76bfe6 --- /dev/null +++ b/src/Orchard.Web/Core/Feeds/IFeedManager.cs @@ -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); + } +} diff --git a/src/Orchard.Web/Core/Feeds/Models/FeedContext.cs b/src/Orchard.Web/Core/Feeds/Models/FeedContext.cs index 024b4bf77..f64e6ad06 100644 --- a/src/Orchard.Web/Core/Feeds/Models/FeedContext.cs +++ b/src/Orchard.Web/Core/Feeds/Models/FeedContext.cs @@ -7,16 +7,12 @@ namespace Orchard.Core.Feeds.Models { ValueProvider = valueProvider; Format = format; Response = new FeedResponse(); - FeedData = new Dictionary(); } public IValueProvider ValueProvider { get; set; } public string Format { get; set; } + public FeedResponse Response { get; set; } public IFeedFormatter FeedFormatter { get; set; } - - public IDictionary FeedData { get; set; } - - public FeedResponse Response { get; set; } } } diff --git a/src/Orchard.Web/Core/Feeds/Models/FeedItem.cs b/src/Orchard.Web/Core/Feeds/Models/FeedItem.cs index 73395b9ad..8ebbf90fb 100644 --- a/src/Orchard.Web/Core/Feeds/Models/FeedItem.cs +++ b/src/Orchard.Web/Core/Feeds/Models/FeedItem.cs @@ -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 : 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); + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Feeds/Rss/Routes.cs b/src/Orchard.Web/Core/Feeds/Rss/Routes.cs index f3f5397d0..8c6921e97 100644 --- a/src/Orchard.Web/Core/Feeds/Rss/Routes.cs +++ b/src/Orchard.Web/Core/Feeds/Rss/Routes.cs @@ -7,20 +7,21 @@ namespace Orchard.Core.Feeds.Rss { public class Routes : IRouteProvider { public IEnumerable 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()) } }; } diff --git a/src/Orchard.Web/Core/Feeds/Rss/RssFeedFormat.cs b/src/Orchard.Web/Core/Feeds/Rss/RssFeedFormat.cs index 85ddd3b35..8ff5209a4 100644 --- a/src/Orchard.Web/Core/Feeds/Rss/RssFeedFormat.cs +++ b/src/Orchard.Web/Core/Feeds/Rss/RssFeedFormat.cs @@ -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 AddItem(FeedContext context, TItem item) { + var feedItem = new FeedItem { + Item = item, Element = new XElement("item"), }; context.Response.Items.Add(feedItem); diff --git a/src/Orchard.Web/Core/Feeds/Services/FeedFilter.cs b/src/Orchard.Web/Core/Feeds/Services/FeedFilter.cs new file mode 100644 index 000000000..14056bd93 --- /dev/null +++ b/src/Orchard.Web/Core/Feeds/Services/FeedFilter.cs @@ -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) { + } + } +} diff --git a/src/Orchard.Web/Core/Feeds/Services/FeedManager.cs b/src/Orchard.Web/Core/Feeds/Services/FeedManager.cs new file mode 100644 index 000000000..2e1759e48 --- /dev/null +++ b/src/Orchard.Web/Core/Feeds/Services/FeedManager.cs @@ -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 _links = new List(); + + 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(@""); + } + + return MvcHtmlString.Create(sb.ToString()); + } + + //public ActionResult Execute(string format, IValueProvider values) { + + //} + } +} diff --git a/src/Orchard.Web/Core/Feeds/StandardBuilders/CorePartsFeedItemBuilder.cs b/src/Orchard.Web/Core/Feeds/StandardBuilders/CorePartsFeedItemBuilder.cs index e652ec9a6..0f9ee0fe2 100644 --- a/src/Orchard.Web/Core/Feeds/StandardBuilders/CorePartsFeedItemBuilder.cs +++ b/src/Orchard.Web/Core/Feeds/StandardBuilders/CorePartsFeedItemBuilder.cs @@ -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>()) { + var inspector = new ItemInspector( - feedItem.ContentItem, - _contentManager.GetItemMetadata(feedItem.ContentItem)); + feedItem.Item, + _contentManager.GetItemMetadata(feedItem.Item)); // TODO: author diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index 686283a2a..301e67373 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -84,7 +84,10 @@ + + + diff --git a/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogController.cs b/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogController.cs index d4cc630f5..db312d61f 100644 --- a/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogController.cs +++ b/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogController.cs @@ -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); } } diff --git a/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogPostController.cs b/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogPostController.cs index 7523247b5..09cf3d2ef 100644 --- a/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogPostController.cs +++ b/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/BlogPostController.cs @@ -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); } } diff --git a/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/FeedExtensions.cs b/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/FeedExtensions.cs new file mode 100644 index 000000000..c2b7152f1 --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Blogs/Controllers/FeedExtensions.cs @@ -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 } }); + } + } +} diff --git a/src/Orchard.Web/Packages/Orchard.Blogs/Orchard.Blogs.csproj b/src/Orchard.Web/Packages/Orchard.Blogs/Orchard.Blogs.csproj index e7d917745..fba8a25fa 100644 --- a/src/Orchard.Web/Packages/Orchard.Blogs/Orchard.Blogs.csproj +++ b/src/Orchard.Web/Packages/Orchard.Blogs/Orchard.Blogs.csproj @@ -79,6 +79,7 @@ + diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs b/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs index a20f456a9..16e1b7f3d 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs +++ b/src/Orchard.Web/Packages/Orchard.Comments/Controllers/AdminController.cs @@ -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)) { diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentFeedItemBuilder.cs b/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentFeedItemBuilder.cs new file mode 100644 index 000000000..9e813ef9f --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentFeedItemBuilder.cs @@ -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>()) { + 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? + } + } + } + } +} diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentedOnContainerFeedQuery.cs b/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentedOnContainerFeedQuery.cs new file mode 100644 index 000000000..7a2188639 --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentedOnContainerFeedQuery.cs @@ -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 _commentRepository; + + public CommentedOnContainerFeedQuery( + IRepository 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); + } + } + } +} diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentedOnFeedQuery.cs b/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentedOnFeedQuery.cs new file mode 100644 index 000000000..2d50411ba --- /dev/null +++ b/src/Orchard.Web/Packages/Orchard.Comments/Feeds/CommentedOnFeedQuery.cs @@ -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 _commentRepository; + + public CommentedOnFeedQuery( + IRepository 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); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs b/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs index 78b6063f6..81c3d2eb3 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs +++ b/src/Orchard.Web/Packages/Orchard.Comments/Models/Comment.cs @@ -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 { diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj b/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj index 688ea9606..d72e5e158 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj +++ b/src/Orchard.Web/Packages/Orchard.Comments/Orchard.Comments.csproj @@ -68,6 +68,9 @@ + + + @@ -103,6 +106,10 @@ {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} Orchard + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + diff --git a/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs b/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs index bfa603ccc..48dbe69ee 100644 --- a/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs +++ b/src/Orchard.Web/Packages/Orchard.Comments/Services/CommentService.cs @@ -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(comment.CommentedOn); + if (commentedOn != null && commentedOn.Container != null) { + comment.CommentedOnContainer = commentedOn.Container.ContentItem.Id; + } + _commentRepository.Create(comment); }