diff --git a/src/Orchard.Web/Modules/Orchard.Alias/Implementation/DefaultAliasService.cs b/src/Orchard.Web/Modules/Orchard.Alias/Implementation/DefaultAliasService.cs index a203f45f6..8e8747de4 100644 --- a/src/Orchard.Web/Modules/Orchard.Alias/Implementation/DefaultAliasService.cs +++ b/src/Orchard.Web/Modules/Orchard.Alias/Implementation/DefaultAliasService.cs @@ -124,7 +124,7 @@ namespace Orchard.Alias.Implementation { return _aliasStorage.List(sourceStartsWith).Select(item => Tuple.Create(item.Item1, item.Item3.ToRouteValueDictionary(), item.Item4)); } - public IEnumerable LookupVirtualPaths(RouteValueDictionary routeValues,HttpContextBase httpContext) { + public IEnumerable LookupVirtualPaths(RouteValueDictionary routeValues, HttpContextBase httpContext) { return Utils.LookupVirtualPaths(httpContext, _routeDescriptors.Value, routeValues); } diff --git a/src/Orchard.Web/Modules/Orchard.Alias/Implementation/Storage/AliasStorage.cs b/src/Orchard.Web/Modules/Orchard.Alias/Implementation/Storage/AliasStorage.cs index 61711eda0..736fcece6 100644 --- a/src/Orchard.Web/Modules/Orchard.Alias/Implementation/Storage/AliasStorage.cs +++ b/src/Orchard.Web/Modules/Orchard.Alias/Implementation/Storage/AliasStorage.cs @@ -6,11 +6,13 @@ using System.Xml.Linq; using Orchard.Alias.Records; using Orchard.Data; using Orchard.Alias.Implementation.Holder; +using Orchard.Validation; namespace Orchard.Alias.Implementation.Storage { public interface IAliasStorage : IDependency { void Set(string path, IDictionary routeValues, string source); IDictionary Get(string aliasPath); + void Remove(Expression> filter); void Remove(string path); void Remove(string path, string aliasSource); void RemoveBySource(string aliasSource); @@ -61,7 +63,7 @@ namespace Orchard.Alias.Implementation.Storage { r => r.Area == areaName && r.Controller == controllerName && r.Action == actionName, o => o.Asc(r => r.Id), 0, 1).FirstOrDefault(); aliasRecord.Action = aliasRecord.Action ?? new ActionRecord { Area = areaName, Controller = controllerName, Action = actionName }; - + aliasRecord.RouteValues = values.ToString(); aliasRecord.Source = source; if (aliasRecord.Action.Id == 0 || aliasRecord.Id == 0) { @@ -88,38 +90,23 @@ namespace Orchard.Alias.Implementation.Storage { } public void Remove(string path) { - - if (path == null) { - throw new ArgumentNullException("path"); - } - - foreach (var aliasRecord in _aliasRepository.Fetch(r => r.Path == path)) { - _aliasRepository.Delete(aliasRecord); - // Bulk updates might go wrong if we don't flush - _aliasRepository.Flush(); - var dict = ToDictionary(aliasRecord); - _aliasHolder.RemoveAlias(new AliasInfo() { Path = dict.Item1, Area = dict.Item2, RouteValues = dict.Item3 }); - } + Remove(x => x.Path == path && x.Source == path); } + public void Remove(string path, string aliasSource) { - - if (path == null) { - throw new ArgumentNullException("path"); - } - - foreach (var aliasRecord in _aliasRepository.Fetch(r => r.Path == path && r.Source == aliasSource)) { - _aliasRepository.Delete(aliasRecord); - // Bulk updates might go wrong if we don't flush - _aliasRepository.Flush(); - var dict = ToDictionary(aliasRecord); - _aliasHolder.RemoveAlias(new AliasInfo() { Path = dict.Item1, Area = dict.Item2, RouteValues = dict.Item3 }); - } + Remove(x => x.Path == path && x.Source == aliasSource); } public void RemoveBySource(string aliasSource) { - foreach (var aliasRecord in _aliasRepository.Fetch(r => r.Source == aliasSource)) { + Remove(x => x.Source == aliasSource); + } + + public void Remove(Expression> filter) { + Argument.ThrowIfNull(filter, "filter"); + + foreach (var aliasRecord in _aliasRepository.Fetch(filter)) { _aliasRepository.Delete(aliasRecord); - // Bulk updates might go wrong if we don't flush + // Bulk updates might go wrong if we don't flush. _aliasRepository.Flush(); var dict = ToDictionary(aliasRecord); _aliasHolder.RemoveAlias(new AliasInfo() { Path = dict.Item1, Area = dict.Item2, RouteValues = dict.Item3 }); @@ -127,7 +114,7 @@ namespace Orchard.Alias.Implementation.Storage { } public IEnumerable, string, int>> List() { - return List((Expression>) null); + return List((Expression>)null); } public IEnumerable, string, int>> List(Expression> predicate) { diff --git a/src/Orchard.Web/Modules/Orchard.Autoroute/Services/AutorouteService.cs b/src/Orchard.Web/Modules/Orchard.Autoroute/Services/AutorouteService.cs index 78a6426b7..275e17b32 100644 --- a/src/Orchard.Web/Modules/Orchard.Autoroute/Services/AutorouteService.cs +++ b/src/Orchard.Web/Modules/Orchard.Autoroute/Services/AutorouteService.cs @@ -3,23 +3,23 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Orchard.Alias; +using Orchard.Alias.Implementation.Storage; using Orchard.Autoroute.Models; using Orchard.Autoroute.Settings; using Orchard.ContentManagement; using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData.Models; -using Orchard.Localization; -using Orchard.Logging; using Orchard.Tokens; namespace Orchard.Autoroute.Services { - public class AutorouteService : IAutorouteService { + public class AutorouteService : Component, IAutorouteService { private readonly IAliasService _aliasService; private readonly ITokenizer _tokenizer; private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IContentManager _contentManager; private readonly IRouteEvents _routeEvents; + private readonly IAliasStorage _aliasStorage; private const string AliasSource = "Autoroute:View"; public AutorouteService( @@ -27,39 +27,36 @@ namespace Orchard.Autoroute.Services { ITokenizer tokenizer, IContentDefinitionManager contentDefinitionManager, IContentManager contentManager, - IRouteEvents routeEvents) { - _aliasService = aliasService; - _tokenizer = tokenizer; - _contentDefinitionManager = contentDefinitionManager; + IRouteEvents routeEvents, + IAliasStorage aliasStorage) { + + _aliasService = aliasService; + _tokenizer = tokenizer; + _contentDefinitionManager = contentDefinitionManager; _contentManager = contentManager; _routeEvents = routeEvents; - - Logger = NullLogger.Instance; - T = NullLocalizer.Instance; + _aliasStorage = aliasStorage; } - public ILogger Logger { get; set; } - public Localizer T { get; set; } - public string GenerateAlias(AutoroutePart part) { if (part == null) { throw new ArgumentNullException("part"); } - string pattern = GetDefaultPattern(part.ContentItem.ContentType).Pattern; - - // String.Empty forces pattern based generation. "/" forces homepage - if(part.UseCustomPattern + var pattern = GetDefaultPattern(part.ContentItem.ContentType).Pattern; + + // String.Empty forces pattern based generation. "/" forces homepage. + if (part.UseCustomPattern && (!String.IsNullOrWhiteSpace(part.CustomPattern) || String.Equals(part.CustomPattern, "/"))) { pattern = part.CustomPattern; } - // Convert the pattern and route values via tokens + // Convert the pattern and route values via tokens. var path = _tokenizer.Replace(pattern, BuildTokenContext(part.ContentItem), new ReplaceOptions { Encoding = ReplaceOptions.NoEncode }); - // removing trailing slashes in case the container is empty, and tokens are base on it (e.g. home page) - while(path.StartsWith("/")) { + // Removing trailing slashes in case the container is empty, and tokens are base on it (e.g. home page). + while (path.StartsWith("/")) { path = path.Substring(1); } @@ -68,9 +65,7 @@ namespace Orchard.Autoroute.Services { public void PublishAlias(AutoroutePart part) { var displayRouteValues = _contentManager.GetItemMetadata(part).DisplayRouteValues; - _aliasService.Replace(part.DisplayAlias, displayRouteValues, AliasSource); - _routeEvents.Routed(part, part.DisplayAlias); } @@ -97,7 +92,7 @@ namespace Orchard.Autoroute.Services { patterns.Add(routePattern); settings.Patterns = patterns; - // define which pattern is the default + // Define which pattern is the default. if (makeDefault || settings.Patterns.Count == 1) { settings.DefaultPatternIndex = settings.Patterns.IndexOf(routePattern); } @@ -113,26 +108,33 @@ namespace Orchard.Autoroute.Services { public RoutePattern GetDefaultPattern(string contentType) { var settings = GetTypePartSettings(contentType).GetModel(); - // return a default pattern if none is defined - if(settings.DefaultPatternIndex < settings.Patterns.Count) { - return settings.Patterns.ElementAt(settings.DefaultPatternIndex); + // Return a default pattern if none is defined. + if (settings.DefaultPatternIndex < settings.Patterns.Count) { + return settings.Patterns.ElementAt(settings.DefaultPatternIndex); } - return new RoutePattern {Name = "Title", Description = "my-title", Pattern = "{Content.Slug}"}; + return new RoutePattern { Name = "Title", Description = "my-title", Pattern = "{Content.Slug}" }; } public void RemoveAliases(AutoroutePart part) { - _aliasService.Delete(part.Path, AliasSource); - } + // https://github.com/OrchardCMS/Orchard/issues/5137 + // If the alias of the specified part is empty while not being the homepage, + // we need to make sure we are not removing all empty aliases in order to prevent losing the homepage content item being the homepage. + if (String.IsNullOrWhiteSpace(part.Path)) { + if (!IsHomePage(part)) { + // The item being removed is NOT the homepage, so we need to make sure we're not removing the alias for the homepage. + var aliasRecordId = GetHomePageAliasRecordId(); - private SettingsDictionary GetTypePartSettings(string contentType) { - var contentDefinition = _contentDefinitionManager.GetTypeDefinition(contentType); - - if (contentDefinition == null) { - throw new OrchardException(T("Unknown content type: {0}", contentType)); + // Remove all aliases EXCEPT for the alias of the homepage. + _aliasStorage.Remove(x => x.Path == part.Path && x.Source == AliasSource && x.Id != aliasRecordId); + + // Done. + return; + } } - - return contentDefinition.Parts.First(x => x.PartDefinition.Name == "AutoroutePart").Settings; + + // Safe to delete all aliases for the specified part since it is definitely not the homepage. + _aliasService.Delete(part.Path, AliasSource); } public string GenerateUniqueSlug(AutoroutePart part, IEnumerable existingPaths) { @@ -142,22 +144,10 @@ namespace Orchard.Autoroute.Services { int? version = existingPaths.Select(s => GetSlugVersion(part.Path, s)).OrderBy(i => i).LastOrDefault(); return version != null - ? string.Format("{0}-{1}", part.Path, version) + ? String.Format("{0}-{1}", part.Path, version) : part.Path; } - 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 GetSimilarPaths(string path) { return _contentManager.Query() @@ -170,11 +160,10 @@ namespace Orchard.Autoroute.Services { } public bool ProcessPath(AutoroutePart part) { - var pathsLikeThis = GetSimilarPaths(part.Path).ToArray(); // Don't include *this* part in the list - // of slugs to consider for conflict detection + // of slugs to consider for conflict detection. pathsLikeThis = pathsLikeThis.Where(p => p.ContentItem.Id != part.ContentItem.Id).ToArray(); if (pathsLikeThis.Any()) { @@ -188,5 +177,37 @@ namespace Orchard.Autoroute.Services { return true; } + + private bool IsHomePage(IContent content) { + var homePageRoute = _aliasService.Get(""); + var homePageId = homePageRoute.ContainsKey("id") ? XmlHelper.Parse((string)homePageRoute["id"]) : default(int?); + return content.Id == homePageId; + } + + private int GetHomePageAliasRecordId() { + return _aliasStorage.List(x => x.Path == "").First().Item5; + } + + private SettingsDictionary GetTypePartSettings(string contentType) { + var contentDefinition = _contentDefinitionManager.GetTypeDefinition(contentType); + + if (contentDefinition == null) { + throw new OrchardException(T("Unknown content type: {0}", contentType)); + } + + return contentDefinition.Parts.First(x => x.PartDefinition.Name == "AutoroutePart").Settings; + } + + private static int? GetSlugVersion(string path, string potentialConflictingPath) { + int v; + var slugParts = potentialConflictingPath.Split(new[] { path }, StringSplitOptions.RemoveEmptyEntries); + + if (slugParts.Length == 0) + return 2; + + return Int32.TryParse(slugParts[0].TrimStart('-'), out v) + ? (int?)++v + : null; + } } } diff --git a/src/Orchard.Web/Modules/Orchard.Autoroute/Views/EditorTemplates/Parts.Autoroute.Edit.cshtml b/src/Orchard.Web/Modules/Orchard.Autoroute/Views/EditorTemplates/Parts.Autoroute.Edit.cshtml index 80d8ed1f4..e8b8a01ae 100644 --- a/src/Orchard.Web/Modules/Orchard.Autoroute/Views/EditorTemplates/Parts.Autoroute.Edit.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Autoroute/Views/EditorTemplates/Parts.Autoroute.Edit.cshtml @@ -1,8 +1,7 @@ -@model Orchard.Autoroute.ViewModels.AutoroutePartEditViewModel -@using Orchard.Autoroute -@using Orchard.Mvc.Extensions -@using Orchard.Utility.Extensions; +@using Orchard.Autoroute @using Orchard.Environment.Configuration +@using Orchard.Mvc.Extensions +@model Orchard.Autoroute.ViewModels.AutoroutePartEditViewModel @if(Model.Settings.DefaultPatternIndex == -1) {
@T("The current Content Type does not have a default Autoroute Pattern. Please edit the settings first.")
diff --git a/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeStepQueue.cs b/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeStepQueue.cs index 0a7e31ff7..899d3ac9c 100644 --- a/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeStepQueue.cs +++ b/src/Orchard.Web/Modules/Orchard.Recipes/Services/RecipeStepQueue.cs @@ -57,7 +57,7 @@ namespace Orchard.Recipes.Services { _appDataFolder.DeleteFile(stepPath); } - if (stepIndex < 1) { + if (stepIndex < 0) { _appDataFolder.DeleteFile(Path.Combine(_recipeQueueFolder, executionId)); } @@ -86,4 +86,4 @@ namespace Orchard.Recipes.Services { return lastIndex; } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Taxonomies/Handlers/TermsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Taxonomies/Handlers/TermsPartHandler.cs index 3a8e9087d..69d1cf093 100644 --- a/src/Orchard.Web/Modules/Orchard.Taxonomies/Handlers/TermsPartHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Taxonomies/Handlers/TermsPartHandler.cs @@ -91,7 +91,8 @@ namespace Orchard.Taxonomies.Handlers { // Fires off a processing engine task to run the count processing after the request so it's non-blocking. private void RecalculateCount(IProcessingEngine processingEngine, ShellSettings shellSettings, IShellDescriptorManager shellDescriptorManager, TermsPart part) { - processingEngine.AddTask(shellSettings, shellDescriptorManager.GetShellDescriptor(), "ITermCountProcessor.Process", new Dictionary { { "termsPartId", part.ContentItem.Id } }); + var termPartRecordIds = part.Terms.Select(t => t.TermRecord.Id).ToArray(); + processingEngine.AddTask(shellSettings, shellDescriptorManager.GetShellDescriptor(), "ITermCountProcessor.Process", new Dictionary { { "termPartRecordIds", termPartRecordIds } }); } diff --git a/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/ITermCountProcessor.cs b/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/ITermCountProcessor.cs index ceeb3ae09..3e6fc1cad 100644 --- a/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/ITermCountProcessor.cs +++ b/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/ITermCountProcessor.cs @@ -7,6 +7,6 @@ using Orchard.Events; namespace Orchard.Taxonomies.Services { public interface ITermCountProcessor : IEventHandler { - void Process(int termsPartId); + void Process(params int[] termPartRecordIds); } } diff --git a/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TaxonomyService.cs b/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TaxonomyService.cs index a442baa9d..b03f3dbb8 100644 --- a/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TaxonomyService.cs +++ b/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TaxonomyService.cs @@ -9,6 +9,9 @@ using Orchard.ContentManagement.MetaData; using Orchard.Core.Common.Models; using Orchard.Core.Title.Models; using Orchard.Data; +using Orchard.Environment.Configuration; +using Orchard.Environment.Descriptor; +using Orchard.Environment.State; using Orchard.Localization; using Orchard.Logging; using Orchard.Security; @@ -23,6 +26,10 @@ namespace Orchard.Taxonomies.Services { private readonly IAuthorizationService _authorizationService; private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IOrchardServices _services; + private readonly IProcessingEngine _processingEngine; + private readonly ShellSettings _shellSettings; + private readonly IShellDescriptorManager _shellDescriptorManager; + public TaxonomyService( IRepository termContentItemRepository, @@ -30,13 +37,20 @@ namespace Orchard.Taxonomies.Services { INotifier notifier, IContentDefinitionManager contentDefinitionManager, IAuthorizationService authorizationService, - IOrchardServices services) { + IOrchardServices services, + IProcessingEngine processingEngine, + ShellSettings shellSettings, + IShellDescriptorManager shellDescriptorManager) + { _termContentItemRepository = termContentItemRepository; _contentManager = contentManager; _notifier = notifier; _authorizationService = authorizationService; _contentDefinitionManager = contentDefinitionManager; _services = services; + _processingEngine = processingEngine; + _shellSettings = shellSettings; + _shellDescriptorManager = shellDescriptorManager; Logger = NullLogger.Instance; T = NullLocalizer.Instance; @@ -230,24 +244,31 @@ namespace Orchard.Taxonomies.Services { var termsPart = contentItem.As(); // removing current terms for specific field - var fieldIndexes = termsPart.Terms.Select((t, i) => new {Term = t, Index = i}) + var termList = termsPart.Terms.Select((t, i) => new {Term = t, Index = i}) .Where(x => x.Term.Field == field) - .Select(x => x.Index) - .OrderByDescending(i => i) + .Select(x => x) + .OrderByDescending(i => i.Index) .ToList(); - - foreach(var x in fieldIndexes) { - termsPart.Terms.RemoveAt(x); + + foreach (var x in termList) { + termsPart.Terms.RemoveAt(x.Index); } // adding new terms list foreach(var term in terms) { + // Remove the newly added terms because they will get processed by the Published-Event + termList.RemoveAll(t => t.Term.Id == term.Id); termsPart.Terms.Add( new TermContentItem { TermsPartRecord = termsPart.Record, TermRecord = term.Record, Field = field }); } + + var termPartRecordIds = termList.Select(t => t.Term.TermRecord.Id).ToArray(); + _processingEngine.AddTask(_shellSettings, _shellDescriptorManager.GetShellDescriptor(), "ITermCountProcessor.Process", new Dictionary { { "termPartRecordIds", termPartRecordIds } }); + + } public IContentQuery GetContentItemsQuery(TermPart term, string fieldName = null) { diff --git a/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TermCountProcessor.cs b/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TermCountProcessor.cs index cc1a24a96..8b908bc2c 100644 --- a/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TermCountProcessor.cs +++ b/src/Orchard.Web/Modules/Orchard.Taxonomies/Services/TermCountProcessor.cs @@ -15,16 +15,10 @@ namespace Orchard.Taxonomies.Services { _taxonomyService = taxonomyService; } - public void Process(int termsPartId) { - var termsPart = _contentManager.Get(termsPartId); + public void Process(params int[] termPartRecordIds) { - if (termsPart == null) { - return; - } - - // Retrieve the number of associated content items, for the whole hierarchy - foreach (var term in termsPart.Terms) { - var termPart = _taxonomyService.GetTerm(term.TermRecord.Id); + foreach (var id in termPartRecordIds) { + var termPart = _taxonomyService.GetTerm(id); while (termPart != null) { termPart.Count = (int)_taxonomyService.GetContentItemsCount(termPart);