From 856f3fc2a8365c141d1418d3cfff502be233c104 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 05 May 2014 11:17:14 -0400
Subject: [PATCH] Overhaul menu item classes and add AdminMenuExtension point

---
 src/main/java/com/gitblit/models/Menu.java                   |  302 +++++++++++++++++++++++++++
 src/main/java/com/gitblit/wicket/pages/ProjectsPage.java     |    4 
 src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java |    4 
 src/main/java/com/gitblit/wicket/pages/UserPage.java         |    4 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties    |    1 
 src/main/java/com/gitblit/wicket/panels/DropDownMenu.java    |   33 ++
 src/main/java/com/gitblit/wicket/pages/RootPage.java         |   66 +++--
 src/site/plugins_extensions.mkd                              |   26 ++
 src/main/java/com/gitblit/wicket/pages/ActivityPage.java     |    6 
 src/main/java/com/gitblit/extensions/AdminMenuExtension.java |   40 +++
 src/main/java/com/gitblit/wicket/PageRegistration.java       |  150 -------------
 src/main/java/com/gitblit/wicket/pages/DashboardPage.java    |    4 
 src/main/java/com/gitblit/wicket/pages/ProjectPage.java      |   16 
 13 files changed, 460 insertions(+), 196 deletions(-)

diff --git a/src/main/java/com/gitblit/extensions/AdminMenuExtension.java b/src/main/java/com/gitblit/extensions/AdminMenuExtension.java
new file mode 100644
index 0000000..8fe4288
--- /dev/null
+++ b/src/main/java/com/gitblit/extensions/AdminMenuExtension.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.extensions;
+
+import java.util.List;
+
+import ro.fortsoft.pf4j.ExtensionPoint;
+
+import com.gitblit.models.Menu.MenuItem;
+import com.gitblit.models.UserModel;
+
+/**
+ * Extension point to contribute administration menu items.
+ *
+ * @author James Moger
+ * @since 1.6.0
+ *
+ */
+public abstract class AdminMenuExtension implements ExtensionPoint {
+
+	/**
+	 * @param user
+	 * @since 1.6.0
+	 * @return a list of menu items
+	 */
+	public abstract List<MenuItem> getMenuItems(UserModel user);
+}
diff --git a/src/main/java/com/gitblit/models/Menu.java b/src/main/java/com/gitblit/models/Menu.java
new file mode 100644
index 0000000..7c949b3
--- /dev/null
+++ b/src/main/java/com/gitblit/models/Menu.java
@@ -0,0 +1,302 @@
+package com.gitblit.models;
+
+import java.io.Serializable;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.WebPage;
+
+import com.gitblit.utils.StringUtils;
+
+public class Menu {
+
+	/**
+	 * A MenuItem for a drop down menu.
+	 *
+	 * @author James Moger
+	 * @since 1.6.0
+	 */
+	public abstract static class MenuItem implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		final String displayText;
+
+		MenuItem(String displayText) {
+			this.displayText = displayText;
+		}
+
+		@Override
+		public int hashCode() {
+			return displayText.hashCode();
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof MenuItem) {
+				return hashCode() == o.hashCode();
+			}
+			return false;
+		}
+
+		@Override
+		public String toString() {
+			return displayText;
+		}
+	}
+
+	/**
+	 * A divider for the menu.
+	 *
+	 * @since 1.6.0
+	 */
+	public static class MenuDivider extends MenuItem {
+
+		private static final long serialVersionUID = 1L;
+
+		public MenuDivider() {
+			super("");
+		}
+	}
+
+
+	/**
+	 * A MenuItem for setting a parameter of the current url.
+	 *
+	 * @author James Moger
+	 *
+	 */
+	public static class ParameterMenuItem extends MenuItem {
+
+		private static final long serialVersionUID = 1L;
+
+		final PageParameters parameters;
+		final String parameter;
+		final String value;
+		final boolean isSelected;
+
+		/**
+		 * @param displayText
+		 */
+		public ParameterMenuItem(String displayText) {
+			this(displayText, null, null, null);
+		}
+
+		/**
+		 * @param displayText
+		 * @param parameter
+		 * @param value
+		 */
+		public ParameterMenuItem(String displayText, String parameter, String value) {
+			this(displayText, parameter, value, null);
+		}
+
+		/**
+		 * @param displayText
+		 * @param parameter
+		 * @param value
+		 */
+		public ParameterMenuItem(String displayText, String parameter, String value,
+				PageParameters params) {
+			super(displayText);
+			this.parameter = parameter;
+			this.value = value;
+
+			if (params == null) {
+				// no parameters specified
+				parameters = new PageParameters();
+				setParameter(parameter, value);
+				isSelected = false;
+			} else {
+				parameters = new PageParameters(params);
+				if (parameters.containsKey(parameter)) {
+					isSelected = params.getString(parameter).equals(value);
+					// set the new selection value
+					setParameter(parameter, value);
+				} else {
+					// not currently selected
+					isSelected = false;
+					setParameter(parameter, value);
+				}
+			}
+		}
+
+		protected void setParameter(String parameter, String value) {
+			if (!StringUtils.isEmpty(parameter)) {
+				if (StringUtils.isEmpty(value)) {
+					this.parameters.remove(parameter);
+				} else {
+					this.parameters.put(parameter, value);
+				}
+			}
+		}
+
+		public String formatParameter() {
+			if (StringUtils.isEmpty(parameter) || StringUtils.isEmpty(value)) {
+				return "";
+			}
+			return parameter + "=" + value;
+		}
+
+		public PageParameters getPageParameters() {
+			return parameters;
+		}
+
+		public boolean isSelected() {
+			return isSelected;
+		}
+
+		@Override
+		public int hashCode() {
+			if (StringUtils.isEmpty(displayText)) {
+				return value.hashCode() + parameter.hashCode();
+			}
+			return displayText.hashCode();
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof MenuItem) {
+				return hashCode() == o.hashCode();
+			}
+			return false;
+		}
+
+		@Override
+		public String toString() {
+			if (StringUtils.isEmpty(displayText)) {
+				return formatParameter();
+			}
+			return displayText;
+		}
+	}
+
+	/**
+	 * Menu item for toggling a parameter.
+	 *
+	 */
+	public static class ToggleMenuItem extends ParameterMenuItem {
+
+		private static final long serialVersionUID = 1L;
+
+		/**
+		 * @param displayText
+		 * @param parameter
+		 * @param value
+		 */
+		public ToggleMenuItem(String displayText, String parameter, String value,
+				PageParameters params) {
+			super(displayText, parameter, value, params);
+			if (isSelected) {
+				// already selected, so remove this enables toggling
+				parameters.remove(parameter);
+			}
+		}
+	}
+
+	/**
+	 * Menu item for linking to another Wicket page.
+	 *
+	 * @since 1.6.0
+	 */
+	public static class PageLinkMenuItem extends MenuItem {
+
+		private static final long serialVersionUID = 1L;
+
+		private final Class<? extends WebPage> pageClass;
+
+		private final PageParameters params;
+
+		/**
+		 * Page Link Item links to another page.
+		 *
+		 * @param displayText
+		 * @param pageClass
+		 * @since 1.6.0
+		 */
+		public PageLinkMenuItem(String displayText, Class<? extends WebPage> pageClass) {
+			this(displayText, pageClass, null);
+		}
+
+		/**
+		 * Page Link Item links to another page.
+		 *
+		 * @param displayText
+		 * @param pageClass
+		 * @param params
+		 * @since 1.6.0
+		 */
+		public PageLinkMenuItem(String displayText, Class<? extends WebPage> pageClass, PageParameters params) {
+			super(displayText);
+			this.pageClass = pageClass;
+			this.params = params;
+		}
+
+		/**
+		 * @return the page class
+		 * @since 1.6.0
+		 */
+		public Class<? extends WebPage> getPageClass() {
+			return pageClass;
+		}
+
+		/**
+		 * @return the page parameters
+		 * @since 1.6.0
+		 */
+		public PageParameters getPageParameters() {
+			return params;
+		}
+	}
+
+	/**
+	 * Menu item to link to an external page.
+	 *
+	 * @since 1.6.0
+	 */
+	public static class ExternalLinkMenuItem extends MenuItem {
+
+		private static final long serialVersionUID = 1L;
+
+		private final String href;
+
+		private final boolean newWindow;
+
+		/**
+		 * External Link Item links to something else.
+		 *
+		 * @param displayText
+		 * @param href
+		 * @since 1.6.0
+		 */
+		public ExternalLinkMenuItem(String displayText, String href) {
+			this(displayText, href, false);
+		}
+
+		/**
+		 * External Link Item links to something else.
+		 *
+		 * @param displayText
+		 * @param href
+		 * @since 1.6.0
+		 */
+		public ExternalLinkMenuItem(String displayText, String href, boolean newWindow) {
+			super(displayText);
+			this.href = href;
+			this.newWindow = newWindow;
+		}
+
+		/**
+		 * @since 1.6.0
+		 */
+		public String getHref() {
+			return href;
+		}
+
+		/**
+		 * @since 1.6.0
+		 */
+		public boolean openInNewWindow() {
+			return newWindow;
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index 0ed2ed5..2c83dd6 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -680,3 +680,4 @@
 gb.overdue = overdue
 gb.openMilestones = open milestones
 gb.closedMilestones = closed milestones
+gb.adminMenuItem = admin
diff --git a/src/main/java/com/gitblit/wicket/PageRegistration.java b/src/main/java/com/gitblit/wicket/PageRegistration.java
index 1b98f2c..ff4a55b 100644
--- a/src/main/java/com/gitblit/wicket/PageRegistration.java
+++ b/src/main/java/com/gitblit/wicket/PageRegistration.java
@@ -22,7 +22,7 @@
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.WebPage;
 
-import com.gitblit.utils.StringUtils;
+import com.gitblit.models.Menu.MenuItem;
 
 /**
  * Represents a page link registration for the topbar.
@@ -88,156 +88,12 @@
 
 		private static final long serialVersionUID = 1L;
 
-		public final List<DropDownMenuItem> menuItems;
+		public final List<MenuItem> menuItems;
 
 		public DropDownMenuRegistration(String translationKey, Class<? extends WebPage> pageClass) {
 			super(translationKey, pageClass);
-			menuItems = new ArrayList<DropDownMenuItem>();
+			menuItems = new ArrayList<MenuItem>();
 		}
 	}
 
-	/**
-	 * A MenuItem for the DropDownMenu.
-	 *
-	 * @author James Moger
-	 *
-	 */
-	public static class DropDownMenuItem implements Serializable {
-
-		private static final long serialVersionUID = 1L;
-
-		final PageParameters parameters;
-		final String displayText;
-		final String parameter;
-		final String value;
-		final boolean isSelected;
-
-		/**
-		 * Divider constructor.
-		 */
-		public DropDownMenuItem() {
-			this(null, null, null, null);
-		}
-
-		/**
-		 * Standard Menu Item constructor.
-		 *
-		 * @param displayText
-		 * @param parameter
-		 * @param value
-		 */
-		public DropDownMenuItem(String displayText, String parameter, String value) {
-			this(displayText, parameter, value, null);
-		}
-
-		/**
-		 * Standard Menu Item constructor that preserves aggregate parameters.
-		 *
-		 * @param displayText
-		 * @param parameter
-		 * @param value
-		 */
-		public DropDownMenuItem(String displayText, String parameter, String value,
-				PageParameters params) {
-			this.displayText = displayText;
-			this.parameter = parameter;
-			this.value = value;
-
-			if (params == null) {
-				// no parameters specified
-				parameters = new PageParameters();
-				setParameter(parameter, value);
-				isSelected = false;
-			} else {
-				parameters = new PageParameters(params);
-				if (parameters.containsKey(parameter)) {
-					isSelected = params.getString(parameter).equals(value);
-					// set the new selection value
-					setParameter(parameter, value);
-				} else {
-					// not currently selected
-					isSelected = false;
-					setParameter(parameter, value);
-				}
-			}
-		}
-
-		protected void setParameter(String parameter, String value) {
-			if (!StringUtils.isEmpty(parameter)) {
-				if (StringUtils.isEmpty(value)) {
-					this.parameters.remove(parameter);
-				} else {
-					this.parameters.put(parameter, value);
-				}
-			}
-		}
-
-		public String formatParameter() {
-			if (StringUtils.isEmpty(parameter) || StringUtils.isEmpty(value)) {
-				return "";
-			}
-			return parameter + "=" + value;
-		}
-
-		public PageParameters getPageParameters() {
-			return parameters;
-		}
-
-		public boolean isDivider() {
-			return displayText == null && value == null && parameter == null;
-		}
-
-		public boolean isSelected() {
-			return isSelected;
-		}
-
-		@Override
-		public int hashCode() {
-			if (isDivider()) {
-				// divider menu item
-				return super.hashCode();
-			}
-			if (StringUtils.isEmpty(displayText)) {
-				return value.hashCode() + parameter.hashCode();
-			}
-			return displayText.hashCode();
-		}
-
-		@Override
-		public boolean equals(Object o) {
-			if (o instanceof DropDownMenuItem) {
-				return hashCode() == o.hashCode();
-			}
-			return false;
-		}
-
-		@Override
-		public String toString() {
-			if (StringUtils.isEmpty(displayText)) {
-				return formatParameter();
-			}
-			return displayText;
-		}
-	}
-
-	public static class DropDownToggleItem extends DropDownMenuItem {
-
-		private static final long serialVersionUID = 1L;
-
-		/**
-		 * Toggle Menu Item constructor that preserves aggregate parameters.
-		 *
-		 * @param displayText
-		 * @param parameter
-		 * @param value
-		 */
-		public DropDownToggleItem(String displayText, String parameter, String value,
-				PageParameters params) {
-			super(displayText, parameter, value, params);
-			if (isSelected) {
-				// already selected, so remove this enables toggling
-				parameters.remove(parameter);
-			}
-		}
-	}
 }
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ActivityPage.java b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
index f0e390d..0870ff9 100644
--- a/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
@@ -31,6 +31,7 @@
 
 import com.gitblit.Keys;
 import com.gitblit.models.Activity;
+import com.gitblit.models.Menu.ParameterMenuItem;
 import com.gitblit.models.Metric;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.ActivityUtils;
@@ -38,7 +39,6 @@
 import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.charting.Chart;
@@ -153,7 +153,7 @@
 
 		if (filters.menuItems.size() > 0) {
 			// Reset Filter
-			filters.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+			filters.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
 		}
 		pages.add(filters);
 	}
@@ -209,7 +209,7 @@
 		}
 		charts.addChart(chart);
 
-		// active repositories pie chart 
+		// active repositories pie chart
 		chart = charts.createPieChart("chartRepositories", getString("gb.activeRepositories"),
 				getString("gb.repository"), getString("gb.commits"));
 		for (Metric metric : repositoryMetrics.values()) {
diff --git a/src/main/java/com/gitblit/wicket/pages/DashboardPage.java b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
index 9853449..16b0b73 100644
--- a/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
@@ -36,6 +36,7 @@
 
 import com.gitblit.Keys;
 import com.gitblit.models.DailyLogEntry;
+import com.gitblit.models.Menu.ParameterMenuItem;
 import com.gitblit.models.Metric;
 import com.gitblit.models.RefLogEntry;
 import com.gitblit.models.RepositoryCommit;
@@ -46,7 +47,6 @@
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebApp;
 import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.charting.Chart;
 import com.gitblit.wicket.charting.Charts;
@@ -152,7 +152,7 @@
 
 		if (menu.menuItems.size() > 0) {
 			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+			menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
 		}
 
 		pages.add(menu);
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
index b92282b..6c8aa4f 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
@@ -26,6 +26,9 @@
 import org.apache.wicket.markup.html.link.ExternalLink;
 
 import com.gitblit.Keys;
+import com.gitblit.models.Menu.MenuDivider;
+import com.gitblit.models.Menu.MenuItem;
+import com.gitblit.models.Menu.ParameterMenuItem;
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
@@ -38,7 +41,6 @@
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.GitblitRedirectException;
 import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.FilterableRepositoryList;
@@ -172,7 +174,7 @@
 
 		if (menu.menuItems.size() > 0) {
 			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), "p", WicketUtils.getProjectName(params)));
+			menu.menuItems.add(new ParameterMenuItem(getString("gb.reset"), "p", WicketUtils.getProjectName(params)));
 		}
 
 		pages.add(menu);
@@ -202,8 +204,8 @@
 		return null;
 	}
 
-	protected List<DropDownMenuItem> getProjectsMenu() {
-		List<DropDownMenuItem> menu = new ArrayList<DropDownMenuItem>();
+	protected List<MenuItem> getProjectsMenu() {
+		List<MenuItem> menu = new ArrayList<MenuItem>();
 		List<ProjectModel> projects = new ArrayList<ProjectModel>();
 		for (ProjectModel model : getProjectModels()) {
 			if (!model.isUserProject()) {
@@ -230,11 +232,11 @@
 		}
 
 		for (ProjectModel project : projects) {
-			menu.add(new DropDownMenuItem(project.getDisplayName(), "p", project.name));
+			menu.add(new ParameterMenuItem(project.getDisplayName(), "p", project.name));
 		}
 		if (showAllProjects) {
-			menu.add(new DropDownMenuItem());
-			menu.add(new DropDownMenuItem("all projects", null, null));
+			menu.add(new MenuDivider());
+			menu.add(new ParameterMenuItem("all projects"));
 		}
 		return menu;
 	}
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
index 77d4984..c404ae6 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
@@ -24,10 +24,10 @@
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
 
 import com.gitblit.Keys;
+import com.gitblit.models.Menu.ParameterMenuItem;
 import com.gitblit.models.ProjectModel;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.LinkPanel;
@@ -128,7 +128,7 @@
 
 		if (menu.menuItems.size() > 0) {
 			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+			menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
 		}
 
 		pages.add(menu);
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
index f4ddf40..41fe057 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -29,6 +29,7 @@
 import org.eclipse.jgit.lib.Constants;
 
 import com.gitblit.Keys;
+import com.gitblit.models.Menu.ParameterMenuItem;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
@@ -36,7 +37,6 @@
 import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.RepositoriesPanel;
@@ -105,7 +105,7 @@
 
 		if (menu.menuItems.size() > 0) {
 			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+			menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
 		}
 
 		pages.add(menu);
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java
index c59c189..b9055c1 100644
--- a/src/main/java/com/gitblit/wicket/pages/RootPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -47,6 +47,12 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Keys;
+import com.gitblit.extensions.AdminMenuExtension;
+import com.gitblit.models.Menu.MenuDivider;
+import com.gitblit.models.Menu.MenuItem;
+import com.gitblit.models.Menu.PageLinkMenuItem;
+import com.gitblit.models.Menu.ParameterMenuItem;
+import com.gitblit.models.Menu.ToggleMenuItem;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
@@ -54,8 +60,7 @@
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
-import com.gitblit.wicket.PageRegistration.DropDownToggleItem;
+import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.SessionlessForm;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.GravatarImage;
@@ -164,9 +169,6 @@
 			add(new Label("userPanel").setVisible(false));
 		}
 
-		boolean showRegistrations = app().federation().canFederate()
-				&& app().settings().getBoolean(Keys.web.showFederationRegistrations, false);
-
 		// navigation links
 		List<PageRegistration> pages = new ArrayList<PageRegistration>();
 		if (!authenticateView || (authenticateView && isLoggedIn)) {
@@ -181,11 +183,29 @@
 			if (allowLucene) {
 				pages.add(new PageRegistration("gb.search", LuceneSearchPage.class));
 			}
+
+			UserModel user = GitBlitWebSession.get().getUser();
+
 			if (showAdmin) {
-				pages.add(new PageRegistration("gb.users", UsersPage.class));
-			}
-			if (showAdmin || showRegistrations) {
-				pages.add(new PageRegistration("gb.federation", FederationPage.class));
+				// admin dropdown menu
+				DropDownMenuRegistration adminMenu = new DropDownMenuRegistration("gb.adminMenuItem", MyDashboardPage.class);
+
+				adminMenu.menuItems.add(new PageLinkMenuItem(getString("gb.users"), UsersPage.class));
+
+				boolean showRegistrations = app().federation().canFederate()
+						&& app().settings().getBoolean(Keys.web.showFederationRegistrations, false);
+				if (showRegistrations) {
+					adminMenu.menuItems.add(new PageLinkMenuItem(getString("gb.federation"), FederationPage.class));
+				}
+
+				// allow plugins to contribute admin menu items
+				List<AdminMenuExtension> extensions = app().plugins().getExtensions(AdminMenuExtension.class);
+				for (AdminMenuExtension ext : extensions) {
+					adminMenu.menuItems.add(new MenuDivider());
+					adminMenu.menuItems.addAll(ext.getMenuItems(user));
+				}
+
+				pages.add(adminMenu);
 			}
 
 			if (!authenticateView || (authenticateView && isLoggedIn)) {
@@ -289,9 +309,9 @@
 
 	}
 
-	protected List<DropDownMenuItem> getRepositoryFilterItems(PageParameters params) {
+	protected List<com.gitblit.models.Menu.MenuItem> getRepositoryFilterItems(PageParameters params) {
 		final UserModel user = GitBlitWebSession.get().getUser();
-		Set<DropDownMenuItem> filters = new LinkedHashSet<DropDownMenuItem>();
+		Set<MenuItem> filters = new LinkedHashSet<MenuItem>();
 		List<RepositoryModel> repositories = getRepositoryModels();
 
 		// accessible repositories by federation set
@@ -310,11 +330,11 @@
 			List<String> sets = new ArrayList<String>(setMap.keySet());
 			Collections.sort(sets);
 			for (String set : sets) {
-				filters.add(new DropDownToggleItem(MessageFormat.format("{0} ({1})", set,
+				filters.add(new ToggleMenuItem(MessageFormat.format("{0} ({1})", set,
 						setMap.get(set).get()), "set", set, params));
 			}
 			// divider
-			filters.add(new DropDownMenuItem());
+			filters.add(new MenuDivider());
 		}
 
 		// user's team memberships
@@ -322,11 +342,11 @@
 			List<TeamModel> teams = new ArrayList<TeamModel>(user.teams);
 			Collections.sort(teams);
 			for (TeamModel team : teams) {
-				filters.add(new DropDownToggleItem(MessageFormat.format("{0} ({1})", team.name,
+				filters.add(new ToggleMenuItem(MessageFormat.format("{0} ({1})", team.name,
 						team.repositories.size()), "team", team.name, params));
 			}
 			// divider
-			filters.add(new DropDownMenuItem());
+			filters.add(new MenuDivider());
 		}
 
 		// custom filters
@@ -337,18 +357,18 @@
 			for (String expression : expressions) {
 				if (!StringUtils.isEmpty(expression)) {
 					addedExpression = true;
-					filters.add(new DropDownToggleItem(null, "x", expression, params));
+					filters.add(new ToggleMenuItem(null, "x", expression, params));
 				}
 			}
 			// if we added any custom expressions, add a divider
 			if (addedExpression) {
-				filters.add(new DropDownMenuItem());
+				filters.add(new MenuDivider());
 			}
 		}
-		return new ArrayList<DropDownMenuItem>(filters);
+		return new ArrayList<MenuItem>(filters);
 	}
 
-	protected List<DropDownMenuItem> getTimeFilterItems(PageParameters params) {
+	protected List<MenuItem> getTimeFilterItems(PageParameters params) {
 		// days back choices - additive parameters
 		int daysBack = app().settings().getInteger(Keys.web.activityDuration, 7);
 		int maxDaysBack = app().settings().getInteger(Keys.web.activityDurationMaximum, 30);
@@ -369,7 +389,7 @@
 			clonedParams.put("db",  daysBack);
 		}
 
-		List<DropDownMenuItem> items = new ArrayList<DropDownMenuItem>();
+		List<MenuItem> items = new ArrayList<MenuItem>();
 		Set<Integer> choicesSet = new TreeSet<Integer>(app().settings().getIntegers(Keys.web.activityDurationChoices));
 		if (choicesSet.isEmpty()) {
 			 choicesSet.addAll(Arrays.asList(1, 3, 7, 14, 21, 28));
@@ -379,13 +399,13 @@
 		String lastDaysPattern = getString("gb.lastNDays");
 		for (Integer db : choices) {
 			if (db == 1) {
-				items.add(new DropDownMenuItem(getString("gb.time.today"), "db", db.toString(), clonedParams));
+				items.add(new ParameterMenuItem(getString("gb.time.today"), "db", db.toString(), clonedParams));
 			} else {
 				String txt = MessageFormat.format(lastDaysPattern, db);
-				items.add(new DropDownMenuItem(txt, "db", db.toString(), clonedParams));
+				items.add(new ParameterMenuItem(txt, "db", db.toString(), clonedParams));
 			}
 		}
-		items.add(new DropDownMenuItem());
+		items.add(new MenuDivider());
 		return items;
 	}
 
diff --git a/src/main/java/com/gitblit/wicket/pages/UserPage.java b/src/main/java/com/gitblit/wicket/pages/UserPage.java
index a5d38d1..0767621 100644
--- a/src/main/java/com/gitblit/wicket/pages/UserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/UserPage.java
@@ -29,6 +29,7 @@
 import org.eclipse.jgit.lib.PersonIdent;
 
 import com.gitblit.Keys;
+import com.gitblit.models.Menu.ParameterMenuItem;
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
@@ -37,7 +38,6 @@
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.GitblitRedirectException;
 import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.GravatarImage;
@@ -140,7 +140,7 @@
 
 		if (menu.menuItems.size() > 0) {
 			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+			menu.menuItems.add(new ParameterMenuItem(getString("gb.reset")));
 		}
 
 		pages.add(menu);
diff --git a/src/main/java/com/gitblit/wicket/panels/DropDownMenu.java b/src/main/java/com/gitblit/wicket/panels/DropDownMenu.java
index d1a632e..f561143 100644
--- a/src/main/java/com/gitblit/wicket/panels/DropDownMenu.java
+++ b/src/main/java/com/gitblit/wicket/panels/DropDownMenu.java
@@ -21,7 +21,11 @@
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
 
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
+import com.gitblit.models.Menu.MenuDivider;
+import com.gitblit.models.Menu.ExternalLinkMenuItem;
+import com.gitblit.models.Menu.MenuItem;
+import com.gitblit.models.Menu.PageLinkMenuItem;
+import com.gitblit.models.Menu.ParameterMenuItem;
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
 import com.gitblit.wicket.WicketUtils;
 
@@ -33,26 +37,39 @@
 		super(id);
 
 		add(new Label("label", label).setRenderBodyOnly(true));
-		ListDataProvider<DropDownMenuItem> items = new ListDataProvider<DropDownMenuItem>(
+		ListDataProvider<MenuItem> items = new ListDataProvider<MenuItem>(
 				menu.menuItems);
-		DataView<DropDownMenuItem> view = new DataView<DropDownMenuItem>("menuItems", items) {
+		DataView<MenuItem> view = new DataView<MenuItem>("menuItems", items) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
-			public void populateItem(final Item<DropDownMenuItem> item) {
-				DropDownMenuItem entry = item.getModelObject();
-				if (entry.isDivider()) {
+			public void populateItem(final Item<MenuItem> item) {
+				MenuItem entry = item.getModelObject();
+				if (entry instanceof PageLinkMenuItem) {
+					// link to another Wicket page
+					PageLinkMenuItem pageLink = (PageLinkMenuItem) entry;
+					item.add(new LinkPanel("menuItem", null, null, pageLink.toString(), pageLink.getPageClass(),
+							pageLink.getPageParameters(), false).setRenderBodyOnly(true));
+				} else if (entry instanceof ExternalLinkMenuItem) {
+					// link to a specified href
+					ExternalLinkMenuItem extLink = (ExternalLinkMenuItem) entry;
+					item.add(new LinkPanel("menuItem", null, extLink.toString(), extLink.getHref(),
+							extLink.openInNewWindow()).setRenderBodyOnly(true));
+				} else if (entry instanceof MenuDivider) {
+					// divider
 					item.add(new Label("menuItem").setRenderBodyOnly(true));
 					WicketUtils.setCssClass(item, "divider");
 				} else {
+					ParameterMenuItem parameter = (ParameterMenuItem) entry;
+					// parameter link for the current page
 					String icon = null;
-					if (entry.isSelected()) {
+					if (parameter.isSelected()) {
 						icon = "icon-ok";
 					} else {
 						icon = "icon-ok-white";
 					}
 					item.add(new LinkPanel("menuItem", icon, null, entry.toString(), menu.pageClass,
-							entry.getPageParameters(), false).setRenderBodyOnly(true));
+							parameter.getPageParameters(), false).setRenderBodyOnly(true));
 				}
 			}
 		};
diff --git a/src/site/plugins_extensions.mkd b/src/site/plugins_extensions.mkd
index e8b53d1..684373e 100644
--- a/src/site/plugins_extensions.mkd
+++ b/src/site/plugins_extensions.mkd
@@ -205,3 +205,29 @@
 }
 ```
 
+### Admin Menu Items
+
+*SINCE 1.6.0*
+
+You can provide your own admin menu items by subclassing the *AdminMenuExtension* class.
+
+```java
+import java.util.Arrays;
+import java.util.List;
+import ro.fortsoft.pf4j.Extension;
+import com.gitblit.extensions.AdminMenuExtension;
+import com.gitblit.models.Menu.ExternalLinkMenuItem;
+import com.gitblit.models.Menu.MenuItem;
+import com.gitblit.models.UserModel;
+
+@Extension
+public class MyAdminMenuContributor extends AdminMenuExtension {
+
+    @Override
+    public List<MenuItem> getMenuItems(UserModel user) {
+        return Arrays.asList((MenuItem) new ExternalLinkMenuItem("Github", String.format("https://github.com/%s", user.username));
+    }
+}
+```
+
+

--
Gitblit v1.9.1