mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-04-05 21:01:35 +08:00
192 lines
7.8 KiB
C#
192 lines
7.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Orchard.ContentManagement;
|
|
using Orchard.ContentManagement.Aspects;
|
|
using Orchard.Core.Common.Models;
|
|
using Orchard.Core.Routable.Events;
|
|
using Orchard.Core.Routable.Models;
|
|
|
|
namespace Orchard.Core.Routable.Services {
|
|
public class RoutableService : IRoutableService {
|
|
private readonly IContentManager _contentManager;
|
|
private readonly IEnumerable<ISlugEventHandler> _slugEventHandlers;
|
|
private readonly IRoutablePathConstraint _routablePathConstraint;
|
|
|
|
public RoutableService(IContentManager contentManager, IEnumerable<ISlugEventHandler> slugEventHandlers, IRoutablePathConstraint routablePathConstraint) {
|
|
_contentManager = contentManager;
|
|
_slugEventHandlers = slugEventHandlers;
|
|
_routablePathConstraint = routablePathConstraint;
|
|
}
|
|
|
|
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) {
|
|
var route = itemRoute.As<IRoutableAspect>();
|
|
|
|
if(route == null) {
|
|
continue;
|
|
}
|
|
|
|
var path = route.Path;
|
|
route.Path = route.GetPathWithSlug(route.Slug);
|
|
|
|
// 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);
|
|
FixContainedPaths(route);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(route.Path))
|
|
_routablePathConstraint.AddPath(route.Path);
|
|
|
|
}
|
|
}
|
|
|
|
public static string RemoveDiacritics(string slug) {
|
|
string stFormD = slug.Normalize(NormalizationForm.FormD);
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int ich = 0; ich < stFormD.Length; ich++) {
|
|
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(stFormD[ich]);
|
|
if (uc != UnicodeCategory.NonSpacingMark) {
|
|
sb.Append(stFormD[ich]);
|
|
}
|
|
}
|
|
|
|
return (sb.ToString().Normalize(NormalizationForm.FormC));
|
|
}
|
|
|
|
public void FillSlugFromTitle<TModel>(TModel model) where TModel : IRoutableAspect {
|
|
if ((model.Slug != null && !string.IsNullOrEmpty(model.Slug.Trim())) || string.IsNullOrEmpty(model.Title))
|
|
return;
|
|
|
|
FillSlugContext slugContext = new FillSlugContext(model.Title);
|
|
|
|
foreach (ISlugEventHandler slugEventHandler in _slugEventHandlers) {
|
|
slugEventHandler.FillingSlugFromTitle(slugContext);
|
|
}
|
|
|
|
if (!slugContext.Adjusted) {
|
|
var disallowed = new Regex(@"[/:?#\[\]@!$&'()*+,;=\s\""\<\>\\]+");
|
|
|
|
slugContext.Slug = disallowed.Replace(slugContext.Slug, "-").Trim('-');
|
|
|
|
if (slugContext.Slug.Length > 1000)
|
|
slugContext.Slug = slugContext.Slug.Substring(0, 1000);
|
|
|
|
// dots are not allowed at the begin and the end of routes
|
|
slugContext.Slug = RemoveDiacritics(slugContext.Slug.Trim('.').ToLower());
|
|
}
|
|
|
|
foreach (ISlugEventHandler slugEventHandler in _slugEventHandlers) {
|
|
slugEventHandler.FilledSlugFromTitle(slugContext);
|
|
}
|
|
|
|
model.Slug = slugContext.Slug;
|
|
}
|
|
|
|
public string GenerateUniqueSlug(IRoutableAspect part, IEnumerable<string> existingPaths) {
|
|
if (existingPaths == null || !existingPaths.Contains(part.Path))
|
|
return part.Slug;
|
|
|
|
int? version = existingPaths.Select(s => GetSlugVersion(part.Path, s)).OrderBy(i => i).LastOrDefault();
|
|
|
|
return version != null
|
|
? string.Format("{0}-{1}", part.Slug, version)
|
|
: part.Slug;
|
|
}
|
|
|
|
private static int? GetSlugVersion(string path, string potentialConflictingPath) {
|
|
int v;
|
|
string[] slugParts = potentialConflictingPath.Split(new[] { path }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if (slugParts.Length == 0)
|
|
return 2;
|
|
|
|
return int.TryParse(slugParts[0].TrimStart('-'), out v)
|
|
? (int?)++v
|
|
: null;
|
|
}
|
|
|
|
public IEnumerable<IRoutableAspect> GetSimilarPaths(string path) {
|
|
return
|
|
_contentManager.Query<RoutePart, RoutePartRecord>()
|
|
.List()
|
|
.Select(i => i.As<RoutePart>())
|
|
.Where(routable => routable.Path != null && routable.Path.StartsWith(path, StringComparison.OrdinalIgnoreCase))
|
|
.ToArray();
|
|
}
|
|
|
|
public bool IsSlugValid(string slug) {
|
|
return String.IsNullOrWhiteSpace(slug) || Regex.IsMatch(slug, @"^[^:?#\[\]@!$&'()*+,;=\s\""\<\>\\]+$") && !(slug.StartsWith(".") || slug.EndsWith("."));
|
|
}
|
|
|
|
public bool ProcessSlug(IRoutableAspect part) {
|
|
FillSlugFromTitle(part);
|
|
|
|
if (string.IsNullOrEmpty(part.Slug))
|
|
return true;
|
|
|
|
part.Path = part.GetPathWithSlug(part.Slug);
|
|
var pathsLikeThis = GetSimilarPaths(part.Path);
|
|
|
|
// Don't include *this* part in the list
|
|
// of slugs to consider for conflict detection
|
|
pathsLikeThis = pathsLikeThis.Where(p => p.ContentItem.Id != part.ContentItem.Id);
|
|
|
|
if (pathsLikeThis.Count() > 0) {
|
|
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;
|
|
}
|
|
|
|
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 GetChildPath(this IRoutableAspect routableAspect, string slug) {
|
|
return string.Format("{0}/{1}", routableAspect.Path, 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();
|
|
}
|
|
}
|
|
} |