From 2798850a38866de714ea5578ebc2f0c51ad16ede Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Tue, 30 Nov 2010 16:03:01 -0800 Subject: [PATCH] Some work on making a blog or (routable) content item inaccessible by its path if it is set as the home page --HG-- branch : dev --- src/Orchard.Specs/Blogs.feature | 24 +++++++- src/Orchard.Specs/Blogs.feature.cs | 61 +++++++++++++++++++ .../Routable/Controllers/ItemController.cs | 31 +++++++--- .../Routable/Handlers/RoutePartHandler.cs | 19 +++++- .../Services/RoutableHomePageProvider.cs | 15 ++++- .../Controllers/BlogController.cs | 17 +++++- .../Orchard.Blogs/Handlers/BlogPartHandler.cs | 30 ++++++++- .../Modules/Orchard.Blogs/Routes.cs | 3 +- src/Orchard/Services/IHomePageProvider.cs | 1 + 9 files changed, 186 insertions(+), 15 deletions(-) diff --git a/src/Orchard.Specs/Blogs.feature b/src/Orchard.Specs/Blogs.feature index 9dad395d7..550056c30 100644 --- a/src/Orchard.Specs/Blogs.feature +++ b/src/Orchard.Specs/Blogs.feature @@ -147,4 +147,26 @@ Scenario: The virtual path of my installation when at the root is reflected in t | Routable.Title | My Blog | And I hit "Save" And I go to "admin/blogs/my-blog/posts/create" - Then I should see "http\://localhost/my-blog/" \ No newline at end of file + Then I should see "http\://localhost/my-blog/" + +Scenario: I set my blog to be the content for the home page and the posts for the blog should still be at the blog path prefixed path + Given I have installed Orchard + When I go to "admin/blogs/create" + And I fill in + | name | value | + | Routable.Title | My Blog | + | Routable.PromoteToHomePage | true | + And I hit "Save" + And I go to "admin/blogs/my-blog/posts/create" + And I fill in + | name | value | + | Routable.Title | My Post | + | Body.Text | Hi there. | + And I hit "Publish Now" + And I am redirected + And I go to "/Default.aspx" + Then I should see "

My Blog

" + When I go to "/my-blog" + Then the status should be 404 "Not Found" + When I go to "/my-blog/my-post" + Then I should see "

My Post

" diff --git a/src/Orchard.Specs/Blogs.feature.cs b/src/Orchard.Specs/Blogs.feature.cs index b00e46221..4e88c4f33 100644 --- a/src/Orchard.Specs/Blogs.feature.cs +++ b/src/Orchard.Specs/Blogs.feature.cs @@ -457,6 +457,67 @@ this.ScenarioSetup(scenarioInfo); testRunner.And("I go to \"admin/blogs/my-blog/posts/create\""); #line 150 testRunner.Then("I should see \"http\\://localhost/my-blog/\""); +#line hidden + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("I set my blog to be the content for the home page and the posts for the blog shou" + + "ld still be at the blog path prefixed path")] + public virtual void ISetMyBlogToBeTheContentForTheHomePageAndThePostsForTheBlogShouldStillBeAtTheBlogPathPrefixedPath() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("I set my blog to be the content for the home page and the posts for the blog shou" + + "ld still be at the blog path prefixed path", ((string[])(null))); +#line 152 +this.ScenarioSetup(scenarioInfo); +#line 153 + testRunner.Given("I have installed Orchard"); +#line 154 + testRunner.When("I go to \"admin/blogs/create\""); +#line hidden + TechTalk.SpecFlow.Table table15 = new TechTalk.SpecFlow.Table(new string[] { + "name", + "value"}); + table15.AddRow(new string[] { + "Routable.Title", + "My Blog"}); + table15.AddRow(new string[] { + "Routable.PromoteToHomePage", + "true"}); +#line 155 + testRunner.And("I fill in", ((string)(null)), table15); +#line 159 + testRunner.And("I hit \"Save\""); +#line 160 + testRunner.And("I go to \"admin/blogs/my-blog/posts/create\""); +#line hidden + TechTalk.SpecFlow.Table table16 = new TechTalk.SpecFlow.Table(new string[] { + "name", + "value"}); + table16.AddRow(new string[] { + "Routable.Title", + "My Post"}); + table16.AddRow(new string[] { + "Body.Text", + "Hi there."}); +#line 161 + testRunner.And("I fill in", ((string)(null)), table16); +#line 165 + testRunner.And("I hit \"Publish Now\""); +#line 166 + testRunner.And("I am redirected"); +#line 167 + testRunner.And("I go to \"/Default.aspx\""); +#line 168 + testRunner.Then("I should see \"

My Blog

\""); +#line 169 + testRunner.When("I go to \"/my-blog\""); +#line 170 + testRunner.Then("the status should be 404 \"Not Found\""); +#line 171 + testRunner.When("I go to \"/my-blog/my-post\""); +#line 172 + testRunner.Then("I should see \"

My Post

\""); #line hidden testRunner.CollectScenarioErrors(); } diff --git a/src/Orchard.Web/Core/Routable/Controllers/ItemController.cs b/src/Orchard.Web/Core/Routable/Controllers/ItemController.cs index 93957e3a9..fa7418b6c 100644 --- a/src/Orchard.Web/Core/Routable/Controllers/ItemController.cs +++ b/src/Orchard.Web/Core/Routable/Controllers/ItemController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using Orchard.ContentManagement; @@ -8,6 +9,7 @@ using Orchard.Core.Routable.Services; using Orchard.Data; using Orchard.DisplayManagement; using Orchard.Localization; +using Orchard.Services; using Orchard.Themes; namespace Orchard.Core.Routable.Controllers { @@ -16,25 +18,33 @@ namespace Orchard.Core.Routable.Controllers { private readonly IContentManager _contentManager; private readonly ITransactionManager _transactionManager; private readonly IRoutablePathConstraint _routablePathConstraint; + private readonly IWorkContextAccessor _workContextAccessor; + private readonly IHomePageProvider _routableHomePageProvider; public ItemController( IContentManager contentManager, ITransactionManager transactionManager, - IRoutablePathConstraint routablePathConstraint, - IShapeFactory shapeFactory) { + IRoutablePathConstraint routablePathConstraint, + IShapeFactory shapeFactory, + IWorkContextAccessor workContextAccessor, + IEnumerable homePageProviders) { _contentManager = contentManager; _transactionManager = transactionManager; _routablePathConstraint = routablePathConstraint; + _workContextAccessor = workContextAccessor; + _routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name); Shape = shapeFactory; + T = NullLocalizer.Instance; } + public Localizer T { get; set; } dynamic Shape { get; set; } [Themed] public ActionResult Display(string path) { var matchedPath = _routablePathConstraint.FindPath(path); - if (string.IsNullOrEmpty(matchedPath)) { - throw new ApplicationException("404 - should not have passed path constraint"); + if (matchedPath == null) { + throw new ApplicationException(T("404 - should not have passed path constraint").Text); } var hits = _contentManager @@ -42,13 +52,20 @@ namespace Orchard.Core.Routable.Controllers { .Where(r => r.Path == matchedPath) .Slice(0, 2); if (hits.Count() == 0) { - throw new ApplicationException("404 - should not have passed path constraint"); + throw new ApplicationException(T("404 - should not have passed path constraint").Text); } if (hits.Count() != 1) { - throw new ApplicationException("Ambiguous content"); + throw new ApplicationException(T("Ambiguous content").Text); } - dynamic model = _contentManager.BuildDisplay(hits.Single()); + var item = hits.Single(); + // primary action run for a home paged item shall not pass + if (!RouteData.DataTokens.ContainsKey("ParentActionViewContext") + && item.Id == _routableHomePageProvider.GetHomePageId(_workContextAccessor.GetContext().CurrentSite.HomePage)) { + return HttpNotFound(); + } + + dynamic model = _contentManager.BuildDisplay(item); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation. return View((object)model); } diff --git a/src/Orchard.Web/Core/Routable/Handlers/RoutePartHandler.cs b/src/Orchard.Web/Core/Routable/Handlers/RoutePartHandler.cs index 62682ab66..5ba7b9384 100644 --- a/src/Orchard.Web/Core/Routable/Handlers/RoutePartHandler.cs +++ b/src/Orchard.Web/Core/Routable/Handlers/RoutePartHandler.cs @@ -23,6 +23,7 @@ namespace Orchard.Core.Routable.Handlers { IRepository repository, IRoutablePathConstraint routablePathConstraint, IRoutableService routableService, + IWorkContextAccessor workContextAccessor, IEnumerable homePageProviders) { _services = services; _routablePathConstraint = routablePathConstraint; @@ -46,8 +47,10 @@ namespace Orchard.Core.Routable.Handlers { OnPublished((context, route) => { FinalizePath(route, context, processSlug); - if (route.ContentItem.Id != 0 && route.PromoteToHomePage && _routableHomePageProvider != null) + if (route.ContentItem.Id != 0 && route.PromoteToHomePage && _routableHomePageProvider != null) { _services.WorkContext.CurrentSite.HomePage = _routableHomePageProvider.GetSettingValue(route.ContentItem.Id); + _routablePathConstraint.AddPath(""); + } }); OnRemoved((context, route) => { @@ -87,6 +90,14 @@ namespace Orchard.Core.Routable.Handlers { } public class RoutePartHandlerBase : ContentHandlerBase { + private readonly IWorkContextAccessor _workContextAccessor; + private readonly IHomePageProvider _routableHomePageProvider; + + public RoutePartHandlerBase(IWorkContextAccessor workContextAccessor, IEnumerable homePageProviders) { + _workContextAccessor = workContextAccessor; + _routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name); + } + public override void GetContentItemMetadata(GetContentItemMetadataContext context) { var routable = context.ContentItem.As(); @@ -98,11 +109,15 @@ namespace Orchard.Core.Routable.Handlers { // set the display route values if it hasn't been set or only has been set by the Contents module. // allows other modules to set their own display. probably not common enough to warrant some priority implemntation if (context.Metadata.DisplayRouteValues == null || context.Metadata.DisplayRouteValues["Area"] as string == "Contents") { + var itemPath = routable.Id == _routableHomePageProvider.GetHomePageId(_workContextAccessor.GetContext().CurrentSite.HomePage) + ? "" + : routable.Path; + context.Metadata.DisplayRouteValues = new RouteValueDictionary { {"Area", "Routable"}, {"Controller", "Item"}, {"Action", "Display"}, - {"path", routable.Path} + {"path", itemPath} }; } } diff --git a/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs b/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs index 47c80c74f..24a00b28a 100644 --- a/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs +++ b/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs @@ -1,7 +1,9 @@ -using System.Web.Mvc; +using System; +using System.Web.Mvc; using JetBrains.Annotations; using Orchard.Core.Routable.Models; using Orchard.DisplayManagement; +using Orchard.Localization; using Orchard.Services; using Orchard.ContentManagement; @@ -16,8 +18,10 @@ namespace Orchard.Core.Routable.Services { IShapeFactory shapeFactory) { _contentManager = contentManager; Shape = shapeFactory; + T = NullLocalizer.Instance; } + public Localizer T { get; set; } dynamic Shape { get; set; } public string GetProviderName() { @@ -28,6 +32,15 @@ namespace Orchard.Core.Routable.Services { return GetProviderName() + ";" + id; } + public int GetHomePageId(string value) { + int id; + + if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value.Substring(Name.Length + 1), out id)) + throw new ApplicationException(T("Invalid home page setting value for {0}: {1}", Name, value).Text); + + return id; + } + public ActionResult GetHomePage(int id) { var contentItem = _contentManager.Get(id, VersionOptions.Published); if (contentItem == null || !contentItem.Is()) diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Controllers/BlogController.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Controllers/BlogController.cs index 790a10383..5661a51ce 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Controllers/BlogController.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Controllers/BlogController.cs @@ -1,11 +1,14 @@ +using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using Orchard.Blogs.Extensions; using Orchard.Blogs.Routing; using Orchard.Blogs.Services; using Orchard.Core.Feeds; +using Orchard.Core.Routable.Services; using Orchard.DisplayManagement; using Orchard.Logging; +using Orchard.Services; using Orchard.Themes; using Orchard.UI.Navigation; @@ -17,6 +20,8 @@ namespace Orchard.Blogs.Controllers { private readonly IBlogPostService _blogPostService; private readonly IBlogSlugConstraint _blogSlugConstraint; private readonly IFeedManager _feedManager; + private readonly IWorkContextAccessor _workContextAccessor; + private readonly IHomePageProvider _routableHomePageProvider; public BlogController( IOrchardServices services, @@ -24,12 +29,16 @@ namespace Orchard.Blogs.Controllers { IBlogPostService blogPostService, IBlogSlugConstraint blogSlugConstraint, IFeedManager feedManager, - IShapeFactory shapeFactory) { + IShapeFactory shapeFactory, + IWorkContextAccessor workContextAccessor, + IEnumerable homePageProviders) { _services = services; _blogService = blogService; _blogPostService = blogPostService; _blogSlugConstraint = blogSlugConstraint; _feedManager = feedManager; + _workContextAccessor = workContextAccessor; + _routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name); Logger = NullLogger.Instance; Shape = shapeFactory; } @@ -59,6 +68,12 @@ namespace Orchard.Blogs.Controllers { if (blogPart == null) return HttpNotFound(); + // primary action run for a home paged item shall not pass + if (!RouteData.DataTokens.ContainsKey("ParentActionViewContext") + && blogPart.Id == _routableHomePageProvider.GetHomePageId(_workContextAccessor.GetContext().CurrentSite.HomePage)) { + return HttpNotFound(); + } + _feedManager.Register(blogPart); var blogPosts = _blogPostService.Get(blogPart, pager.GetStartIndex(), pager.PageSize) .Select(b => _services.ContentManager.BuildDisplay(b, "Summary")); diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Handlers/BlogPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Handlers/BlogPartHandler.cs index afbb78849..70ed19a5e 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Handlers/BlogPartHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Handlers/BlogPartHandler.cs @@ -1,17 +1,39 @@ +using System.Collections.Generic; +using System.Linq; using System.Web.Routing; using JetBrains.Annotations; using Orchard.Blogs.Models; +using Orchard.Blogs.Routing; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; using Orchard.Core.Routable.Models; +using Orchard.Core.Routable.Services; using Orchard.Data; +using Orchard.Services; namespace Orchard.Blogs.Handlers { [UsedImplicitly] public class BlogPartHandler : ContentHandler { - public BlogPartHandler(IRepository repository) { + private readonly IWorkContextAccessor _workContextAccessor; + private readonly IBlogSlugConstraint _blogSlugConstraint; + private readonly IHomePageProvider _routableHomePageProvider; + + public BlogPartHandler(IRepository repository, IWorkContextAccessor workContextAccessor, IEnumerable homePageProviders, IBlogSlugConstraint blogSlugConstraint) { + _workContextAccessor = workContextAccessor; + _blogSlugConstraint = blogSlugConstraint; + _routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name); Filters.Add(StorageFilter.For(repository)); + OnPublished((context, route) => { + if (route.Is()) { + if (route.ContentItem.Id != 0 && route.PromoteToHomePage) + _blogSlugConstraint.AddSlug(""); + } + else if (route.ContentItem.Id != 0 && route.PromoteToHomePage) { + _blogSlugConstraint.RemoveSlug(""); + } + }); + OnGetDisplayShape((context, blog) => { context.Shape.Description = blog.Description; context.Shape.PostCount = blog.PostCount; @@ -24,11 +46,15 @@ namespace Orchard.Blogs.Handlers { if (blog == null) return; + var blogSlug = blog.Id == _routableHomePageProvider.GetHomePageId(_workContextAccessor.GetContext().CurrentSite.HomePage) + ? "" + : blog.As().Slug; + context.Metadata.DisplayRouteValues = new RouteValueDictionary { {"Area", "Orchard.Blogs"}, {"Controller", "Blog"}, {"Action", "Item"}, - {"blogSlug", blog.As().Slug} + {"blogSlug", blogSlug} }; context.Metadata.CreateRouteValues = new RouteValueDictionary { {"Area", "Orchard.Blogs"}, diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Routes.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Routes.cs index e6407fdcc..73759e2ff 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Routes.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Routes.cs @@ -243,7 +243,8 @@ namespace Orchard.Blogs { new RouteValueDictionary { {"area", "Orchard.Blogs"}, {"controller", "Blog"}, - {"action", "Item"} + {"action", "Item"}, + {"blogSlug", ""} }, new RouteValueDictionary { {"blogSlug", _blogSlugConstraint} diff --git a/src/Orchard/Services/IHomePageProvider.cs b/src/Orchard/Services/IHomePageProvider.cs index 7def1fd96..be2d9a06c 100644 --- a/src/Orchard/Services/IHomePageProvider.cs +++ b/src/Orchard/Services/IHomePageProvider.cs @@ -4,6 +4,7 @@ namespace Orchard.Services { public interface IHomePageProvider : IDependency { string GetProviderName(); string GetSettingValue(int itemId); + int GetHomePageId(string value); ActionResult GetHomePage(int itemId); } }