mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-01-19 17:51:45 +08:00
Updating Routable to fix the paths of contained items when a container's path changes
--HG-- branch : dev
This commit is contained in:
@@ -62,3 +62,33 @@ Scenario: I can create a new blog with multiple blog posts each with the same ti
|
||||
And I go to "my-blog/my-post-3"
|
||||
Then I should see "<h1[^>]*>.*?My Post.*?</h1>"
|
||||
And I should see "Are you still there?"
|
||||
|
||||
Scenario: I can create a new blog and blog post and when I change the slug of the blog the path of the plog post is updated
|
||||
Given I have installed Orchard
|
||||
When I go to "admin/blogs/create"
|
||||
And I fill in
|
||||
| name | value |
|
||||
| Routable.Title | My Blog |
|
||||
And I hit "Save"
|
||||
And I go to "my-blog"
|
||||
Then I should see "<h1[^>]*>.*?My Blog.*?</h1>"
|
||||
When 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 go to "my-blog/my-post"
|
||||
Then I should see "<h1[^>]*>.*?My Post.*?</h1>"
|
||||
And I should see "Hi there."
|
||||
When I go to "admin/blogs/my-blog"
|
||||
And I follow "Blog Properties"
|
||||
And I fill in
|
||||
| name | value |
|
||||
| Routable.Slug | my-other-blog |
|
||||
And I hit "Save"
|
||||
And I go to "my-other-blog"
|
||||
Then I should see "<h1[^>]*>.*?My Blog.*?</h1>"
|
||||
When I go to "my-other-blog/my-post"
|
||||
Then I should see "<h1[^>]*>.*?My Post.*?</h1>"
|
||||
And I should see "Hi there."
|
||||
79
src/Orchard.Specs/Blogs.feature.cs
generated
79
src/Orchard.Specs/Blogs.feature.cs
generated
@@ -215,6 +215,85 @@ this.ScenarioSetup(scenarioInfo);
|
||||
testRunner.Then("I should see \"<h1[^>]*>.*?My Post.*?</h1>\"");
|
||||
#line 64
|
||||
testRunner.And("I should see \"Are you still there?\"");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
}
|
||||
|
||||
[NUnit.Framework.TestAttribute()]
|
||||
[NUnit.Framework.DescriptionAttribute("I can create a new blog and blog post and when I change the slug of the blog the " +
|
||||
"path of the plog post is updated")]
|
||||
public virtual void ICanCreateANewBlogAndBlogPostAndWhenIChangeTheSlugOfTheBlogThePathOfThePlogPostIsUpdated()
|
||||
{
|
||||
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("I can create a new blog and blog post and when I change the slug of the blog the " +
|
||||
"path of the plog post is updated", ((string[])(null)));
|
||||
#line 66
|
||||
this.ScenarioSetup(scenarioInfo);
|
||||
#line 67
|
||||
testRunner.Given("I have installed Orchard");
|
||||
#line 68
|
||||
testRunner.When("I go to \"admin/blogs/create\"");
|
||||
#line hidden
|
||||
TechTalk.SpecFlow.Table table7 = new TechTalk.SpecFlow.Table(new string[] {
|
||||
"name",
|
||||
"value"});
|
||||
table7.AddRow(new string[] {
|
||||
"Routable.Title",
|
||||
"My Blog"});
|
||||
#line 69
|
||||
testRunner.And("I fill in", ((string)(null)), table7);
|
||||
#line 72
|
||||
testRunner.And("I hit \"Save\"");
|
||||
#line 73
|
||||
testRunner.And("I go to \"my-blog\"");
|
||||
#line 74
|
||||
testRunner.Then("I should see \"<h1[^>]*>.*?My Blog.*?</h1>\"");
|
||||
#line 75
|
||||
testRunner.When("I go to \"admin/blogs/my-blog/posts/create\"");
|
||||
#line hidden
|
||||
TechTalk.SpecFlow.Table table8 = new TechTalk.SpecFlow.Table(new string[] {
|
||||
"name",
|
||||
"value"});
|
||||
table8.AddRow(new string[] {
|
||||
"Routable.Title",
|
||||
"My Post"});
|
||||
table8.AddRow(new string[] {
|
||||
"Body.Text",
|
||||
"Hi there."});
|
||||
#line 76
|
||||
testRunner.And("I fill in", ((string)(null)), table8);
|
||||
#line 80
|
||||
testRunner.And("I hit \"Publish Now\"");
|
||||
#line 81
|
||||
testRunner.And("I go to \"my-blog/my-post\"");
|
||||
#line 82
|
||||
testRunner.Then("I should see \"<h1[^>]*>.*?My Post.*?</h1>\"");
|
||||
#line 83
|
||||
testRunner.And("I should see \"Hi there.\"");
|
||||
#line 84
|
||||
testRunner.When("I go to \"admin/blogs/my-blog\"");
|
||||
#line 85
|
||||
testRunner.And("I follow \"Blog Properties\"");
|
||||
#line hidden
|
||||
TechTalk.SpecFlow.Table table9 = new TechTalk.SpecFlow.Table(new string[] {
|
||||
"name",
|
||||
"value"});
|
||||
table9.AddRow(new string[] {
|
||||
"Routable.Slug",
|
||||
"my-other-blog"});
|
||||
#line 86
|
||||
testRunner.And("I fill in", ((string)(null)), table9);
|
||||
#line 89
|
||||
testRunner.And("I hit \"Save\"");
|
||||
#line 90
|
||||
testRunner.And("I go to \"my-other-blog\"");
|
||||
#line 91
|
||||
testRunner.Then("I should see \"<h1[^>]*>.*?My Blog.*?</h1>\"");
|
||||
#line 92
|
||||
testRunner.When("I go to \"my-other-blog/my-post\"");
|
||||
#line 93
|
||||
testRunner.Then("I should see \"<h1[^>]*>.*?My Post.*?</h1>\"");
|
||||
#line 94
|
||||
testRunner.And("I should see \"Hi there.\"");
|
||||
#line hidden
|
||||
testRunner.CollectScenarioErrors();
|
||||
}
|
||||
|
||||
@@ -33,11 +33,27 @@ namespace Orchard.Core.Routable.Handlers {
|
||||
OnGetEditorShape<RoutePart>(SetModelProperties);
|
||||
OnUpdateEditorShape<RoutePart>(SetModelProperties);
|
||||
|
||||
OnPublishing<RoutePart>((context, routable) => {
|
||||
OnPublished<RoutePart>((context, route) => {
|
||||
var path = route.Path;
|
||||
route.Path = route.GetPathWithSlug(route.Slug);
|
||||
|
||||
if (context.PublishingItemVersionRecord != null)
|
||||
processSlug(routable);
|
||||
if (!string.IsNullOrEmpty(routable.Path))
|
||||
_routablePathConstraint.AddPath(routable.Path);
|
||||
processSlug(route);
|
||||
|
||||
// if the path has changed by having the slug changed on the way in (e.g. user input) or to avoid conflict
|
||||
// then update and publish all contained items
|
||||
if (path != route.Path) {
|
||||
_routablePathConstraint.RemovePath(path);
|
||||
_routableService.FixContainedPaths(route);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(route.Path))
|
||||
_routablePathConstraint.AddPath(route.Path);
|
||||
});
|
||||
|
||||
OnRemoved<RoutePart>((context, route) => {
|
||||
if (!string.IsNullOrWhiteSpace(route.Path))
|
||||
_routablePathConstraint.RemovePath(route.Path);
|
||||
});
|
||||
|
||||
OnIndexing<RoutePart>((context, part) => context.DocumentIndex.Add("title", part.Record.Title).RemoveTags().Analyze());
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<Placement>
|
||||
<!-- available display shapes -->
|
||||
<!--
|
||||
Parts_RoutableTitle
|
||||
Parts_RoutableTitle_Summary
|
||||
Parts_RoutableTitle_SummaryAdmin
|
||||
-->
|
||||
<Place Parts_Routable_Edit="Content:before.5"/>
|
||||
<Match DisplayType="Detail">
|
||||
<Place Parts_RoutableTitle="Header:5"/>
|
||||
</Match>
|
||||
<Match DisplayType="Summary">
|
||||
<Place Parts_RoutableTitle="Header:5"/>
|
||||
<Place Parts_RoutableTitle_Summary="Header:5"/>
|
||||
</Match>
|
||||
</Placement>
|
||||
@@ -22,5 +22,9 @@ namespace Orchard.Core.Routable.Services {
|
||||
/// <returns>True if the slug has been created, False if a conflict occured</returns>
|
||||
bool ProcessSlug(IRoutableAspect part);
|
||||
|
||||
/// <summary>
|
||||
/// Updated the paths of all contained items to reflect the current path of this item
|
||||
/// </summary>
|
||||
void FixContainedPaths(IRoutableAspect part);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ namespace Orchard.Core.Routable.Services {
|
||||
|
||||
public void RemovePath(string path) {
|
||||
lock (_syncLock) {
|
||||
if (path != null && _paths.ContainsKey(path))
|
||||
_paths.Remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Aspects;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Core.Routable.Models;
|
||||
|
||||
namespace Orchard.Core.Routable.Services {
|
||||
@@ -14,6 +15,18 @@ namespace Orchard.Core.Routable.Services {
|
||||
_contentManager = contentManager;
|
||||
}
|
||||
|
||||
public void FixContainedPaths(IRoutableAspect part) {
|
||||
var items = _contentManager.Query(VersionOptions.Published)
|
||||
.Join<CommonPartRecord>().Where(cr => cr.Container.Id == part.Id)
|
||||
.List()
|
||||
.Select(item => item.As<IRoutableAspect>()).Where(item => item != null);
|
||||
|
||||
foreach (var itemRoute in items) {
|
||||
itemRoute.ContentItem.VersionRecord.Published = false; // <- to force a republish
|
||||
_contentManager.Publish(itemRoute.ContentItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void FillSlugFromTitle<TModel>(TModel model) where TModel : IRoutableAspect {
|
||||
if (!string.IsNullOrEmpty(model.Slug) || string.IsNullOrEmpty(model.Title))
|
||||
return;
|
||||
@@ -86,6 +99,7 @@ namespace Orchard.Core.Routable.Services {
|
||||
var originalSlug = part.Slug;
|
||||
var newSlug = GenerateUniqueSlug(part, pathsLikeThis.Select(p => p.Path));
|
||||
part.Path = part.GetPathWithSlug(newSlug);
|
||||
part.Slug = newSlug;
|
||||
|
||||
if (originalSlug != newSlug)
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
@{
|
||||
Orchard.ContentManagement.ContentItem contentItem = Model.ContentPart.ContentItem;
|
||||
string title = Model.Title.ToString();
|
||||
}
|
||||
|
||||
<h1>@Html.ItemDisplayLink(title, contentItem)</h1>
|
||||
<h1>@Model.Title</h1>
|
||||
@@ -73,17 +73,16 @@ namespace Orchard.Blogs.Controllers {
|
||||
}
|
||||
|
||||
_contentManager.Publish(blog.ContentItem);
|
||||
_blogSlugConstraint.AddSlug(blog.As<IRoutableAspect>().GetEffectiveSlug());
|
||||
|
||||
var slug = blog.As<IRoutableAspect>().GetEffectiveSlug();
|
||||
_blogSlugConstraint.AddSlug(slug);
|
||||
return Redirect(Url.BlogForAdmin(blog));
|
||||
}
|
||||
|
||||
public ActionResult Edit(string blogSlug) {
|
||||
public ActionResult Edit(int id) {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageBlogs, T("Not allowed to edit blog")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var blog = _blogService.Get(blogSlug);
|
||||
var blog = _blogService.Get(id, VersionOptions.Latest);
|
||||
if (blog == null)
|
||||
return HttpNotFound();
|
||||
|
||||
@@ -92,20 +91,24 @@ namespace Orchard.Blogs.Controllers {
|
||||
}
|
||||
|
||||
[HttpPost, ActionName("Edit")]
|
||||
public ActionResult EditPOST(string blogSlug) {
|
||||
public ActionResult EditPOST(int id) {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageBlogs, T("Couldn't edit blog")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
var blog = _blogService.Get(blogSlug);
|
||||
var blog = _blogService.Get(id, VersionOptions.DraftRequired);
|
||||
if (blog == null)
|
||||
return HttpNotFound();
|
||||
|
||||
var model = Services.ContentManager.UpdateEditor(blog, this);
|
||||
if (!ModelState.IsValid)
|
||||
if (!ModelState.IsValid) {
|
||||
Services.TransactionManager.Cancel();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
_contentManager.Publish(blog);
|
||||
_blogSlugConstraint.AddSlug(blog.As<IRoutableAspect>().GetEffectiveSlug());
|
||||
Services.Notifier.Information(T("Blog information updated"));
|
||||
|
||||
return Redirect(Url.BlogsForAdmin());
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,8 @@ namespace Orchard.Blogs.Controllers {
|
||||
return View(model);
|
||||
}
|
||||
|
||||
conditionallyPublish(blogPost.ContentItem);
|
||||
|
||||
Services.Notifier.Information(T("Your {0} has been saved.", blogPost.TypeDefinition.DisplayName));
|
||||
|
||||
if (!String.IsNullOrEmpty(returnUrl))
|
||||
|
||||
@@ -48,11 +48,11 @@ namespace Orchard.Blogs.Extensions {
|
||||
}
|
||||
|
||||
public static string BlogEdit(this UrlHelper urlHelper, BlogPart blogPart) {
|
||||
return urlHelper.Action("Edit", "BlogAdmin", new { blogSlug = blogPart.As<IRoutableAspect>().Path, area = "Orchard.Blogs" });
|
||||
return urlHelper.Action("Edit", "BlogAdmin", new { blogPart.Id, area = "Orchard.Blogs" });
|
||||
}
|
||||
|
||||
public static string BlogRemove(this UrlHelper urlHelper, BlogPart blogPart) {
|
||||
return urlHelper.Action("Remove", "BlogAdmin", new { blogSlug = blogPart.As<IRoutableAspect>().Path, area = "Orchard.Blogs" });
|
||||
return urlHelper.Action("Remove", "BlogAdmin", new { blogPart.Id, area = "Orchard.Blogs" });
|
||||
}
|
||||
|
||||
public static string BlogPostCreate(this UrlHelper urlHelper, BlogPart blogPart) {
|
||||
|
||||
@@ -35,15 +35,13 @@ namespace Orchard.Blogs {
|
||||
},
|
||||
new RouteDescriptor {
|
||||
Route = new Route(
|
||||
"Admin/Blogs/{blogSlug}/Edit",
|
||||
"Admin/Blogs/{id}/Edit",
|
||||
new RouteValueDictionary {
|
||||
{"area", "Orchard.Blogs"},
|
||||
{"controller", "BlogAdmin"},
|
||||
{"action", "Edit"}
|
||||
},
|
||||
new RouteValueDictionary {
|
||||
{"blogSlug", _blogSlugConstraint}
|
||||
},
|
||||
new RouteValueDictionary (),
|
||||
new RouteValueDictionary {
|
||||
{"area", "Orchard.Blogs"}
|
||||
},
|
||||
@@ -51,15 +49,13 @@ namespace Orchard.Blogs {
|
||||
},
|
||||
new RouteDescriptor {
|
||||
Route = new Route(
|
||||
"Admin/Blogs/{blogSlug}/Remove",
|
||||
"Admin/Blogs/{id}/Remove",
|
||||
new RouteValueDictionary {
|
||||
{"area", "Orchard.Blogs"},
|
||||
{"controller", "BlogAdmin"},
|
||||
{"action", "Remove"}
|
||||
},
|
||||
new RouteValueDictionary {
|
||||
{"blogSlug", _blogSlugConstraint}
|
||||
},
|
||||
new RouteValueDictionary (),
|
||||
new RouteValueDictionary {
|
||||
{"area", "Orchard.Blogs"}
|
||||
},
|
||||
|
||||
@@ -24,6 +24,10 @@ namespace Orchard.Blogs.Services {
|
||||
.List().FirstOrDefault();
|
||||
}
|
||||
|
||||
public ContentItem Get(int id, VersionOptions versionOptions) {
|
||||
return _contentManager.Get(id, versionOptions);
|
||||
}
|
||||
|
||||
public IEnumerable<BlogPart> Get() {
|
||||
return _contentManager.Query<BlogPart, BlogPartRecord>()
|
||||
.Join<RoutePartRecord>()
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Blogs.Models;
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.Blogs.Services {
|
||||
public interface IBlogService : IDependency {
|
||||
BlogPart Get(string slug);
|
||||
ContentItem Get(int id, VersionOptions versionOptions);
|
||||
IEnumerable<BlogPart> Get();
|
||||
void Delete(BlogPart blogPart);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user