2010-06-10 06:18:12 +08:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text.RegularExpressions ;
using Orchard.ContentManagement ;
using Orchard.Core.Routable.Models ;
namespace Orchard.Core.Routable.Services {
public class RoutableService : IRoutableService {
private readonly IContentManager _contentManager ;
public RoutableService ( IContentManager contentManager ) {
_contentManager = contentManager ;
}
2010-08-25 02:41:41 +08:00
public void FillSlugFromTitle < TModel > ( TModel model ) where TModel : RoutePart {
2010-06-10 06:18:12 +08:00
if ( ! string . IsNullOrEmpty ( model . Slug ) | | string . IsNullOrEmpty ( model . Title ) )
return ;
var slug = model . Title ;
var dissallowed = new Regex ( @"[/:?#\[\]@!$&'()*+,;=\s]+" ) ;
slug = dissallowed . Replace ( slug , "-" ) ;
slug = slug . Trim ( '-' ) ;
if ( slug . Length > 1000 )
slug = slug . Substring ( 0 , 1000 ) ;
model . Slug = slug . ToLowerInvariant ( ) ;
}
2010-08-24 07:14:10 +08:00
public string GenerateUniqueSlug ( RoutePart part , IEnumerable < string > existingPaths ) {
var slugCandidate = part . Slug ;
if ( existingPaths = = null | | ! existingPaths . Contains ( part . Path ) )
2010-06-10 06:18:12 +08:00
return slugCandidate ;
2010-08-24 07:14:10 +08:00
int? version = existingPaths . Select ( s = > GetSlugVersion ( slugCandidate , s ) ) . OrderBy ( i = > i ) . LastOrDefault ( ) ;
2010-06-10 06:18:12 +08:00
return version ! = null
? string . Format ( "{0}-{1}" , slugCandidate , version )
: slugCandidate ;
}
private static int? GetSlugVersion ( string slugCandidate , string slug ) {
int v ;
string [ ] slugParts = slug . Split ( new [ ] { slugCandidate } , StringSplitOptions . RemoveEmptyEntries ) ;
if ( slugParts . Length = = 0 )
return 2 ;
return int . TryParse ( slugParts [ 0 ] . TrimStart ( '-' ) , out v )
? ( int? ) + + v
: null ;
}
2010-08-24 07:14:10 +08:00
public IEnumerable < RoutePart > GetSimilarPaths ( string path ) {
2010-06-10 06:18:12 +08:00
return
2010-07-23 16:01:49 +08:00
_contentManager . Query ( ) . Join < RoutePartRecord > ( )
2010-06-10 06:18:12 +08:00
. List ( )
2010-07-23 16:01:49 +08:00
. Select ( i = > i . As < RoutePart > ( ) )
2010-08-24 07:14:10 +08:00
. 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
2010-06-10 06:18:12 +08:00
. ToArray ( ) ;
}
public bool IsSlugValid ( string slug ) {
// see http://tools.ietf.org/html/rfc3987 for prohibited chars
2010-08-26 22:32:45 +08:00
return slug = = null | | String . IsNullOrEmpty ( slug . Trim ( ) ) | | Regex . IsMatch ( slug , @"^[^:?#\[\]@!$&'()*+,;=\s]+$" ) ;
2010-06-10 06:18:12 +08:00
}
2010-08-25 02:41:41 +08:00
public bool ProcessSlug ( RoutePart part ) {
FillSlugFromTitle ( part ) ;
2010-06-10 06:18:12 +08:00
if ( string . IsNullOrEmpty ( part . Slug ) )
return true ;
2010-08-25 02:41:41 +08:00
part . Path = part . GetPathWithSlug ( part . Slug ) ;
2010-08-24 07:14:10 +08:00
var pathsLikeThis = GetSimilarPaths ( part . Path ) ;
2010-06-10 06:18:12 +08:00
2010-08-25 02:41:41 +08:00
// Don't include *this* part in the list
2010-08-24 07:14:10 +08:00
// of slugs to consider for conflict detection
pathsLikeThis = pathsLikeThis . Where ( p = > p . ContentItem . Id ! = part . ContentItem . Id ) ;
2010-06-10 06:18:12 +08:00
//todo: (heskew) need better messages
2010-08-25 02:41:41 +08:00
if ( pathsLikeThis . Count ( ) > 0 ) {
2010-06-10 06:18:12 +08:00
var originalSlug = part . Slug ;
//todo: (heskew) make auto-uniqueness optional
2010-08-24 07:14:10 +08:00
part . Slug = GenerateUniqueSlug ( part , pathsLikeThis . Select ( p = > p . Path ) ) ;
2010-06-10 06:18:12 +08:00
if ( originalSlug ! = part . Slug ) {
return false ;
}
}
return true ;
}
}
}