Implementing Menu Widgets

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros 2012-05-16 18:07:48 -07:00
parent ea1e5fba36
commit 1900aaa01a
33 changed files with 660 additions and 312 deletions

View File

@ -1,6 +1,8 @@
using Orchard.Commands;
using System.Linq;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Title.Models;
namespace Orchard.Core.Navigation.Commands {
public class MenuCommands : DefaultOrchardCommandHandler {
@ -20,19 +22,48 @@ namespace Orchard.Core.Navigation.Commands {
public string Url { get; set; }
[OrchardSwitch]
public bool OnMainMenu { get; set; }
public string MenuName { get; set; }
[CommandName("menuitem create")]
[CommandHelp("menuitem create /MenuPosition:<position> /MenuText:<text> /Url:<url> [/OnMainMenu:true|false]\r\n\t" + "Creates a new menu item")]
[OrchardSwitches("MenuPosition,MenuText,Url,OnMainMenu")]
[CommandHelp("menuitem create /MenuPosition:<position> /MenuText:<text> /Url:<url> /MenuName:<name>\r\n\t" + "Creates a new menu item")]
[OrchardSwitches("MenuPosition,MenuText,Url,MenuName")]
public void Create() {
// flushes before doing a query in case a previous command created the menu
_contentManager.Flush();
var menu = _contentManager.Query<TitlePart, TitlePartRecord>()
.Where(x => x.Title == MenuName)
.ForType("Menu")
.Slice(0, 1)
.FirstOrDefault();
if(menu == null) {
Context.Output.WriteLine(T("Menu not found.").Text);
return;
}
var menuItem = _contentManager.Create("MenuItem");
menuItem.As<MenuPart>().MenuPosition = MenuPosition;
menuItem.As<MenuPart>().MenuText = T(MenuText).ToString();
menuItem.As<MenuPart>().OnMainMenu = OnMainMenu;
menuItem.As<MenuPart>().MenuRecord = menu.ContentItem.Record;
menuItem.As<MenuItemPart>().Url = Url;
Context.Output.WriteLine(T("Menu item created successfully.").Text);
}
[CommandName("menu create")]
[CommandHelp("menu create /MenuName:<name>\r\n\t" + "Creates a new menu")]
[OrchardSwitches("MenuName")]
public void CreateMenu() {
if (string.IsNullOrWhiteSpace(MenuName)) {
Context.Output.WriteLine(T("Menu name can't be empty.").Text);
return;
}
var menu = _contentManager.Create("Menu");
menu.As<TitlePart>().Title = MenuName;
Context.Output.WriteLine(T("Menu created successfully.").Text);
}
}
}

View File

@ -106,35 +106,6 @@ namespace Orchard.Core.Navigation.Controllers {
};
}
public ActionResult Create() {
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult Create(NavigationManagementViewModel model) {
if (!Services.Authorizer.Authorize(Permissions.ManageMainMenu, T("Couldn't manage the main menu")))
return new HttpUnauthorizedResult();
var menuPart = Services.ContentManager.New<MenuPart>("MenuItem");
menuPart.OnMainMenu = true;
menuPart.MenuText = model.NewMenuItem.Text;
menuPart.MenuPosition = model.NewMenuItem.Position;
if (string.IsNullOrEmpty(menuPart.MenuPosition))
menuPart.MenuPosition = Position.GetNext(_navigationManager.BuildMenu("main"));
var menuItem = menuPart.As<MenuItemPart>();
menuItem.Url = model.NewMenuItem.Url;
if (!ModelState.IsValid) {
Services.TransactionManager.Cancel();
return View("Index", model);
}
Services.ContentManager.Create(menuPart);
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult Delete(int id) {
if (!Services.Authorizer.Authorize(Permissions.ManageMainMenu, T("Couldn't manage the main menu")))
@ -143,10 +114,7 @@ namespace Orchard.Core.Navigation.Controllers {
MenuPart menuPart = _menuService.Get(id);
if (menuPart != null) {
if (menuPart.Is<MenuItemPart>())
_menuService.Delete(menuPart);
else
menuPart.OnMainMenu = false;
_menuService.Delete(menuPart);
}
return RedirectToAction("Index");
@ -165,7 +133,7 @@ namespace Orchard.Core.Navigation.Controllers {
return new HttpUnauthorizedResult();
// create a new temporary menu item
MenuPart menuPart = Services.ContentManager.New<MenuPart>(id);
var menuPart = Services.ContentManager.New<MenuPart>(id);
if (menuPart == null)
return HttpNotFound();
@ -178,7 +146,7 @@ namespace Orchard.Core.Navigation.Controllers {
try {
// filter the content items for this specific menu
menuPart.MenuPosition = Position.GetNext(_navigationManager.BuildMenu("main").Where(x => x.MenuId == menuId));
menuPart.MenuPosition = Position.GetNext(_navigationManager.BuildMenu(menu));
dynamic model = Services.ContentManager.BuildEditor(menuPart);
@ -197,7 +165,7 @@ namespace Orchard.Core.Navigation.Controllers {
if (!Services.Authorizer.Authorize(Permissions.ManageMainMenu, T("Couldn't manage the main menu")))
return new HttpUnauthorizedResult();
MenuPart menuPart = Services.ContentManager.New<MenuPart>(id);
var menuPart = Services.ContentManager.New<MenuPart>(id);
if (menuPart == null)
return HttpNotFound();
@ -210,11 +178,8 @@ namespace Orchard.Core.Navigation.Controllers {
var model = Services.ContentManager.UpdateEditor(menuPart, this);
menuPart.MenuPosition = Position.GetNext(_navigationManager.BuildMenu("main").Where(x => x.MenuId == menuId));
menuPart.OnMainMenu = true;
// the menu is the container for the menu item
menuPart.As<CommonPart>().Container = menu;
menuPart.MenuPosition = Position.GetNext(_navigationManager.BuildMenu(menu));
menuPart.MenuRecord = menu.Record;
Services.ContentManager.Create(menuPart);

View File

@ -41,7 +41,7 @@ namespace Orchard.Core.Navigation.Drivers {
updater.TryUpdateModel(part, Prefix, null, null);
if (part.OnMainMenu && string.IsNullOrEmpty(part.MenuText))
if (string.IsNullOrEmpty(part.MenuText))
updater.AddModelError("MenuText", T("The MenuText field is required"));
return Editor(part, shapeHelper);
@ -58,16 +58,15 @@ namespace Orchard.Core.Navigation.Drivers {
part.MenuPosition = position;
}
var onMainMenu = context.Attribute(part.PartDefinition.Name, "OnMainMenu");
if (onMainMenu != null) {
part.OnMainMenu = Convert.ToBoolean(onMainMenu);
}
context.ImportAttribute(part.PartDefinition.Name, "Menu", x => part.MenuRecord = context.GetItemFromSession(x).Record);
}
protected override void Exporting(MenuPart part, ContentManagement.Handlers.ExportContentContext context) {
var menuIdentity = _orchardServices.ContentManager.GetItemMetadata(_orchardServices.ContentManager.Get(part.MenuRecord.Id)).Identity;
context.Element(part.PartDefinition.Name).SetAttributeValue("Menu", menuIdentity);
context.Element(part.PartDefinition.Name).SetAttributeValue("MenuText", part.MenuText);
context.Element(part.PartDefinition.Name).SetAttributeValue("MenuPosition", part.MenuPosition);
context.Element(part.PartDefinition.Name).SetAttributeValue("OnMainMenu", part.OnMainMenu);
}
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Navigation.ViewModels;
using Orchard.Core.Title.Models;
using Orchard.Localization;
using Orchard.UI.Navigation;
using Orchard.Utility.Extensions;
namespace Orchard.Core.Navigation.Drivers {
public class MenuWidgetPartDriver : ContentPartDriver<MenuWidgetPart> {
private readonly IContentManager _contentManager;
private readonly INavigationManager _navigationManager;
private readonly IWorkContextAccessor _workContextAccessor;
public MenuWidgetPartDriver(
IContentManager contentManager,
INavigationManager navigationManager,
IWorkContextAccessor workContextAccessor) {
_contentManager = contentManager;
_navigationManager = navigationManager;
_workContextAccessor = workContextAccessor;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
protected override string Prefix {
get {
return "MenuWidget";
}
}
protected override DriverResult Display(MenuWidgetPart part, string displayType, dynamic shapeHelper) {
return ContentShape( "Parts_MenuWidget", () => {
var menu = _contentManager.Get(part.Menu.Id, VersionOptions.Published, new QueryHints().ExpandRecords<TitlePartRecord>());
if(menu == null) {
return null;
}
var menuName = menu.As<TitlePart>().Title.HtmlClassify();
IEnumerable<MenuItem> menuItems = _navigationManager.BuildMenu(menu);
var routeData = _workContextAccessor.GetContext().HttpContext.Request.RequestContext.RouteData;
// Set the currently selected path
Stack<MenuItem> selectedPath = NavigationHelper.SetSelectedPath(menuItems, routeData);
// Populate main nav
if(part.Breadcrumb) {
menuItems = NavigationHelper.SetSelectedPath(menuItems, routeData);
}
dynamic menuShape = shapeHelper.Menu().MenuName(menuName);
NavigationHelper.PopulateMenu(shapeHelper, menuShape, menuShape, menuItems);
return shapeHelper.Parts_MenuWidget(Menu: menuShape);
});
}
protected override DriverResult Editor(MenuWidgetPart part, dynamic shapeHelper) {
return ContentShape("Parts_MenuWidget_Edit", () => {
var model = new MenuWidgetViewModel {
CurrentMenuId = part.Menu == null ? -1 : part.Menu.Id,
StartLevel = part.StartLevel,
StopLevel = part.Levels,
Breadcrumb = part.Breadcrumb,
Menus = _contentManager.Query().ForType("Menu").Join<TitlePartRecord>().OrderBy(x => x.Title).List()
};
return shapeHelper.EditorTemplate(TemplateName: "Parts.MenuWidget.Edit", Model: model, Prefix: Prefix);
});
}
protected override DriverResult Editor(MenuWidgetPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new MenuWidgetViewModel();
if(updater.TryUpdateModel(model, Prefix, null, null)) {
part.StartLevel = model.StartLevel;
part.Levels = model.StopLevel;
part.Breadcrumb = model.Breadcrumb;
part.Menu = _contentManager.Get(model.CurrentMenuId).Record;
}
return Editor(part, shapeHelper);
}
protected override void Importing(MenuWidgetPart part, ImportContentContext context) {
context.ImportAttribute(part.PartDefinition.Name, "StartLevel", x => part.StartLevel = Convert.ToInt32(x));
context.ImportAttribute(part.PartDefinition.Name, "Levels", x => part.Levels = Convert.ToInt32(x));
context.ImportAttribute(part.PartDefinition.Name, "Breadcrumb", x => part.Breadcrumb = Convert.ToBoolean(x));
context.ImportAttribute(part.PartDefinition.Name, "Menu", x => part.Menu = context.GetItemFromSession(x).Record);
}
protected override void Exporting(MenuWidgetPart part, ExportContentContext context) {
var menuIdentity = _contentManager.GetItemMetadata(_contentManager.Get(part.Menu.Id)).Identity;
context.Element(part.PartDefinition.Name).SetAttributeValue("Menu", menuIdentity);
context.Element(part.PartDefinition.Name).SetAttributeValue("StartLevel", part.StartLevel);
context.Element(part.PartDefinition.Name).SetAttributeValue("Levels", part.Levels);
context.Element(part.PartDefinition.Name).SetAttributeValue("Breadcrumb", part.Breadcrumb);
}
}
}

View File

@ -11,7 +11,6 @@ namespace Orchard.Core.Navigation.Handlers {
Filters.Add(StorageFilter.For(menuPartRepository));
OnInitializing<MenuPart>((ctx, x) => {
x.OnMainMenu = false;
x.MenuText = String.Empty;
});
}

View File

@ -0,0 +1,13 @@
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Navigation.Models;
using Orchard.Data;
namespace Orchard.Core.Navigation.Handlers {
public class MenuWidgetPartHandler : ContentHandler {
public MenuWidgetPartHandler(IRepository<MenuWidgetPartRecord> repository) {
Filters.Add(StorageFilter.For(repository));
OnInitializing<MenuWidgetPart>((context, part) => { part.StartLevel = 1; });
}
}
}

View File

@ -6,6 +6,8 @@ namespace Orchard.Core.Navigation {
public class Migrations : DataMigrationImpl {
public int Create() {
ContentDefinitionManager.AlterPartDefinition("MenuPart", builder => builder.Attachable());
SchemaBuilder.CreateTable("MenuItemPartRecord",
table => table
.ContentPartRecord()
@ -17,14 +19,39 @@ namespace Orchard.Core.Navigation {
.ContentPartRecord()
.Column<string>("MenuText")
.Column<string>("MenuPosition")
.Column<bool>("OnMainMenu")
.Column<int>("MenuRecord_id")
);
ContentDefinitionManager.AlterTypeDefinition("Page", cfg => cfg.WithPart("MenuPart"));
ContentDefinitionManager.AlterTypeDefinition("MenuItem", cfg => cfg.WithPart("MenuPart"));
ContentDefinitionManager.AlterPartDefinition("MenuPart", builder => builder.Attachable());
return 1;
ContentDefinitionManager.AlterTypeDefinition("MenuItem", cfg => cfg
.WithPart("MenuPart")
.WithPart("CommonPart")
.DisplayedAs("Custom Link")
.WithSetting("Description", "Represents a simple custom link with a text and an url.")
.WithSetting("Stereotype", "MenuItem") // because we declare a new stereotype, the Shape MenuItem_Edit is needed
);
ContentDefinitionManager.AlterTypeDefinition("Menu", cfg => cfg
.WithPart("CommonPart", p => p.WithSetting("OwnerEditorSettings.ShowOwnerEditor", "false"))
.WithPart("TitlePart")
.WithPart("Identity")
);
SchemaBuilder.CreateTable("MenuWidgetPartRecord", table => table
.ContentPartRecord()
.Column<int>("StartLevel")
.Column<int>("Levels")
.Column<bool>("Breadcrumb")
.Column<int>("Menu_id")
);
ContentDefinitionManager.AlterTypeDefinition("MenuWidget", cfg => cfg
.WithPart("CommonPart")
.WithPart("WidgetPart")
.WithPart("MenuWidgetPart")
.WithSetting("Stereotype", "Widget")
);
return 3;
}
public int UpdateFrom1() {
@ -54,6 +81,28 @@ namespace Orchard.Core.Navigation {
.WithPart("Identity")
);
SchemaBuilder.CreateTable("MenuWidgetPartRecord",table => table
.ContentPartRecord()
.Column<int>("StartLevel")
.Column<int>("Levels")
.Column<bool>("Breadcrumb")
.Column<int>("Menu_id")
);
ContentDefinitionManager.AlterTypeDefinition("MenuWidget", cfg => cfg
.WithPart("CommonPart")
.WithPart("WidgetPart")
.WithPart("MenuWidgetPart")
.WithSetting("Stereotype", "Widget")
);
SchemaBuilder
.AlterTable("MenuPartRecord", table => table.DropColumn("OnMainMenu"))
.AlterTable("MenuPartRecord", table => table.AddColumn<int>("MenuRecord_id"))
;
ContentDefinitionManager.AlterTypeDefinition("Page", cfg => cfg.RemovePart("MenuPart"));
return 3;
}
}

View File

@ -0,0 +1,7 @@
using Orchard.ContentManagement.Records;
namespace Orchard.Core.Navigation.Models {
public class ContentMenuItemPartRecord : ContentPartRecord {
public virtual ContentItemRecord ContentItemRecord { get; set; }
}
}

View File

@ -1,9 +1,8 @@
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement;
namespace Orchard.Core.Navigation.Models {
public class MenuItemPart : ContentPart<MenuItemPartRecord> {
[Required]
public string Url {
get { return Record.Url; }
set { Record.Url = value; }

View File

@ -1,12 +1,13 @@
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace Orchard.Core.Navigation.Models {
public class MenuPart : ContentPart<MenuPartRecord> {
public bool OnMainMenu {
get { return Record.OnMainMenu; }
set { Record.OnMainMenu = value; }
public ContentItemRecord MenuRecord {
get { return Record.MenuRecord; }
set { Record.MenuRecord = value; }
}
[StringLength(MenuPartRecord.DefaultMenuTextLength)]

View File

@ -8,6 +8,6 @@ namespace Orchard.Core.Navigation.Models {
[StringLength(DefaultMenuTextLength)]
public virtual string MenuText { get; set; }
public virtual string MenuPosition { get; set; }
public virtual bool OnMainMenu { get; set; }
public virtual ContentItemRecord MenuRecord { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace Orchard.Core.Navigation.Models {
public class MenuWidgetPart : ContentPart<MenuWidgetPartRecord> {
public int StartLevel {
get { return Record.StartLevel; }
set { Record.StartLevel = value; }
}
public int Levels {
get { return Record.Levels; }
set { Record.Levels = value; }
}
public bool Breadcrumb {
get { return Record.Breadcrumb; }
set { Record.Breadcrumb = value; }
}
public ContentItemRecord Menu {
get { return Record.Menu; }
set { Record.Menu = value; }
}
}
}

View File

@ -0,0 +1,11 @@
using Orchard.ContentManagement.Records;
namespace Orchard.Core.Navigation.Models {
public class MenuWidgetPartRecord : ContentPartRecord {
public virtual int StartLevel { get; set; }
public virtual int Levels { get; set; }
public virtual bool Breadcrumb { get; set; }
public virtual ContentItemRecord Menu { get; set; }
}
}

View File

@ -2,4 +2,6 @@
<Place Parts_Navigation_Menu_Edit="Content:9"/>
<Place Parts_Navigation_AdminMenu_Edit="Content:9.1"/>
<Place Parts_MenuItem_Edit="Content:10"/>
<Place Parts_MenuWidget_Edit="Content:10"/>
<Place Parts_MenuWidget="Content"/>
</Placement>

View File

@ -1,37 +1,34 @@
using System.Web;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Navigation.Models;
using Orchard.Localization;
using Orchard.UI.Navigation;
namespace Orchard.Core.Navigation.Services {
[UsedImplicitly]
public class MainMenuNavigationProvider : INavigationProvider {
public class DefaultMenuProvider : IMenuProvider {
private readonly IContentManager _contentManager;
public MainMenuNavigationProvider(IContentManager contentManager) {
public DefaultMenuProvider(IContentManager contentManager) {
_contentManager = contentManager;
}
public string MenuName { get { return "main"; } }
public void GetMenu(IContent menu, NavigationBuilder builder) {
var menuParts = _contentManager
.Query<MenuPart, MenuPartRecord>()
.Where(x => x.MenuRecord.Id == menu.Id)
.WithQueryHints(new QueryHints().ExpandRecords<MenuItemPartRecord>())
.List();
public void GetNavigation(NavigationBuilder builder) {
var menuParts = _contentManager.Query<MenuPart, MenuPartRecord>().Where(x => x.OnMainMenu).WithQueryHints(new QueryHints().ExpandRecords<MenuItemPartRecord>()).List();
foreach (var menuPart in menuParts) {
if (menuPart != null) {
var part = menuPart;
int menuId = -1;
var commonPart = part.As<CommonPart>().Container;
if(commonPart != null) {
menuId = commonPart.Id;
}
if (part.Is<MenuItemPart>())
builder.Add(new LocalizedString(HttpUtility.HtmlEncode(part.MenuText)), part.MenuPosition, item => item.Url(part.As<MenuItemPart>().Url).MenuId(menuId));
builder.Add(new LocalizedString(HttpUtility.HtmlEncode(part.MenuText)), part.MenuPosition, item => item.Url(part.As<MenuItemPart>().Url));
else
builder.Add(new LocalizedString(HttpUtility.HtmlEncode(part.MenuText)), part.MenuPosition, item => item.Action(_contentManager.GetItemMetadata(part.ContentItem).DisplayRouteValues).MenuId(menuId));
builder.Add(new LocalizedString(HttpUtility.HtmlEncode(part.MenuText)), part.MenuPosition, item => item.Action(_contentManager.GetItemMetadata(part.ContentItem).DisplayRouteValues));
}
}
}

View File

@ -1,7 +1,6 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.Core.Common.Models;
using Orchard.Core.Navigation.Models;
namespace Orchard.Core.Navigation.Services {
@ -14,13 +13,13 @@ namespace Orchard.Core.Navigation.Services {
}
public IEnumerable<MenuPart> Get() {
return _contentManager.Query<MenuPart, MenuPartRecord>().Where(x => x.OnMainMenu).List();
return _contentManager.Query<MenuPart, MenuPartRecord>().List();
}
public IEnumerable<MenuPart> GetMenu(int menuId) {
return _contentManager
.Query<MenuPart, MenuPartRecord>().Where(x => x.OnMainMenu)
.Join<CommonPartRecord>().Where( x => x.Container.Id == menuId)
.Query<MenuPart, MenuPartRecord>()
.Where( x => x.MenuRecord.Id == menuId)
.List();
}

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
namespace Orchard.Core.Navigation.ViewModels {
public class MenuWidgetViewModel {
public IEnumerable<ContentItem> Menus { get; set; }
public int CurrentMenuId { get; set; }
public int StartLevel { get; set; }
public int StopLevel { get; set; }
public bool Breadcrumb { get; set; }
}
}

View File

@ -1,8 +1,5 @@
@model NavigationManagementViewModel
@using Orchard.ContentManagement;
@using Orchard.Core.Navigation.Models;
@using Orchard.Core.Navigation.ViewModels;
@using Orchard.Core.Title.Models
@using Orchard.Utility.Extensions;
@{
@ -21,11 +18,11 @@
<label for="menuId">@T("Current Menu:")</label>
<select id="menuId" name="menuId">
@foreach (var menu in Model.Menus) {
@Html.SelectOption(Model.CurrentMenu.Id, menu.Id, menu.As<TitlePart>().Title)
@Html.SelectOption(Model.CurrentMenu.Id, menu.Id, Html.ItemDisplayText(menu).ToString())
}
</select>
<button type="submit" class="apply-bulk-actions-auto">@T("Show")</button>
@Html.Link(T("Edit").Text, Url.ItemEditUrl(Model.CurrentMenu), new { @class = "button", returnUrl = Request.RawUrl })
@Html.ActionLink(T("Edit").Text, "Edit", "Admin", new { area = "Contents", id = Model.CurrentMenu.Id, returnUrl = Request.RawUrl }, new { @class = "button" })
</fieldset>
}
}

View File

@ -0,0 +1,31 @@
@model Orchard.Core.Navigation.ViewModels.MenuWidgetViewModel
@using Orchard.ContentManagement
@using Orchard.Core.Navigation.Models;
<fieldset>
@Html.LabelFor(m => m.CurrentMenuId, T("For Menu"))
<select id="@Html.FieldIdFor(m => m.CurrentMenuId)" name="@Html.FieldNameFor(m => m.CurrentMenuId)">
@foreach(ContentItem menu in Model.Menus) {
@Html.SelectOption(Model.CurrentMenuId, menu.Id, Html.ItemDisplayText(menu).ToString())
}
</select>
<span class="hint">@T("Select which menu you want to display")</span>
</fieldset>
<fieldset>
<label for="@Html.FieldIdFor(m => m.StartLevel)">@T("Start Level")</label>
@Html.TextBoxFor(m => m.StartLevel, new { @class = "text text-small" })
<span class="hint">@T("The level the menu should start at.")</span>
</fieldset>
<fieldset>
<label for="@Html.FieldIdFor(m => m.StopLevel)">@T("Levels to display")</label>
@Html.TextBoxFor(m => m.StopLevel, new { @class = "text text-small" })
<span class="hint">@T("The number of levels to display, \"0\" meaning all levels.")</span>
</fieldset>
<fieldset>
@Html.EditorFor(m => m.Breadcrumb)
<label class="forcheckbox" for="@Html.FieldIdFor(m => m.Breadcrumb)">@T("Display as Breadcrumb")</label>
<span class="hint">@T("Check to render the path to the current content item.")</span>
</fieldset>

View File

@ -4,12 +4,9 @@
@if (!Model.ContentItem.TypeDefinition.Settings.ContainsKey("Stereotype") || Model.ContentItem.TypeDefinition.Settings["Stereotype"] != "MenuItem") {
<fieldset>
@Html.EditorFor(m => m.OnMainMenu)
<label for="OnMainMenu" class="forcheckbox">@T("Show on main menu")</label>
<div data-controllerid="OnMainMenu" class="">
<label for="MenuText">@T("Menu text")</label>
@Html.TextBoxFor(m => m.MenuText, new { @class = "text-box single-line" })
</div>
<label for="MenuText">@T("Menu text")</label>
@Html.TextBoxFor(m => m.MenuText, new { @class = "text-box single-line" })
<span class="hint">@T("The text that should appear in the menu.")</span>
</fieldset>
}

View File

@ -1 +1 @@
@Display(Model.Content)
@Display(Model.Content)

View File

@ -0,0 +1 @@
@Display(Model.Menu)

View File

@ -131,16 +131,22 @@
<Compile Include="Dashboard\Services\CompilationErrorBanner.cs" />
<Compile Include="Navigation\Commands\MenuCommands.cs" />
<Compile Include="Navigation\Drivers\AdminMenuPartDriver.cs" />
<Compile Include="Navigation\Drivers\MenuWidgetPartDriver.cs" />
<Compile Include="Navigation\Handlers\AdminMenuPartHandler.cs" />
<Compile Include="Navigation\Handlers\MenuWidgetPartHandler.cs" />
<Compile Include="Navigation\Models\AdminMenuPart.cs" />
<Compile Include="Navigation\Models\AdminMenuPartRecord.cs" />
<Compile Include="Navigation\Models\ContentMenuItemPartRecord.cs" />
<Compile Include="Navigation\Models\MenuWidgetPartRecord.cs" />
<Compile Include="Navigation\Models\MenuWidgetPart.cs" />
<Compile Include="Navigation\Services\AdminMenuNavigationProvider.cs" />
<Compile Include="Navigation\Services\DefaultMenuManager.cs" />
<Compile Include="Navigation\Services\IMenuManager.cs" />
<Compile Include="Navigation\Services\MainMenuNavigationProvider.cs" />
<Compile Include="Navigation\Services\DefaultMenuProvider.cs" />
<Compile Include="Navigation\Settings\AdminMenuPartTypeSettings.cs" />
<Compile Include="Contents\ViewModels\ListContentsViewModel.cs" />
<Compile Include="Contents\ViewModels\ListContentTypesViewModel.cs" />
<Compile Include="Navigation\ViewModels\MenuWidgetViewModel.cs" />
<Compile Include="Reports\AdminMenu.cs" />
<Compile Include="Reports\Controllers\AdminController.cs" />
<Compile Include="Reports\Routes.cs" />
@ -506,6 +512,12 @@
<ItemGroup>
<Content Include="Navigation\Views\EditorTemplates\Parts.MenuItem.Edit.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Navigation\Views\EditorTemplates\Parts.MenuWidget.Edit.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Navigation\Views\Parts.MenuWidget.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@ -56,6 +56,8 @@
site setting set baseurl
theme activate "The Theme Machine"
blog create /Title:"Blog" /Homepage:true /Description:"This is your Orchard Blog."
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true
menu create /MenuName:"Main Menu"
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /MenuName:"Main Menu"
widget create MenuWidget /Title:"Main Menu" /RenderTitle:false /Zone:"Navigation" /Position:"1" /Layer:"Default" /Identity:"MenuWidget1" /MenuName:"Main Menu"
</Command>
</Orchard>

View File

@ -27,6 +27,5 @@
<Command>
page create /Slug:"welcome-to-orchard" /Title:"Welcome to Orchard!" /Path:"welcome-to-orchard" /Homepage:true /Publish:true /Text:"Welcome To Orchard!"
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true
</Command>
</Orchard>

View File

@ -52,7 +52,9 @@
widget create HtmlWidget /Title:"Third Leader Aside" /Zone:"TripelThird" /Position:"5" /Layer:"TheHomepage" /Identity:"SetupHtmlWidget3" /UseLoremIpsumText:true
site setting set baseurl
page create /Slug:"welcome-to-orchard" /Title:"Welcome to Orchard!" /Path:"welcome-to-orchard" /Homepage:true /Publish:true /UseWelcomeText:true
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true
menu create /MenuName:"Main Menu"
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /MenuName:"Main Menu"
widget create MenuWidget /Title:"Main Menu" /RenderTitle:false /Zone:"Navigation" /Position:"1" /Layer:"Default" /Identity:"MenuWidget1" /MenuName:"Main Menu"
theme activate "The Theme Machine"
</Command>
</Orchard>

View File

@ -4,6 +4,8 @@ using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Title.Models;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Widgets.Models;
@ -14,17 +16,28 @@ namespace Orchard.Widgets.Commands {
private readonly IWidgetsService _widgetsService;
private readonly ISiteService _siteService;
private readonly IMembershipService _membershipService;
private readonly IContentManager _contentManager;
private const string LoremIpsum = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
public WidgetCommands(IWidgetsService widgetsService, ISiteService siteService, IMembershipService membershipService) {
public WidgetCommands(
IWidgetsService widgetsService,
ISiteService siteService,
IMembershipService membershipService,
IContentManager contentManager) {
_widgetsService = widgetsService;
_siteService = siteService;
_membershipService = membershipService;
_contentManager = contentManager;
RenderTitle = true;
}
[OrchardSwitch]
public string Title { get; set; }
[OrchardSwitch]
public bool RenderTitle { get; set; }
[OrchardSwitch]
public string Zone { get; set; }
@ -49,9 +62,12 @@ namespace Orchard.Widgets.Commands {
[OrchardSwitch]
public bool Publish { get; set; }
[OrchardSwitch]
public string MenuName { get; set; }
[CommandName("widget create")]
[CommandHelp("widget create <type> /Title:<title> /Zone:<zone> /Position:<position> /Layer:<layer> [/Identity:<identity>] [/Owner:<owner>] [/Text:<text>] [/UseLoremIpsumText:true|false]\r\n\t" + "Creates a new widget")]
[OrchardSwitches("Title,Zone,Position,Layer,Identity,Owner,Text,UseLoremIpsumText")]
[CommandHelp("widget create <type> /Title:<title> /Zone:<zone> /Position:<position> /Layer:<layer> [/Identity:<identity>] [/RenderTitle:true|false] [/Owner:<owner>] [/Text:<text>] [/UseLoremIpsumText:true|false] [/MenuName:<name>]\r\n\t" + "Creates a new widget")]
[OrchardSwitches("Title,Zone,Position,Layer,Identity,Owner,Text,UseLoremIpsumText,MenuName,RenderTitle")]
public void Create(string type) {
var widgetTypeNames = _widgetsService.GetWidgetTypeNames();
if (!widgetTypeNames.Contains(type)) {
@ -80,6 +96,25 @@ namespace Orchard.Widgets.Commands {
}
widget.As<BodyPart>().Text = text;
}
widget.RenderTitle = RenderTitle;
if(widget.Has<MenuWidgetPart>() && !String.IsNullOrWhiteSpace(MenuName)) {
// flushes before doing a query in case a previous command created the menu
_contentManager.Flush();
var menu = _contentManager.Query<TitlePart, TitlePartRecord>()
.Where(x => x.Title == MenuName)
.ForType("Menu")
.Slice(0, 1)
.FirstOrDefault();
if(menu != null) {
widget.RenderTitle = false;
widget.As<MenuWidgetPart>().Menu = menu.ContentItem.Record;
}
}
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}

View File

@ -267,6 +267,8 @@
<Compile Include="Time\SiteTimeZoneSelector.cs" />
<Compile Include="Time\TimeZoneSelectorResult.cs" />
<Compile Include="UI\FlatPositionComparer.cs" />
<Compile Include="UI\Navigation\IMenuProvider.cs" />
<Compile Include="UI\Navigation\NavigationHelper.cs" />
<Compile Include="UI\Navigation\Pager.cs" />
<Compile Include="UI\Navigation\PagerParameters.cs" />
<Compile Include="UI\Resources\IResourceManifestProvider.cs" />

View File

@ -0,0 +1,7 @@
using Orchard.ContentManagement;
namespace Orchard.UI.Navigation {
public interface IMenuProvider : IDependency {
void GetMenu(IContent menu, NavigationBuilder builder);
}
}

View File

@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Web.Routing;
using Orchard.ContentManagement;
namespace Orchard.UI.Navigation {
public interface INavigationManager : IDependency {
IEnumerable<MenuItem> BuildMenu(string menuName);
IEnumerable<MenuItem> BuildMenu(IContent menu);
IEnumerable<string> BuildImageSets(string menuName);
string GetUrl(string menuItemUrl, RouteValueDictionary routeValueDictionary);
}

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.DisplayManagement;
using Orchard.Mvc.Filters;
using Orchard.UI.Admin;
@ -30,213 +28,33 @@ namespace Orchard.UI.Navigation {
WorkContext workContext = _workContextAccessor.GetContext(filterContext);
string menuName = "main";
if (AdminFilter.IsApplied(filterContext.RequestContext)) {
menuName = "admin";
const string menuName = "admin";
if (!AdminFilter.IsApplied(filterContext.RequestContext)) {
return;
}
IEnumerable<MenuItem> menuItems = _navigationManager.BuildMenu(menuName);
// Set the currently selected path
Stack<MenuItem> selectedPath = SetSelectedPath(menuItems, filterContext.RouteData);
Stack<MenuItem> selectedPath = NavigationHelper.SetSelectedPath(menuItems, filterContext.RouteData);
// Populate main nav
dynamic menuShape = _shapeFactory.Menu().MenuName(menuName);
PopulateMenu(_shapeFactory, menuShape, menuShape, menuItems);
NavigationHelper.PopulateMenu(_shapeFactory, menuShape, menuShape, menuItems);
// Add any know image sets to the main nav
IEnumerable<string> menuImageSets = _navigationManager.BuildImageSets(menuName);
if (menuImageSets != null && menuImageSets.Count() > 0)
if (menuImageSets != null && menuImageSets.Any())
menuShape.ImageSets(menuImageSets);
workContext.Layout.Navigation.Add(menuShape);
// Populate local nav
dynamic localMenuShape = _shapeFactory.LocalMenu().MenuName(string.Format("local_{0}", menuName));
PopulateLocalMenu(_shapeFactory, localMenuShape, localMenuShape, selectedPath);
NavigationHelper.PopulateLocalMenu(_shapeFactory, localMenuShape, localMenuShape, selectedPath);
workContext.Layout.LocalNavigation.Add(localMenuShape);
}
/// <summary>
/// Populates the menu shapes.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The menu parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItems">The current level to populate.</param>
protected void PopulateMenu(dynamic shapeFactory, dynamic parentShape, dynamic menu, IEnumerable<MenuItem> menuItems) {
foreach (MenuItem menuItem in menuItems) {
dynamic menuItemShape = BuildMenuItemShape(shapeFactory, parentShape, menu, menuItem);
if (menuItem.Items != null && menuItem.Items.Any()) {
PopulateMenu(shapeFactory, menuItemShape, menu, menuItem.Items);
}
parentShape.Add(menuItemShape, menuItem.Position);
}
}
/// <summary>
/// Populates the local menu starting from the first non local task parent.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The menu parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="selectedPath">The selection path.</param>
protected void PopulateLocalMenu(dynamic shapeFactory, dynamic parentShape, dynamic menu, Stack<MenuItem> selectedPath) {
MenuItem parentMenuItem = FindParentLocalTask(selectedPath);
// find childs tabs and expand them
if (parentMenuItem != null && parentMenuItem.Items != null && parentMenuItem.Items.Any()) {
PopulateLocalMenu(shapeFactory, parentShape, menu, parentMenuItem.Items);
}
}
/// <summary>
/// Populates the local menu shapes.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The menu parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItems">The current level to populate.</param>
protected void PopulateLocalMenu(dynamic shapeFactory, dynamic parentShape, dynamic menu, IEnumerable<MenuItem> menuItems) {
foreach (MenuItem menuItem in menuItems) {
dynamic menuItemShape = BuildLocalMenuItemShape(shapeFactory, parentShape, menu, menuItem);
if (menuItem.Items != null && menuItem.Items.Any()) {
PopulateLocalMenu(shapeFactory, menuItemShape, menu, menuItem.Items);
}
parentShape.Add(menuItemShape, menuItem.Position);
}
}
/// <summary>
/// Identifies the currently selected path, starting from the selected node.
/// </summary>
/// <param name="menuItems">All the menuitems in the navigation menu.</param>
/// <param name="currentRouteData">The current route data.</param>
/// <returns>A stack with the selection path being the last node the currently selected one.</returns>
protected static Stack<MenuItem> SetSelectedPath(IEnumerable<MenuItem> menuItems, RouteData currentRouteData) {
if (menuItems == null)
return null;
foreach (MenuItem menuItem in menuItems) {
Stack<MenuItem> selectedPath = SetSelectedPath(menuItem.Items, currentRouteData);
if (selectedPath != null) {
menuItem.Selected = true;
selectedPath.Push(menuItem);
return selectedPath;
}
if (RouteMatches(menuItem.RouteValues, currentRouteData.Values)) {
menuItem.Selected = true;
selectedPath = new Stack<MenuItem>();
selectedPath.Push(menuItem);
return selectedPath;
}
}
return null;
}
/// <summary>
/// Find the first level in the selection path, starting from the bottom, that is not a local task.
/// </summary>
/// <param name="selectedPath">The selection path stack. The bottom node is the currently selected one.</param>
/// <returns>The first node, starting from the bottom, that is not a local task. Otherwise, null.</returns>
protected static MenuItem FindParentLocalTask(Stack<MenuItem> selectedPath) {
if (selectedPath != null) {
MenuItem parentMenuItem = selectedPath.Pop();
if (parentMenuItem != null) {
while (selectedPath.Count > 0) {
MenuItem currentMenuItem = selectedPath.Pop();
if (currentMenuItem.LocalNav) {
return parentMenuItem;
}
parentMenuItem = currentMenuItem;
}
}
}
return null;
}
/// <summary>
/// Determines if a menu item corresponds to a given route.
/// </summary>
/// <param name="itemValues">The menu item.</param>
/// <param name="requestValues">The route data.</param>
/// <returns>True if the menu item's action corresponds to the route data; false otherwise.</returns>
protected static bool RouteMatches(RouteValueDictionary itemValues, RouteValueDictionary requestValues) {
if (itemValues == null && requestValues == null) {
return true;
}
if (itemValues == null || requestValues == null) {
return false;
}
if (itemValues.Keys.Any(key => requestValues.ContainsKey(key) == false)) {
return false;
}
return itemValues.Keys.All(key => string.Equals(Convert.ToString(itemValues[key]), Convert.ToString(requestValues[key]), StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Builds a menu item shape.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItem">The menu item to build the shape for.</param>
/// <returns>The menu item shape.</returns>
protected dynamic BuildMenuItemShape(dynamic shapeFactory, dynamic parentShape, dynamic menu, MenuItem menuItem) {
var menuItemShape = shapeFactory.MenuItem()
.Text(menuItem.Text)
.IdHint(menuItem.IdHint)
.Href(menuItem.Href)
.LinkToFirstChild(menuItem.LinkToFirstChild)
.LocalNav(menuItem.LocalNav)
.Selected(menuItem.Selected)
.RouteValues(menuItem.RouteValues)
.Item(menuItem)
.Menu(menu)
.Parent(parentShape);
foreach (var className in menuItem.Classes)
menuItemShape.Classes.Add(className);
return menuItemShape;
}
/// <summary>
/// Builds a local menu item shape.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItem">The menu item to build the shape for.</param>
/// <returns>The menu item shape.</returns>
protected dynamic BuildLocalMenuItemShape(dynamic shapeFactory, dynamic parentShape, dynamic menu, MenuItem menuItem) {
var menuItemShape = shapeFactory.LocalMenuItem()
.Text(menuItem.Text)
.IdHint(menuItem.IdHint)
.Href(menuItem.Href)
.LinkToFirstChild(menuItem.LinkToFirstChild)
.LocalNav(menuItem.LocalNav)
.Selected(menuItem.Selected)
.RouteValues(menuItem.RouteValues)
.Item(menuItem)
.Menu(menu)
.Parent(parentShape);
foreach (var className in menuItem.Classes)
menuItemShape.Classes.Add(className);
return menuItemShape;
}
public void OnResultExecuted(ResultExecutedContext filterContext) { }
}
}

View File

@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Routing;
namespace Orchard.UI.Navigation {
public static class NavigationHelper {
/// <summary>
/// Populates the menu shapes.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The menu parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItems">The current level to populate.</param>
public static void PopulateMenu(dynamic shapeFactory, dynamic parentShape, dynamic menu, IEnumerable<MenuItem> menuItems) {
foreach (MenuItem menuItem in menuItems) {
dynamic menuItemShape = BuildMenuItemShape(shapeFactory, parentShape, menu, menuItem);
if (menuItem.Items != null && menuItem.Items.Any()) {
PopulateMenu(shapeFactory, menuItemShape, menu, menuItem.Items);
}
parentShape.Add(menuItemShape, menuItem.Position);
}
}
/// <summary>
/// Populates the local menu starting from the first non local task parent.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The menu parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="selectedPath">The selection path.</param>
public static void PopulateLocalMenu(dynamic shapeFactory, dynamic parentShape, dynamic menu, Stack<MenuItem> selectedPath) {
MenuItem parentMenuItem = FindParentLocalTask(selectedPath);
// find childs tabs and expand them
if (parentMenuItem != null && parentMenuItem.Items != null && parentMenuItem.Items.Any()) {
PopulateLocalMenu(shapeFactory, parentShape, menu, parentMenuItem.Items);
}
}
/// <summary>
/// Populates the local menu shapes.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The menu parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItems">The current level to populate.</param>
public static void PopulateLocalMenu(dynamic shapeFactory, dynamic parentShape, dynamic menu, IEnumerable<MenuItem> menuItems) {
foreach (MenuItem menuItem in menuItems) {
dynamic menuItemShape = BuildLocalMenuItemShape(shapeFactory, parentShape, menu, menuItem);
if (menuItem.Items != null && menuItem.Items.Any()) {
PopulateLocalMenu(shapeFactory, menuItemShape, menu, menuItem.Items);
}
parentShape.Add(menuItemShape, menuItem.Position);
}
}
/// <summary>
/// Identifies the currently selected path, starting from the selected node.
/// </summary>
/// <param name="menuItems">All the menuitems in the navigation menu.</param>
/// <param name="currentRouteData">The current route data.</param>
/// <returns>A stack with the selection path being the last node the currently selected one.</returns>
public static Stack<MenuItem> SetSelectedPath(IEnumerable<MenuItem> menuItems, RouteData currentRouteData) {
if (menuItems == null)
return null;
foreach (MenuItem menuItem in menuItems) {
Stack<MenuItem> selectedPath = SetSelectedPath(menuItem.Items, currentRouteData);
if (selectedPath != null) {
menuItem.Selected = true;
selectedPath.Push(menuItem);
return selectedPath;
}
if (RouteMatches(menuItem.RouteValues, currentRouteData.Values)) {
menuItem.Selected = true;
selectedPath = new Stack<MenuItem>();
selectedPath.Push(menuItem);
return selectedPath;
}
}
return null;
}
/// <summary>
/// Find the first level in the selection path, starting from the bottom, that is not a local task.
/// </summary>
/// <param name="selectedPath">The selection path stack. The bottom node is the currently selected one.</param>
/// <returns>The first node, starting from the bottom, that is not a local task. Otherwise, null.</returns>
public static MenuItem FindParentLocalTask(Stack<MenuItem> selectedPath) {
if (selectedPath != null) {
MenuItem parentMenuItem = selectedPath.Pop();
if (parentMenuItem != null) {
while (selectedPath.Count > 0) {
MenuItem currentMenuItem = selectedPath.Pop();
if (currentMenuItem.LocalNav) {
return parentMenuItem;
}
parentMenuItem = currentMenuItem;
}
}
}
return null;
}
/// <summary>
/// Determines if a menu item corresponds to a given route.
/// </summary>
/// <param name="itemValues">The menu item.</param>
/// <param name="requestValues">The route data.</param>
/// <returns>True if the menu item's action corresponds to the route data; false otherwise.</returns>
public static bool RouteMatches(RouteValueDictionary itemValues, RouteValueDictionary requestValues) {
if (itemValues == null && requestValues == null) {
return true;
}
if (itemValues == null || requestValues == null) {
return false;
}
if (itemValues.Keys.Any(key => requestValues.ContainsKey(key) == false)) {
return false;
}
return itemValues.Keys.All(key => string.Equals(Convert.ToString(itemValues[key]), Convert.ToString(requestValues[key]), StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Builds a menu item shape.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItem">The menu item to build the shape for.</param>
/// <returns>The menu item shape.</returns>
public static dynamic BuildMenuItemShape(dynamic shapeFactory, dynamic parentShape, dynamic menu, MenuItem menuItem) {
var menuItemShape = shapeFactory.MenuItem()
.Text(menuItem.Text)
.IdHint(menuItem.IdHint)
.Href(menuItem.Href)
.LinkToFirstChild(menuItem.LinkToFirstChild)
.LocalNav(menuItem.LocalNav)
.Selected(menuItem.Selected)
.RouteValues(menuItem.RouteValues)
.Item(menuItem)
.Menu(menu)
.Parent(parentShape);
foreach (var className in menuItem.Classes)
menuItemShape.Classes.Add(className);
return menuItemShape;
}
/// <summary>
/// Builds a local menu item shape.
/// </summary>
/// <param name="shapeFactory">The shape factory.</param>
/// <param name="parentShape">The parent shape.</param>
/// <param name="menu">The menu shape.</param>
/// <param name="menuItem">The menu item to build the shape for.</param>
/// <returns>The menu item shape.</returns>
public static dynamic BuildLocalMenuItemShape(dynamic shapeFactory, dynamic parentShape, dynamic menu, MenuItem menuItem) {
var menuItemShape = shapeFactory.LocalMenuItem()
.Text(menuItem.Text)
.IdHint(menuItem.IdHint)
.Href(menuItem.Href)
.LinkToFirstChild(menuItem.LinkToFirstChild)
.LocalNav(menuItem.LocalNav)
.Selected(menuItem.Selected)
.RouteValues(menuItem.RouteValues)
.Item(menuItem)
.Menu(menu)
.Parent(parentShape);
foreach (var className in menuItem.Classes)
menuItemShape.Classes.Add(className);
return menuItemShape;
}
}
}

View File

@ -3,19 +3,27 @@ using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.ContentManagement;
using Orchard.Logging;
using Orchard.Security;
using Orchard.Security.Permissions;
namespace Orchard.UI.Navigation {
public class NavigationManager : INavigationManager {
private readonly IEnumerable<INavigationProvider> _providers;
private readonly IEnumerable<INavigationProvider> _navigationProviders;
private readonly IEnumerable<IMenuProvider> _menuProviders;
private readonly IAuthorizationService _authorizationService;
private readonly UrlHelper _urlHelper;
private readonly IOrchardServices _orchardServices;
public NavigationManager(IEnumerable<INavigationProvider> providers, IAuthorizationService authorizationService, UrlHelper urlHelper, IOrchardServices orchardServices) {
_providers = providers;
public NavigationManager(
IEnumerable<INavigationProvider> navigationProviders,
IEnumerable<IMenuProvider> menuProviders,
IAuthorizationService authorizationService,
UrlHelper urlHelper,
IOrchardServices orchardServices) {
_navigationProviders = navigationProviders;
_menuProviders = menuProviders;
_authorizationService = authorizationService;
_urlHelper = urlHelper;
_orchardServices = orchardServices;
@ -26,6 +34,11 @@ namespace Orchard.UI.Navigation {
public IEnumerable<MenuItem> BuildMenu(string menuName) {
var sources = GetSources(menuName);
return FinishMenu(Reduce(Merge(sources)).ToArray());
}
public IEnumerable<MenuItem> BuildMenu(IContent menu) {
var sources = GetSources(menu);
return FinishMenu(Reduce(Arrange(Merge(sources))).ToArray());
}
@ -62,7 +75,9 @@ namespace Orchard.UI.Navigation {
return url;
}
/// <summary>
/// Updates the items by checking for permissions
/// </summary>
private IEnumerable<MenuItem> Reduce(IEnumerable<MenuItem> items) {
var hasDebugShowAllMenuItems = _authorizationService.TryCheckAccess(Permission.Named("DebugShowAllMenuItems"), _orchardServices.WorkContext.CurrentUser, null);
foreach (var item in items) {
@ -80,14 +95,15 @@ namespace Orchard.UI.Navigation {
Classes = item.Classes,
Url = item.Url,
LinkToFirstChild = item.LinkToFirstChild,
Href = item.Href
Href = item.Href,
MenuId = item.MenuId
};
}
}
}
private IEnumerable<IEnumerable<MenuItem>> GetSources(string menuName) {
foreach (var provider in _providers) {
foreach (var provider in _navigationProviders) {
if (provider.MenuName == menuName) {
var builder = new NavigationBuilder();
IEnumerable<MenuItem> items = null;
@ -105,8 +121,25 @@ namespace Orchard.UI.Navigation {
}
}
private IEnumerable<IEnumerable<MenuItem>> GetSources(IContent menu) {
foreach (var provider in _menuProviders) {
var builder = new NavigationBuilder();
IEnumerable<MenuItem> items = null;
try {
provider.GetMenu(menu, builder);
items = builder.Build();
}
catch (Exception ex) {
Logger.Error(ex, "Unexpected error while querying a menu provider. It was ignored. The menu provided by the provider may not be complete.");
}
if (items != null) {
yield return items;
}
}
}
private IEnumerable<IEnumerable<string>> GetImageSets(string menuName) {
foreach (var provider in _providers) {
foreach (var provider in _navigationProviders) {
if (provider.MenuName == menuName) {
var builder = new NavigationBuilder();
IEnumerable<string> imageSets = null;
@ -139,24 +172,18 @@ namespace Orchard.UI.Navigation {
.SelectMany(positionGroup => positionGroup.OrderBy(item => item.Text == null ? "" : item.Text.TextHint));
}
/// <summary>
/// Organizes a list of <see cref="MenuItem"/> into a hierarchy based on their positions
/// </summary>
private static IEnumerable<MenuItem> Arrange(IEnumerable<MenuItem> items) {
var indexes = new Dictionary<int, Dictionary<string, MenuItem>>();
var result = new List<MenuItem>();
var index = new Dictionary<string, MenuItem>();
foreach (var item in items) {
MenuItem parent = null;
var position = item.Position;
Dictionary<string, MenuItem> index = null;
if(indexes.ContainsKey(item.MenuId)) {
index = indexes[item.MenuId];
}
else {
indexes.Add(item.MenuId, index = new Dictionary<string, MenuItem>());
}
while (parent == null && !String.IsNullOrEmpty(position)) {
if (index.TryGetValue(position, out parent)) {
@ -166,7 +193,11 @@ namespace Orchard.UI.Navigation {
position = position.Substring(0, position.Length - 1);
};
index.Add(item.Position, item);
if (!index.ContainsKey(item.Position)) {
// prevent invalid positions
index.Add(item.Position, item);
}
// if the current element has no parent, it's a top level item
if (parent == null) {
@ -193,6 +224,7 @@ namespace Orchard.UI.Navigation {
Items = Merge(items.Select(x => x.Items)).ToArray(),
Position = SelectBestPositionValue(items.Select(x => x.Position)),
Permissions = items.SelectMany(x => x.Permissions).Distinct(),
MenuId = items.First().MenuId,
};
return joined;
}