mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-01-19 17:51:45 +08:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user