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

@@ -20,7 +20,7 @@ namespace Orchard.Core.Routable.Drivers {
public RoutePartDriver(IOrchardServices services, IRoutableService routableService, IEnumerable<IHomePageProvider> homePageProviders) {
_services = services;
_routableService = routableService;
_routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name); ;
_routableHomePageProvider = homePageProviders.SingleOrDefault(p => p.GetProviderName() == RoutableHomePageProvider.Name);
T = NullLocalizer.Instance;
}
@@ -49,23 +49,15 @@ namespace Orchard.Core.Routable.Drivers {
var model = new RoutableEditorViewModel {
ContentType = part.ContentItem.ContentType,
Id = part.ContentItem.Id,
Slug = part.Slug,
Slug = part.GetEffectiveSlug(),
Title = part.Title,
ContainerId = GetContainerId(part),
};
// TEMP: path format patterns replaces this logic
var path = part.Path;
var slug = part.Slug ?? "";
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)
: "";
}
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);
return ContentShape("Parts_Routable_Edit",
@@ -73,34 +65,26 @@ namespace Orchard.Core.Routable.Drivers {
}
protected override DriverResult Editor(RoutePart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new RoutableEditorViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
part.Title = model.Title;
part.Slug = model.Slug;
if ( !_routableService.IsSlugValid(part.Slug) ) {
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."));
}
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)."));
}
}
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}\"",
originalSlug, part.Slug, part.ContentItem.ContentType));
}
part.Slug, part.GetEffectiveSlug(), part.ContentItem.ContentType));
// TEMP: path format patterns replaces this logic
part.Path = part.GetPathWithSlug(part.Slug);
if (part.ContentItem.Id != 0 && model.PromoteToHomePage && _routableHomePageProvider != null) {
if (part.ContentItem.Id != 0 && model.PromoteToHomePage && _routableHomePageProvider != null)
_services.WorkContext.CurrentSite.HomePage = _routableHomePageProvider.GetSettingValue(part.ContentItem.Id);
}
return Editor(part, shapeHelper);
}

View File

@@ -24,14 +24,9 @@ namespace Orchard.Core.Routable.Handlers {
Action<RoutePart> processSlug = (
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}\"",
originalSlug, routable.Slug, routable.ContentItem.ContentType));
}
// TEMP: path format patterns replaces this logic
routable.Path = routable.GetPathWithSlug(routable.Slug);
routable.Slug, routable.GetEffectiveSlug(), routable.ContentItem.ContentType));
});
OnGetDisplayShape<RoutePart>(SetModelProperties);

View File

@@ -17,25 +17,5 @@ namespace Orchard.Core.Routable.Models {
get { return Record.Path; }
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 Orchard.Core.Routable.Models;
using System.Collections.Generic;
using Orchard.ContentManagement.Aspects;
namespace Orchard.Core.Routable.Services {
public interface IRoutableService : IDependency {
void FillSlugFromTitle<TModel>(TModel model) where TModel : RoutePart;
string GenerateUniqueSlug(RoutePart part, IEnumerable<string> existingPaths);
void FillSlugFromTitle<TModel>(TModel model) where TModel : IRoutableAspect;
string GenerateUniqueSlug(IRoutableAspect part, IEnumerable<string> existingPaths);
/// <summary>
/// Returns any content item with similar path
/// </summary>
IEnumerable<RoutePart> GetSimilarPaths(string path);
IEnumerable<IRoutableAspect> GetSimilarPaths(string path);
/// <summary>
/// Validates the given slug
@@ -21,7 +20,7 @@ namespace Orchard.Core.Routable.Services {
/// Defines the slug of a RoutableAspect and validate its unicity
/// </summary>
/// <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.Text.RegularExpressions;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Routable.Models;
namespace Orchard.Core.Routable.Services {
@@ -13,7 +14,7 @@ namespace Orchard.Core.Routable.Services {
_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))
return;
@@ -32,21 +33,20 @@ namespace Orchard.Core.Routable.Services {
model.Slug = slug.ToLowerInvariant();
}
public string GenerateUniqueSlug(RoutePart part, IEnumerable<string> existingPaths) {
var slugCandidate = part.Slug;
public string GenerateUniqueSlug(IRoutableAspect part, IEnumerable<string> existingPaths) {
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
? string.Format("{0}-{1}", slugCandidate, version)
: slugCandidate;
? string.Format("{0}-{1}", part.Slug, version)
: part.Slug;
}
private static int? GetSlugVersion(string slugCandidate, string slug) {
private static int? GetSlugVersion(string path, string potentialConflictingPath) {
int v;
string[] slugParts = slug.Split(new []{slugCandidate}, StringSplitOptions.RemoveEmptyEntries);
string[] slugParts = potentialConflictingPath.Split(new[] { path }, StringSplitOptions.RemoveEmptyEntries);
if (slugParts.Length == 0)
return 2;
@@ -56,12 +56,12 @@ namespace Orchard.Core.Routable.Services {
: null;
}
public IEnumerable<RoutePart> GetSimilarPaths(string path) {
public IEnumerable<IRoutableAspect> GetSimilarPaths(string path) {
return
_contentManager.Query().Join<RoutePartRecord>()
.List()
.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();
}
@@ -69,7 +69,7 @@ namespace Orchard.Core.Routable.Services {
return String.IsNullOrWhiteSpace(slug) || Regex.IsMatch(slug, @"^[^:?#\[\]@!$&'()*+,;=\s]+$") && !(slug.StartsWith(".") || slug.EndsWith("."));
}
public bool ProcessSlug(RoutePart part) {
public bool ProcessSlug(IRoutableAspect part) {
FillSlugFromTitle(part);
if (string.IsNullOrEmpty(part.Slug))
@@ -84,15 +84,43 @@ namespace Orchard.Core.Routable.Services {
if (pathsLikeThis.Count() > 0) {
var originalSlug = part.Slug;
//todo: (heskew) make auto-uniqueness optional
part.Slug = GenerateUniqueSlug(part, pathsLikeThis.Select(p => p.Path));
var newSlug = GenerateUniqueSlug(part, pathsLikeThis.Select(p => p.Path));
part.Path = part.GetPathWithSlug(newSlug);
if (originalSlug != part.Slug) {
if (originalSlug != newSlug)
return false;
}
}
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();
}
}
}