Some work on making the route part more container aware

- still needs some work in the Slugify method (for client slugification)
- also a container path placeholder...

--HG--
branch : dev
This commit is contained in:
Nathan Heskew 2010-11-13 01:52:53 -08:00
parent 2e2820de1f
commit 036033116f
9 changed files with 153 additions and 109 deletions

View File

@ -83,6 +83,7 @@
<Reference Include="System.Data.SqlServerCe, Version=3.5.1.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL"> <Reference Include="System.Data.SqlServerCe, Version=3.5.1.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\sqlce\System.Data.SqlServerCe.dll</HintPath> <HintPath>..\..\lib\sqlce\System.Data.SqlServerCe.dll</HintPath>
<Private>True</Private>
</Reference> </Reference>
<Reference Include="System.Web" /> <Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions"> <Reference Include="System.Web.Abstractions">

View File

@ -6,7 +6,6 @@ using Moq;
using NUnit.Framework; using NUnit.Framework;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects; using Orchard.ContentManagement.Aspects;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers; using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.Records; using Orchard.ContentManagement.Records;
@ -110,14 +109,13 @@ namespace Orchard.Core.Tests.Routable.Services {
} }
} }
[Test] [Test]
public void VeryLongStringTruncatedTo1000Chars() { public void VeryLongStringTruncatedTo1000Chars() {
var veryVeryLongTitle = "this is a very long title..."; var veryVeryLongTitle = "this is a very long title...";
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
veryVeryLongTitle += "aaaaaaaaaa"; veryVeryLongTitle += "aaaaaaaaaa";
var thing = CreateRoutePart(veryVeryLongTitle); var thing = CreateRoutePartFromScratch(veryVeryLongTitle);
_routableService.FillSlugFromTitle(thing); _routableService.FillSlugFromTitle(thing);
Assert.That(veryVeryLongTitle.Length, Is.AtLeast(1001)); Assert.That(veryVeryLongTitle.Length, Is.AtLeast(1001));
@ -126,72 +124,111 @@ namespace Orchard.Core.Tests.Routable.Services {
[Test] [Test]
public void NoExistingLikeSlugsGeneratesSameSlug() { public void NoExistingLikeSlugsGeneratesSameSlug() {
string slug = _routableService.GenerateUniqueSlug(CreateRoutePart("woohoo"), null); string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), null);
Assert.That(slug, Is.EqualTo("woohoo")); Assert.That(slug, Is.EqualTo("woohoo"));
} }
[Test] [Test]
public void ExistingSingleLikeSlugThatsAConflictGeneratesADash2() { public void ExistingSingleLikeSlugThatsAConflictGeneratesADash2() {
string slug = _routableService.GenerateUniqueSlug(CreateRoutePart("woohoo"), new List<string> { "woohoo" }); string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List<string> { "woohoo" });
Assert.That(slug, Is.EqualTo("woohoo-2")); Assert.That(slug, Is.EqualTo("woohoo-2"));
} }
[Test] [Test]
public void ExistingSingleLikeSlugThatsNotAConflictGeneratesSameSlug() { public void ExistingSingleLikeSlugThatsNotAConflictGeneratesSameSlug() {
string slug = _routableService.GenerateUniqueSlug(CreateRoutePart("woohoo"), new List<string> { "woohoo-2" }); string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List<string> { "woohoo-2" });
Assert.That(slug, Is.EqualTo("woohoo")); Assert.That(slug, Is.EqualTo("woohoo"));
} }
[Test] [Test]
public void ExistingLikeSlugsWithAConflictGeneratesADashVNext() { public void ExistingLikeSlugsWithAConflictGeneratesADashVNext() {
string slug = _routableService.GenerateUniqueSlug(CreateRoutePart("woohoo"), new List<string> { "woohoo", "woohoo-2" }); string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List<string> { "woohoo", "woohoo-2" });
Assert.That(slug, Is.EqualTo("woohoo-3")); Assert.That(slug, Is.EqualTo("woohoo-3"));
} }
[Test] [Test]
public void ExistingSlugsWithVersionGapsAndNoMatchGeneratesSameSlug() { public void ExistingSlugsWithVersionGapsAndNoMatchGeneratesSameSlug() {
string slug = _routableService.GenerateUniqueSlug(CreateRoutePart("woohoo"), new List<string> { "woohoo-2", "woohoo-4", "woohoo-5" }); string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List<string> { "woohoo-2", "woohoo-4", "woohoo-5" });
Assert.That(slug, Is.EqualTo("woohoo")); Assert.That(slug, Is.EqualTo("woohoo"));
} }
[Test] [Test]
public void ExistingSlugsWithVersionGapsAndAMatchGeneratesADash2() { public void ExistingSlugsWithVersionGapsAndAMatchGeneratesADash2() {
string slug = _routableService.GenerateUniqueSlug(CreateRoutePart("woohoo-2"), new List<string> { "woohoo-2", "woohoo-4", "woohoo-5" }); string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo-2"), new List<string> { "woohoo-2", "woohoo-4", "woohoo-5" });
Assert.That(slug, Is.EqualTo("woohoo-2-2")); Assert.That(slug, Is.EqualTo("woohoo-2-2"));
} }
[Test] [Test]
public void GeneratedSlugIsLowerCased() { public void SlugIsGeneratedLowerCased() {
var thing = CreateRoutePart("This Is Some Interesting Title"); var thing = CreateRoutePartFromScratch("This Is Some Interesting Title");
_routableService.FillSlugFromTitle(thing); _routableService.FillSlugFromTitle(thing);
Assert.That(thing.Slug, Is.EqualTo("this-is-some-interesting-title")); Assert.That(thing.Slug, Is.EqualTo("this-is-some-interesting-title"));
} }
[Test] [Test]
public void SlugInConflictWithAnExistingItemsPathIsVersioned() { public void SlugInConflictWithAnExistingItemsPathIsVersioned() {
var thing1 = CreateRoutePart("bar", "bar", "foo"); CreateRoutePartFromScratch("bar", "bar", "foo");
var thing2 = CreateRoutePart("fooslashbar", "foo/bar"); var thing2 = CreateRoutePartFromScratch("fooslashbar", "foo/bar");
Assert.That(thing2.Path, Is.EqualTo("foo/bar-2"));
Assert.That(thing2.Slug, Is.EqualTo("foo/bar-2"));
} }
private RoutePart CreateRoutePart(string title, string slug = "", string containerPath = "") { [Test]
public void GeneratedSlugInConflictInSameContaierPathIsVersioned() {
var thing1 = CreateRoutePartFromScratch("Foo", "", "bar");
var thing2 = CreateRoutePartWithExistingContainer("Foo", thing1.As<ICommonPart>().Container);
Assert.That(thing2.Path, Is.EqualTo("bar/foo-2"));
Assert.That(thing2.Slug, Is.EqualTo("foo"));
}
[Test]
public void GivenSlugInConflictInSameContaierPathIsVersioned() {
var thing1 = CreateRoutePartFromScratch("Hi", "foo", "bar");
var thing2 = CreateRoutePartWithExistingContainer("There", thing1.As<ICommonPart>().Container, "foo");
Assert.That(thing2.Path, Is.EqualTo("bar/foo-2"));
Assert.That(thing2.Slug, Is.EqualTo("foo"));
}
[Test]
public void GeneratedSlugInConflictInDifferentContaierPathIsNotVersioned() {
var thing1 = CreateRoutePartFromScratch("Foo", "", "rab");
var thing2 = CreateRoutePartFromScratch("Foo", "", "bar");
Assert.That(thing1.Path, Is.EqualTo("rab/foo"));
Assert.That(thing2.Path, Is.EqualTo("bar/foo"));
Assert.That(thing1.Slug, Is.EqualTo("foo"));
Assert.That(thing2.Slug, Is.EqualTo("foo"));
}
private RoutePart CreateRoutePartWithExistingContainer(string title, IContent container, string slug = "") {
var contentManager = _container.Resolve<IContentManager>(); var contentManager = _container.Resolve<IContentManager>();
return contentManager.Create<Thing>("thing", t => { return contentManager.Create<Thing>("thing", t => {
t.As<RoutePart>().Record = new RoutePartRecord(); t.As<RoutePart>().Record = new RoutePartRecord();
if (!string.IsNullOrWhiteSpace(slug)) t.Title = title;
t.As<RoutePart>().Slug = slug;
t.Title = title; if (!string.IsNullOrWhiteSpace(slug))
if (!string.IsNullOrWhiteSpace(containerPath)) { t.As<RoutePart>().Slug = slug;
t.As<ICommonPart>().Container = contentManager.Create<Thing>("thing", tt => {
tt.As<RoutePart>().Path = containerPath; if (container != null)
tt.As<RoutePart>().Slug = containerPath; t.As<ICommonPart>().Container = container;
}); }).As<RoutePart>();
} }
})
.As<RoutePart>(); private RoutePart CreateRoutePartFromScratch(string title, string slug = "", string containerPath = "") {
var contentManager = _container.Resolve<IContentManager>();
return contentManager.Create<Thing>("thing", t => {
t.As<RoutePart>().Record = new RoutePartRecord();
if (!string.IsNullOrWhiteSpace(slug))
t.As<RoutePart>().Slug = slug;
t.Title = title;
if (!string.IsNullOrWhiteSpace(containerPath)) {
t.As<ICommonPart>().Container = contentManager.Create<Thing>("thing", tt => {
tt.As<RoutePart>().Path = containerPath;
tt.As<RoutePart>().Slug = containerPath;
tt.As<RoutePart>().Title = "Test Container";
});
}
}).As<RoutePart>();
} }

View File

@ -6,14 +6,21 @@ using Orchard.ContentManagement.Aspects;
using Orchard.ContentManagement.Drivers; using Orchard.ContentManagement.Drivers;
using Orchard.Core.Containers.Models; using Orchard.Core.Containers.Models;
using Orchard.Core.Containers.ViewModels; using Orchard.Core.Containers.ViewModels;
using Orchard.Core.Routable.Models;
using Orchard.Core.Routable.Services;
using Orchard.Localization; using Orchard.Localization;
using Orchard.UI.Notify;
namespace Orchard.Core.Containers.Drivers { namespace Orchard.Core.Containers.Drivers {
public class ContainablePartDriver : ContentPartDriver<ContainablePart> { public class ContainablePartDriver : ContentPartDriver<ContainablePart> {
private readonly IContentManager _contentManager; private readonly IContentManager _contentManager;
private readonly IRoutableService _routableService;
private readonly IOrchardServices _services;
public ContainablePartDriver(IContentManager contentManager) { public ContainablePartDriver(IContentManager contentManager, IRoutableService routableService, IOrchardServices services) {
_contentManager = contentManager; _contentManager = contentManager;
_routableService = routableService;
_services = services;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
} }
@ -39,6 +46,18 @@ namespace Orchard.Core.Containers.Drivers {
updater.TryUpdateModel(model, "Containable", null, null); updater.TryUpdateModel(model, "Containable", null, null);
if (oldContainerId != model.ContainerId) { if (oldContainerId != model.ContainerId) {
commonPart.Container = _contentManager.Get(model.ContainerId, VersionOptions.Latest); commonPart.Container = _contentManager.Get(model.ContainerId, VersionOptions.Latest);
// reprocess slug
var routable = part.As<IRoutableAspect>();
_routableService.ProcessSlug(part.As<IRoutableAspect>());
if (!_routableService.ProcessSlug(routable)) {
var existingConflict = _services.Notifier.List().FirstOrDefault(n => n.Message.TextHint == "Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"");
if (existingConflict != null)
existingConflict.Message = T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
routable.Slug, routable.GetEffectiveSlug(), routable.ContentItem.ContentType);
else
_services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
routable.Slug, routable.GetEffectiveSlug(), routable.ContentItem.ContentType));
}
} }
} }

View File

@ -20,7 +20,7 @@ namespace Orchard.Core.Routable.Drivers {
public RoutePartDriver(IOrchardServices services, IRoutableService routableService, IEnumerable<IHomePageProvider> homePageProviders) { public RoutePartDriver(IOrchardServices services, IRoutableService routableService, IEnumerable<IHomePageProvider> homePageProviders) {
_services = services; _services = services;
_routableService = routableService; _routableService = routableService;
_routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name); ; _routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name);
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
} }
@ -49,23 +49,15 @@ namespace Orchard.Core.Routable.Drivers {
var model = new RoutableEditorViewModel { var model = new RoutableEditorViewModel {
ContentType = part.ContentItem.ContentType, ContentType = part.ContentItem.ContentType,
Id = part.ContentItem.Id, Id = part.ContentItem.Id,
Slug = part.Slug, Slug = part.GetEffectiveSlug(),
Title = part.Title, Title = part.Title,
ContainerId = GetContainerId(part), ContainerId = GetContainerId(part),
}; };
// TEMP: path format patterns replaces this logic var containerPath = part.GetContainerPath();
var path = part.Path; model.DisplayLeadingPath = !string.IsNullOrWhiteSpace(containerPath)
var slug = part.Slug ?? ""; ? string.Format("{0}/", containerPath)
if (path != null && path.EndsWith(slug)) { : "";
model.DisplayLeadingPath = path.Substring(0, path.Length - slug.Length);
}
else {
var containerPath = part.GetContainerPath();
model.DisplayLeadingPath = !string.IsNullOrWhiteSpace(containerPath)
? string.Format("{0}/", containerPath)
: "";
}
model.PromoteToHomePage = model.Id != 0 && part.Path != null && _routableHomePageProvider != null && _services.WorkContext.CurrentSite.HomePage == _routableHomePageProvider.GetSettingValue(model.Id); model.PromoteToHomePage = model.Id != 0 && part.Path != null && _routableHomePageProvider != null && _services.WorkContext.CurrentSite.HomePage == _routableHomePageProvider.GetSettingValue(model.Id);
return ContentShape("Parts_Routable_Edit", return ContentShape("Parts_Routable_Edit",
@ -73,34 +65,26 @@ namespace Orchard.Core.Routable.Drivers {
} }
protected override DriverResult Editor(RoutePart part, IUpdateModel updater, dynamic shapeHelper) { protected override DriverResult Editor(RoutePart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new RoutableEditorViewModel(); var model = new RoutableEditorViewModel();
updater.TryUpdateModel(model, Prefix, null, null); updater.TryUpdateModel(model, Prefix, null, null);
part.Title = model.Title; part.Title = model.Title;
part.Slug = model.Slug; part.Slug = model.Slug;
if ( !_routableService.IsSlugValid(part.Slug) ) { if ( !_routableService.IsSlugValid(part.Slug) ) {
var slug = (part.Slug ?? String.Empty); var slug = (part.Slug ?? String.Empty);
if ( slug.StartsWith(".") || slug.EndsWith(".") ) { if ( slug.StartsWith(".") || slug.EndsWith(".") )
updater.AddModelError("Routable.Slug", T("The \".\" can't be used around routes.")); updater.AddModelError("Routable.Slug", T("The \".\" can't be used around routes."));
} else
else {
updater.AddModelError("Routable.Slug", T("Please do not use any of the following characters in your slugs: \":\", \"?\", \"#\", \"[\", \"]\", \"@\", \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\". No spaces are allowed (please use dashes or underscores instead).")); updater.AddModelError("Routable.Slug", T("Please do not use any of the following characters in your slugs: \":\", \"?\", \"#\", \"[\", \"]\", \"@\", \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\". No spaces are allowed (please use dashes or underscores instead)."));
}
} }
string originalSlug = part.Slug; if (!_routableService.ProcessSlug(part))
if (!_routableService.ProcessSlug(part)) {
_services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"", _services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
originalSlug, part.Slug, part.ContentItem.ContentType)); part.Slug, part.GetEffectiveSlug(), part.ContentItem.ContentType));
}
// TEMP: path format patterns replaces this logic if (part.ContentItem.Id != 0 && model.PromoteToHomePage && _routableHomePageProvider != null)
part.Path = part.GetPathWithSlug(part.Slug);
if (part.ContentItem.Id != 0 && model.PromoteToHomePage && _routableHomePageProvider != null) {
_services.WorkContext.CurrentSite.HomePage = _routableHomePageProvider.GetSettingValue(part.ContentItem.Id); _services.WorkContext.CurrentSite.HomePage = _routableHomePageProvider.GetSettingValue(part.ContentItem.Id);
}
return Editor(part, shapeHelper); return Editor(part, shapeHelper);
} }

View File

@ -24,14 +24,9 @@ namespace Orchard.Core.Routable.Handlers {
Action<RoutePart> processSlug = ( Action<RoutePart> processSlug = (
routable => { routable => {
var originalSlug = routable.Slug; if (!_routableService.ProcessSlug(routable))
if (!_routableService.ProcessSlug(routable)) {
_services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"", _services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"",
originalSlug, routable.Slug, routable.ContentItem.ContentType)); routable.Slug, routable.GetEffectiveSlug(), routable.ContentItem.ContentType));
}
// TEMP: path format patterns replaces this logic
routable.Path = routable.GetPathWithSlug(routable.Slug);
}); });
OnGetDisplayShape<RoutePart>(SetModelProperties); OnGetDisplayShape<RoutePart>(SetModelProperties);

View File

@ -17,25 +17,5 @@ namespace Orchard.Core.Routable.Models {
get { return Record.Path; } get { return Record.Path; }
set { Record.Path = value; } set { Record.Path = value; }
} }
public string GetContainerPath() {
var commonAspect = this.As<ICommonPart>();
if (commonAspect != null && commonAspect.Container != null) {
var routable = commonAspect.Container.As<IRoutableAspect>();
if (routable != null) {
return routable.Path;
}
}
return null;
}
public string GetPathWithSlug(string slug) {
// TEMP: path format patterns replaces this logic
var containerPath = GetContainerPath();
if (string.IsNullOrEmpty(containerPath)) {
return slug;
}
return containerPath + "/" + slug;
}
} }
} }

View File

@ -1,16 +1,15 @@
using System; using System.Collections.Generic;
using System.Collections.Generic; using Orchard.ContentManagement.Aspects;
using Orchard.Core.Routable.Models;
namespace Orchard.Core.Routable.Services { namespace Orchard.Core.Routable.Services {
public interface IRoutableService : IDependency { public interface IRoutableService : IDependency {
void FillSlugFromTitle<TModel>(TModel model) where TModel : RoutePart; void FillSlugFromTitle<TModel>(TModel model) where TModel : IRoutableAspect;
string GenerateUniqueSlug(RoutePart part, IEnumerable<string> existingPaths); string GenerateUniqueSlug(IRoutableAspect part, IEnumerable<string> existingPaths);
/// <summary> /// <summary>
/// Returns any content item with similar path /// Returns any content item with similar path
/// </summary> /// </summary>
IEnumerable<RoutePart> GetSimilarPaths(string path); IEnumerable<IRoutableAspect> GetSimilarPaths(string path);
/// <summary> /// <summary>
/// Validates the given slug /// Validates the given slug
@ -21,7 +20,7 @@ namespace Orchard.Core.Routable.Services {
/// Defines the slug of a RoutableAspect and validate its unicity /// Defines the slug of a RoutableAspect and validate its unicity
/// </summary> /// </summary>
/// <returns>True if the slug has been created, False if a conflict occured</returns> /// <returns>True if the slug has been created, False if a conflict occured</returns>
bool ProcessSlug(RoutePart part); bool ProcessSlug(IRoutableAspect part);
} }
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Routable.Models; using Orchard.Core.Routable.Models;
namespace Orchard.Core.Routable.Services { namespace Orchard.Core.Routable.Services {
@ -13,7 +14,7 @@ namespace Orchard.Core.Routable.Services {
_contentManager = contentManager; _contentManager = contentManager;
} }
public void FillSlugFromTitle<TModel>(TModel model) where TModel : RoutePart { public void FillSlugFromTitle<TModel>(TModel model) where TModel : IRoutableAspect {
if (!string.IsNullOrEmpty(model.Slug) || string.IsNullOrEmpty(model.Title)) if (!string.IsNullOrEmpty(model.Slug) || string.IsNullOrEmpty(model.Title))
return; return;
@ -32,21 +33,20 @@ namespace Orchard.Core.Routable.Services {
model.Slug = slug.ToLowerInvariant(); model.Slug = slug.ToLowerInvariant();
} }
public string GenerateUniqueSlug(RoutePart part, IEnumerable<string> existingPaths) { public string GenerateUniqueSlug(IRoutableAspect part, IEnumerable<string> existingPaths) {
var slugCandidate = part.Slug;
if (existingPaths == null || !existingPaths.Contains(part.Path)) if (existingPaths == null || !existingPaths.Contains(part.Path))
return slugCandidate; return part.Slug;
int? version = existingPaths.Select(s => GetSlugVersion(slugCandidate, s)).OrderBy(i => i).LastOrDefault(); int? version = existingPaths.Select(s => GetSlugVersion(part.Path, s)).OrderBy(i => i).LastOrDefault();
return version != null return version != null
? string.Format("{0}-{1}", slugCandidate, version) ? string.Format("{0}-{1}", part.Slug, version)
: slugCandidate; : part.Slug;
} }
private static int? GetSlugVersion(string slugCandidate, string slug) { private static int? GetSlugVersion(string path, string potentialConflictingPath) {
int v; int v;
string[] slugParts = slug.Split(new []{slugCandidate}, StringSplitOptions.RemoveEmptyEntries); string[] slugParts = potentialConflictingPath.Split(new[] { path }, StringSplitOptions.RemoveEmptyEntries);
if (slugParts.Length == 0) if (slugParts.Length == 0)
return 2; return 2;
@ -56,12 +56,12 @@ namespace Orchard.Core.Routable.Services {
: null; : null;
} }
public IEnumerable<RoutePart> GetSimilarPaths(string path) { public IEnumerable<IRoutableAspect> GetSimilarPaths(string path) {
return return
_contentManager.Query().Join<RoutePartRecord>() _contentManager.Query().Join<RoutePartRecord>()
.List() .List()
.Select(i => i.As<RoutePart>()) .Select(i => i.As<RoutePart>())
.Where(routable => routable.Path != null && routable.Path.StartsWith(path, StringComparison.OrdinalIgnoreCase)) // todo: for some reason the filter doesn't work within the query, even without StringComparison or StartsWith .Where(routable => routable.Path != null && routable.Path.StartsWith(path, StringComparison.OrdinalIgnoreCase))
.ToArray(); .ToArray();
} }
@ -69,7 +69,7 @@ namespace Orchard.Core.Routable.Services {
return String.IsNullOrWhiteSpace(slug) || Regex.IsMatch(slug, @"^[^:?#\[\]@!$&'()*+,;=\s]+$") && !(slug.StartsWith(".") || slug.EndsWith(".")); return String.IsNullOrWhiteSpace(slug) || Regex.IsMatch(slug, @"^[^:?#\[\]@!$&'()*+,;=\s]+$") && !(slug.StartsWith(".") || slug.EndsWith("."));
} }
public bool ProcessSlug(RoutePart part) { public bool ProcessSlug(IRoutableAspect part) {
FillSlugFromTitle(part); FillSlugFromTitle(part);
if (string.IsNullOrEmpty(part.Slug)) if (string.IsNullOrEmpty(part.Slug))
@ -84,15 +84,43 @@ namespace Orchard.Core.Routable.Services {
if (pathsLikeThis.Count() > 0) { if (pathsLikeThis.Count() > 0) {
var originalSlug = part.Slug; var originalSlug = part.Slug;
//todo: (heskew) make auto-uniqueness optional var newSlug = GenerateUniqueSlug(part, pathsLikeThis.Select(p => p.Path));
part.Slug = GenerateUniqueSlug(part, pathsLikeThis.Select(p => p.Path)); part.Path = part.GetPathWithSlug(newSlug);
if (originalSlug != part.Slug) { if (originalSlug != newSlug)
return false; return false;
}
} }
return true; return true;
} }
} }
public static class RoutableAspectExtensions {
public static string GetContainerPath(this IRoutableAspect routableAspect) {
var commonAspect = routableAspect.As<ICommonPart>();
if (commonAspect != null && commonAspect.Container != null) {
var routable = commonAspect.Container.As<IRoutableAspect>();
if (routable != null)
return routable.Path;
}
return null;
}
public static string GetPathWithSlug(this IRoutableAspect routableAspect, string slug) {
var containerPath = routableAspect.GetContainerPath();
return !string.IsNullOrEmpty(containerPath)
? string.Format("{0}/{1}", containerPath, slug)
: slug;
}
public static string GetEffectiveSlug(this IRoutableAspect routableAspect) {
var containerPath = routableAspect.GetContainerPath();
if (string.IsNullOrWhiteSpace(routableAspect.Path))
return "";
var slugParts = routableAspect.Path.Split(new []{string.Format("{0}/", containerPath)}, StringSplitOptions.RemoveEmptyEntries);
return slugParts.FirstOrDefault();
}
}
} }

View File

@ -8,6 +8,7 @@ using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models; using Orchard.Core.Common.Models;
using Orchard.Core.Navigation.Models; using Orchard.Core.Navigation.Models;
using Orchard.Core.Routable.Models; using Orchard.Core.Routable.Models;
using Orchard.Core.Routable.Services;
using Orchard.Security; using Orchard.Security;
using Orchard.Blogs.Services; using Orchard.Blogs.Services;
using Orchard.Core.Navigation.Services; using Orchard.Core.Navigation.Services;