mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-13 18:34:51 +08:00
Implementing Alias Breadcrumb Link to be able to add dynamically generated breadcrumb menu items for content items not being in the menu hierarchy, see #3537
This commit is contained in:
@@ -49,7 +49,7 @@ namespace Orchard.Core.Navigation.Drivers {
|
||||
|
||||
var menuName = menu.As<TitlePart>().Title.HtmlClassify();
|
||||
var currentCulture = _workContextAccessor.GetContext().CurrentCulture;
|
||||
var menuItems = _navigationManager.BuildMenu(menu);
|
||||
var menuItems = _navigationManager.BuildMenu(menu, part.Breadcrumb);
|
||||
var localized = new List<MenuItem>();
|
||||
foreach(var menuItem in menuItems) {
|
||||
// if there is no associated content, it as culture neutral
|
||||
|
@@ -44,21 +44,29 @@ namespace Orchard.Core.Navigation.Services {
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public IEnumerable<MenuItem> BuildMenu(string menuName) {
|
||||
return BuildMenu(menuName, false);
|
||||
}
|
||||
|
||||
public IEnumerable<MenuItem> BuildMenu(string menuName, bool isBreadcrumb) {
|
||||
var sources = GetSources(menuName);
|
||||
var hasDebugShowAllMenuItems = _authorizationService.TryCheckAccess(Permission.Named("DebugShowAllMenuItems"), _orchardServices.WorkContext.CurrentUser, null);
|
||||
return FinishMenu(Reduce(Filter(Merge(sources)), menuName == "admin", hasDebugShowAllMenuItems).ToArray());
|
||||
return FinishMenu(Reduce(Filter(Merge(sources), isBreadcrumb), menuName == "admin", hasDebugShowAllMenuItems).ToArray());
|
||||
}
|
||||
|
||||
public IEnumerable<MenuItem> BuildMenu(IContent menu) {
|
||||
return BuildMenu(menu,false);
|
||||
}
|
||||
|
||||
public IEnumerable<MenuItem> BuildMenu(IContent menu, bool isBreadcrumb) {
|
||||
var sources = GetSources(menu);
|
||||
var hasDebugShowAllMenuItems = _authorizationService.TryCheckAccess(Permission.Named("DebugShowAllMenuItems"), _orchardServices.WorkContext.CurrentUser, null);
|
||||
return FinishMenu(Reduce(Arrange(Filter(Merge(sources))), false, hasDebugShowAllMenuItems).ToArray());
|
||||
return FinishMenu(Reduce(Arrange(Filter(Merge(sources), isBreadcrumb)), false, hasDebugShowAllMenuItems).ToArray());
|
||||
}
|
||||
|
||||
public string GetNextPosition(IContent menu) {
|
||||
var sources = GetSources(menu);
|
||||
var hasDebugShowAllMenuItems = _authorizationService.TryCheckAccess(Permission.Named("DebugShowAllMenuItems"), _orchardServices.WorkContext.CurrentUser, null);
|
||||
return Position.GetNext(Reduce(Arrange(Filter(Merge(sources))), false, hasDebugShowAllMenuItems).ToArray());
|
||||
return Position.GetNext(Reduce(Arrange(Filter(Merge(sources), false)), false, hasDebugShowAllMenuItems).ToArray());
|
||||
}
|
||||
|
||||
public IEnumerable<string> BuildImageSets(string menuName) {
|
||||
@@ -74,10 +82,10 @@ namespace Orchard.Core.Navigation.Services {
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
private IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> menuItems) {
|
||||
private IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> menuItems, bool isBreadcrumb) {
|
||||
IEnumerable<MenuItem> result = menuItems;
|
||||
foreach(var filter in _navigationFilters) {
|
||||
result = filter.Filter(result);
|
||||
foreach (var filter in _navigationFilters) {
|
||||
result = filter.Filter(result, isBreadcrumb);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@@ -0,0 +1,22 @@
|
||||
using Orchard.Data.Migration;
|
||||
using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.Environment.Extensions;
|
||||
|
||||
namespace Orchard.Alias {
|
||||
[OrchardFeature("Orchard.Alias.BreadcrumbLink")]
|
||||
public class AliasBreadcrumbMigration : DataMigrationImpl {
|
||||
public int Create() {
|
||||
ContentDefinitionManager.AlterTypeDefinition("AliasBreadcrumbMenuItem",
|
||||
cfg => cfg
|
||||
.WithPart("MenuPart")
|
||||
.WithPart("CommonPart")
|
||||
.WithIdentity()
|
||||
.DisplayedAs("Alias Breadcrumb Link")
|
||||
.WithSetting("Description", "A menu item that can be used to generate breadcrumb links, using Alias data, to content items without explicitly adding links to those content items in the menu.")
|
||||
.WithSetting("Stereotype", "MenuItem")
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -27,6 +27,5 @@ namespace Orchard.Alias {
|
||||
);
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -18,3 +18,8 @@ Features:
|
||||
Description: Synchronizes aliases when created from different servers.
|
||||
Dependencies: Orchard.Alias
|
||||
Category: Content
|
||||
Orchard.Alias.BreadcrumbLink:
|
||||
Name: Alias Breadcrumb Link
|
||||
Description: Provides Alias Breadcrumb Link menu item type that can be used to generate breadcrumb links, using Alias data, to content items without explicitly adding links to those content items in the menu.
|
||||
Dependencies: Orchard.Alias
|
||||
Category: Navigation
|
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Mvc;
|
||||
using Orchard.UI.Navigation;
|
||||
|
||||
namespace Orchard.Alias.Navigation {
|
||||
|
||||
[OrchardFeature("Orchard.Alias.BreadcrumbLink")]
|
||||
public class NavigationAliasProvider : INavigationFilter {
|
||||
private readonly IAliasService _aliasService;
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IHttpContextAccessor _hca;
|
||||
|
||||
public NavigationAliasProvider(
|
||||
IAliasService aliasService,
|
||||
IContentManager contentManager,
|
||||
IHttpContextAccessor hca) {
|
||||
_aliasService = aliasService;
|
||||
_contentManager = contentManager;
|
||||
_hca = hca;
|
||||
}
|
||||
|
||||
public IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> items, bool isBreadcrumb = false) {
|
||||
foreach (var item in items) {
|
||||
if (item.Content != null && item.Content.ContentItem.ContentType == "AliasBreadcrumbMenuItem") {
|
||||
if (isBreadcrumb) {
|
||||
var request = _hca.Current().Request;
|
||||
var path = request.Path;
|
||||
var appPath = request.ApplicationPath ?? "/";
|
||||
var requestUrl = (path.StartsWith(appPath) ? path.Substring(appPath.Length) : path).TrimStart('/');
|
||||
var endsWithSlash = requestUrl.EndsWith("/");
|
||||
|
||||
var menuPosition = item.Position;
|
||||
|
||||
var urlLevel = endsWithSlash ? requestUrl.Count(l => l == '/') - 1 : requestUrl.Count(l => l == '/');
|
||||
var menuLevel = menuPosition.Count(l => l == '.');
|
||||
|
||||
// Checking menu item if it's the leaf element or it's an intermediate element
|
||||
// or it's an unneccessary element according to the url.
|
||||
RouteValueDictionary contentRoute;
|
||||
if (menuLevel == urlLevel) {
|
||||
contentRoute = _aliasService.Get(requestUrl);
|
||||
}
|
||||
else {
|
||||
// If menuLevel doesn't equal with urlLevel it can mean that this menu item is
|
||||
// an intermediate element (the difference is a positive value) or this menu
|
||||
// item is lower in the navigation hierarchy according to the url (negative
|
||||
// value). If the value is negative, removing the menu item, if the value
|
||||
// is positive finding its place in the hierarchy.
|
||||
var levelDifference = urlLevel - menuLevel;
|
||||
if (levelDifference > 0) {
|
||||
if (endsWithSlash) {
|
||||
levelDifference += levelDifference;
|
||||
}
|
||||
for (int i = 0; i < levelDifference; i++) {
|
||||
requestUrl = requestUrl.Remove(requestUrl.LastIndexOf('/'));
|
||||
path = path.Remove(path.LastIndexOf('/'));
|
||||
}
|
||||
contentRoute = _aliasService.Get(requestUrl);
|
||||
if (contentRoute == null) {
|
||||
// After the exact number of segments is cut out from the url and the
|
||||
// currentRoute is still null, trying another check with the added slash,
|
||||
// because we don't know if the alias was created with a slash at the end or not.
|
||||
contentRoute = _aliasService.Get(requestUrl.Insert(requestUrl.Length, "/"));
|
||||
path = path.Insert(path.Length, "/");
|
||||
}
|
||||
}
|
||||
else {
|
||||
contentRoute = new RouteValueDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
object id;
|
||||
contentRoute.TryGetValue("Id", out id);
|
||||
int contentId;
|
||||
int.TryParse(id as string, out contentId);
|
||||
if (contentId == 0) {
|
||||
// If failed to get the Id's value from currentRoute, transform the alias to the virtual path
|
||||
// and try to get the content item's id from there. E.g. "Blogs/Blog/Item?blogId=12" where
|
||||
// the last digits represents the content item's id. If there is a match in the path we get
|
||||
// the digits after the equality sign.
|
||||
// There is an another type of the routes: like "Contents/Item/Display/13", but when the
|
||||
// content item's route is in this form we already have the id from contentRoute.TryGetValue("Id", out id).
|
||||
var virtualPath = _aliasService.LookupVirtualPaths(contentRoute, _hca.Current()).FirstOrDefault();
|
||||
int.TryParse(virtualPath != null ? virtualPath.VirtualPath.Substring(virtualPath.VirtualPath.LastIndexOf('=') + 1) : "0", out contentId);
|
||||
}
|
||||
if (contentId != 0) {
|
||||
var currentContentItem = _contentManager.Get(contentId);
|
||||
if (currentContentItem != null) {
|
||||
var menuText = _contentManager.GetItemMetadata(currentContentItem).DisplayText;
|
||||
var routes = _contentManager.GetItemMetadata(currentContentItem).DisplayRouteValues;
|
||||
|
||||
var inserted = new MenuItem {
|
||||
Text = new LocalizedString(menuText),
|
||||
IdHint = item.IdHint,
|
||||
Classes = item.Classes,
|
||||
Url = path,
|
||||
Href = item.Href,
|
||||
LinkToFirstChild = false,
|
||||
RouteValues = routes,
|
||||
LocalNav = item.LocalNav,
|
||||
Items = Enumerable.Empty<MenuItem>(),
|
||||
Position = menuPosition,
|
||||
Permissions = item.Permissions,
|
||||
Content = currentContentItem
|
||||
};
|
||||
|
||||
yield return inserted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -101,10 +101,12 @@
|
||||
<Content Include="Views\Admin\Edit.cshtml" />
|
||||
<Content Include="Views\Admin\IndexUnmanaged.cshtml" />
|
||||
<Content Include="Web.config" />
|
||||
<Compile Include="AliasBreadcrumbLinkMigrations.cs" />
|
||||
<Compile Include="Implementation\Updater\AliasHolderUpdater.cs" />
|
||||
<Compile Include="Implementation\Updater\AliasUpdateCursor.cs" />
|
||||
<Compile Include="Implementation\Updater\AliasUpdaterBackgroundTask.cs" />
|
||||
<Compile Include="Implementation\Updater\IAliasUpdateCursor.cs" />
|
||||
<Compile Include="Navigation\NavigationAliasProvider.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Content Include="Module.txt" />
|
||||
</ItemGroup>
|
||||
|
@@ -21,7 +21,7 @@ namespace Orchard.Projections.Navigation {
|
||||
_projectionManager = projectionManager;
|
||||
}
|
||||
|
||||
public IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> items) {
|
||||
public IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> items, bool isBreadcrumb) {
|
||||
|
||||
foreach (var item in items) {
|
||||
if (item.Content != null && item.Content.ContentItem.ContentType == "NavigationQueryMenuItem") {
|
||||
|
@@ -24,7 +24,7 @@ namespace Orchard.Taxonomies.Navigation {
|
||||
_taxonomyService = taxonomyService;
|
||||
}
|
||||
|
||||
public IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> items) {
|
||||
public IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> items, bool isBreadcrumb) {
|
||||
|
||||
foreach (var item in items) {
|
||||
if (item.Content != null && item.Content.ContentItem.ContentType == "TaxonomyNavigationMenuItem") {
|
||||
|
@@ -5,6 +5,6 @@ namespace Orchard.UI.Navigation {
|
||||
/// Provides a way to alter the main navigation, for instance by dynamically injecting new items
|
||||
/// </summary>
|
||||
public interface INavigationFilter : IDependency {
|
||||
IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> menuItems);
|
||||
IEnumerable<MenuItem> Filter(IEnumerable<MenuItem> menuItems, bool isBreadcrumb = false);
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,9 @@ using Orchard.ContentManagement;
|
||||
namespace Orchard.UI.Navigation {
|
||||
public interface INavigationManager : IDependency {
|
||||
IEnumerable<MenuItem> BuildMenu(string menuName);
|
||||
IEnumerable<MenuItem> BuildMenu(string menuName, bool isBreadcrumbMenu);
|
||||
IEnumerable<MenuItem> BuildMenu(IContent menu);
|
||||
IEnumerable<MenuItem> BuildMenu(IContent menu, bool isBreadcrumbMenu);
|
||||
IEnumerable<string> BuildImageSets(string menuName);
|
||||
string GetUrl(string menuItemUrl, RouteValueDictionary routeValueDictionary);
|
||||
string GetNextPosition(IContent menu);
|
||||
|
Reference in New Issue
Block a user