Top menu improvements

* Add a "browse" link for screens with a project context (fixes
  https://code.google.com/p/gerrit/issues/detail?id=2335 using a
  new feature in Gerrit 2.11).
* Make all the submenu item titles configurable in a [plugin "gitblit"]
  section in gerrit.config.
* Update init step to account for that.

Change-Id: Id43eb206f32499ef0cb3703808e331ea18afcc29
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java
index 329ef60..99cd6de 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitInitStep.java
@@ -13,24 +13,31 @@
 // limitations under the License.
 package com.googlesource.gerrit.plugins.gitblit;
 
+import org.eclipse.jgit.lib.Config;
+
 import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.pgm.init.api.InitStep;
 import com.google.gerrit.pgm.init.api.Section;
 import com.google.gerrit.pgm.init.api.Section.Factory;
-import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.inject.Inject;
 
 public class GitBlitInitStep implements InitStep {
   private final ConsoleUI ui;
   private final String pluginName;
   private final Factory sections;
+  private final Config cfg;
 
   @Inject
-  public GitBlitInitStep(final ConsoleUI ui, final Section.Factory sections,
-      @PluginName final String pluginName) {
+  public GitBlitInitStep(ConsoleUI ui,
+      Section.Factory sections,
+      @PluginName String pluginName,
+      InitFlags flags) {
     this.ui = ui;
     this.pluginName = pluginName;
     this.sections = sections;
+    this.cfg = flags.cfg;
   }
 
   @Override
@@ -41,21 +48,68 @@
     if(ui.yesno(true, "Do you want to use GitBlit as your GitWeb viewer ?")) {
       configureGitBlit();
     }
+    // If we don't use GitBlit here, we leave a potential [plugin "gitblit"]
+    // section in the config. It won't hurt,
+    // and maybe the user will later re-enable GitBlit, and then he'd be
+    // surprised if his customized settings were
+    // gone.
   }
 
   private void configureGitBlit() {
+    initGitWebConfig();
+    initGitBlitPluginConfig();
+  }
+
+  private void initGitWebConfig() {
     Section gitWeb = sections.get("gitweb", null);
     gitWeb.set("type", "custom");
     gitWeb.set("url", "plugins/");
     gitWeb.set("project", pluginName + "/summary/?r=${project}");
     gitWeb.set("revision", pluginName + "/commit/?r=${project}&h=${commit}");
     gitWeb.set("branch", pluginName + "/log/?r=${project}&h=${branch}");
-    gitWeb.set("filehistory", pluginName + "/history/?f=${file}&r=${project}&h=${branch}");
-    gitWeb.set("file", pluginName + "/blob/?r=${project}&h=${commit}&f=${file}");
+    gitWeb.set("filehistory", pluginName
+        + "/history/?f=${file}&r=${project}&h=${branch}");
+    gitWeb
+        .set("file", pluginName + "/blob/?r=${project}&h=${commit}&f=${file}");
     gitWeb.set("roottree", pluginName + "/tree/?r=${project}&h=${commit}");
     gitWeb.string("Link name", "linkname", "GitBlit");
   }
 
+  private void initGitBlitPluginConfig() {
+    Section pluginCfg = sections.get("plugin", pluginName);
+    // These values are displayed in the UI.
+    pluginCfg.string("\"Repositories\" submenu title", "repositories",
+        "Repositories", true);
+    pluginCfg
+        .string("\"Activity\" submenu title", "activity", "Activity", true);
+    String originalValue = pluginCfg.get("search");
+    if (originalValue == null) {
+      pluginCfg
+          .string(
+              "\"Search\" submenu title (makes only sense to set if some projects are indexed in GitBlit)",
+              "search", "", true);
+    } else {
+      String newValue =
+          ui.readString(
+              originalValue,
+              "%s",
+              "\"Search\" submenu title (makes only sense to set if some projects are indexed in GitBlit; single dash unsets)");
+      if (newValue == null || "-".equals(newValue)) {
+        pluginCfg.unset("search");
+      } else if (!originalValue.equals(newValue)) {
+        pluginCfg.set("search", newValue);
+      }
+    }
+    pluginCfg.string(
+        "\"Browse\" submenu title for the \"Projects\" top-level menu",
+        "browse", "Browse", true);
+    // If everything is at the default, then make sure we don't have the section
+    // at all.
+    if (cfg.getNames("plugin", pluginName).isEmpty()) {
+      cfg.unsetSection("plugin", pluginName);
+    }
+  }
+
   @Override
   public void postRun() throws Exception {
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java
index b4e5778..5041708 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitblit/GitBlitTopMenu.java
@@ -17,45 +17,68 @@
 import java.util.Arrays;
 import java.util.List;
 
+import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
 import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.client.GerritTopMenu;
 import com.google.gerrit.extensions.webui.TopMenu;
-import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
 @Listen
 public class GitBlitTopMenu implements TopMenu {
+
+  // Not configurable to avoid mis-configurations clashing with predefined top
+  // menus.
+  private static final String GITBLIT_TOPMENU_NAME = "GitBlit";
+
   private final MenuEntry fullMenuEntries;
   private final MenuEntry restrictedMenuEntries;
+  private final MenuEntry extraProjectEntries;
   private final Provider<CurrentUser> userProvider;
 
   @Inject
-  public GitBlitTopMenu(final @PluginName String pluginName,
-      final Provider<CurrentUser> userProvider) {
+  public GitBlitTopMenu(@PluginName String pluginName,
+      @PluginCanonicalWebUrl String pluginUrl,
+      Provider<CurrentUser> userProvider,
+      PluginConfigFactory cfgProvider) {
     this.userProvider = userProvider;
 
-    String gitBlitBaseUrl = "/plugins/" + pluginName + "/";
-    this.restrictedMenuEntries =
-        menu("Gitblit", item("Repositories", gitBlitBaseUrl + "repositories/"));
-    this.fullMenuEntries =
-        menu("GitBlit", item("Repositories", gitBlitBaseUrl + "repositories/"),
-            item("Activity", gitBlitBaseUrl + "activity/"),
-            item("Search", gitBlitBaseUrl + "lucene/"));
-  }
-
-  private MenuEntry menu(String name, MenuItem... items) {
-    return new MenuEntry(name, Arrays.asList(items));
-  }
-
-  private MenuItem item(String name, String url) {
-    return new MenuItem(name, url, "");
+    String gitBlitBaseUrl = pluginUrl;
+    PluginConfig cfg = cfgProvider.getFromGerritConfig(pluginName, true);
+    // We don't have to worry about XSS here; the way these menu item get
+    // created through GWT ensures that these values read from the config
+    // end up as text nodes in the DOM, even if they contain potentially
+    // malicious code. So if somebody sets these values to some HTML snippet,
+    // he'll simply end up with a funny looking menu item, but he can't inject
+    // things here.
+    MenuItem repositories =
+        new MenuItem(cfg.getString("repositories", "Repositories"),
+            gitBlitBaseUrl + "repositories/", "");
+    restrictedMenuEntries =
+        new MenuEntry(GITBLIT_TOPMENU_NAME, Arrays.asList(repositories));
+    List<MenuItem> fullMenuItems = Lists.newArrayList();
+    fullMenuItems.add(repositories);
+    fullMenuItems.add(new MenuItem(cfg.getString("activity", "Activity"),
+        gitBlitBaseUrl + "activity/", ""));
+    String search = cfg.getString("search");
+    if (search != null && !search.isEmpty()) {
+      fullMenuItems.add(new MenuItem(search, gitBlitBaseUrl + "lucene/", ""));
+    }
+    fullMenuEntries = new MenuEntry(GITBLIT_TOPMENU_NAME, fullMenuItems);
+    extraProjectEntries =
+        new MenuEntry(GerritTopMenu.PROJECTS, Arrays.asList(new MenuItem(cfg
+            .getString("browse", "Browse"), gitBlitBaseUrl
+            + "summary?r=${projectName}", "")));
   }
 
   @Override
   public List<MenuEntry> getEntries() {
-    return Arrays.asList(userProvider.get() instanceof AnonymousUser
-        ? restrictedMenuEntries : fullMenuEntries);
+    return Arrays.asList(userProvider.get().isIdentifiedUser()
+        ? fullMenuEntries : restrictedMenuEntries, extraProjectEntries);
   }
 }