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.Common.Models ;
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-07-23 16:01:49 +08:00
public void FillSlug < 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-07-23 16:01:49 +08:00
public void FillSlug < TModel > ( TModel model , Func < string , string > generateSlug ) where TModel : RoutePart {
2010-06-10 06:18:12 +08:00
if ( ! string . IsNullOrEmpty ( model . Slug ) | | string . IsNullOrEmpty ( model . Title ) )
return ;
model . Slug = generateSlug ( model . Title ) . ToLowerInvariant ( ) ;
}
public string GenerateUniqueSlug ( string slugCandidate , IEnumerable < string > existingSlugs ) {
if ( existingSlugs = = null | | ! existingSlugs . Contains ( slugCandidate ) )
return slugCandidate ;
int? version = existingSlugs . Select ( s = > GetSlugVersion ( slugCandidate , s ) ) . OrderBy ( i = > i ) . LastOrDefault ( ) ;
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-07-23 16:01:49 +08:00
public IEnumerable < RoutePart > GetSimilarSlugs ( string contentType , string slug )
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-07-20 02:50:13 +08:00
. Where ( routable = > routable . Path ! = null & & routable . Path . Equals ( slug , 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
return slug = = null | | String . IsNullOrEmpty ( slug . Trim ( ) ) | | Regex . IsMatch ( slug , @"^[^/:?#\[\]@!$&'()*+,;=\s]+$" ) ;
}
2010-07-23 16:01:49 +08:00
public bool ProcessSlug ( RoutePart part )
2010-06-10 06:18:12 +08:00
{
FillSlug ( part ) ;
if ( string . IsNullOrEmpty ( part . Slug ) )
{
return true ;
}
2010-07-17 08:27:01 +08:00
var slugsLikeThis = GetSimilarSlugs ( part . ContentItem . ContentType , part . Path ) ;
2010-06-10 06:18:12 +08:00
// If the part is already a valid content item, don't include it in the list
// of slug to consider for conflict detection
if ( part . ContentItem . Id ! = 0 )
slugsLikeThis = slugsLikeThis . Where ( p = > p . ContentItem . Id ! = part . ContentItem . Id ) ;
//todo: (heskew) need better messages
if ( slugsLikeThis . Count ( ) > 0 )
{
var originalSlug = part . Slug ;
//todo: (heskew) make auto-uniqueness optional
part . Slug = GenerateUniqueSlug ( part . Slug , slugsLikeThis . Select ( p = > p . Slug ) ) ;
if ( originalSlug ! = part . Slug ) {
return false ;
}
}
return true ;
}
}
}