summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/gitblit/wicket/pages/RootPage.java
blob: b1c3639dfc68d83d522f6d0cd3d24511cab647dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
/*
 * Copyright 2011 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.wicket.pages;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

import org.apache.wicket.MarkupContainer;
import org.apache.wicket.PageParameters;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.WebResponse;

import com.gitblit.Constants;
import com.gitblit.Keys;
import com.gitblit.extensions.NavLinkExtension;
import com.gitblit.extensions.UserMenuExtension;
import com.gitblit.models.Menu.ExternalLinkMenuItem;
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.NavLink;
import com.gitblit.models.NavLink.PageNavLink;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ModelUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.GravatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.NavigationPanel;

/**
 * Root page is a topbar, navigable page like Repositories, Users, or
 * Federation.
 *
 * @author James Moger
 *
 */
public abstract class RootPage extends BasePage {

	boolean showAdmin;

	IModel<String> username = new Model<String>("");
	IModel<String> password = new Model<String>("");
	List<RepositoryModel> repositoryModels = new ArrayList<RepositoryModel>();

	public RootPage() {
		super();
	}

	public RootPage(PageParameters params) {
		super(params);
	}

	@Override
	protected void setupPage(String repositoryName, String pageName) {

		// CSS header overrides
		add(new HeaderContributor(new IHeaderContributor() {
			private static final long serialVersionUID = 1L;

			@Override
			public void renderHead(IHeaderResponse response) {
				StringBuilder buffer = new StringBuilder();
				buffer.append("<style type=\"text/css\">\n");
				buffer.append(".navbar-inner {\n");
				final String headerBackground = app().settings().getString(Keys.web.headerBackgroundColor, null);
				if (!StringUtils.isEmpty(headerBackground)) {
					buffer.append(MessageFormat.format("background-color: {0};\n", headerBackground));
				}
				final String headerBorder = app().settings().getString(Keys.web.headerBorderColor, null);
				if (!StringUtils.isEmpty(headerBorder)) {
					buffer.append(MessageFormat.format("border-bottom: 1px solid {0} !important;\n", headerBorder));
				}
				buffer.append("}\n");
				final String headerBorderFocus = app().settings().getString(Keys.web.headerBorderFocusColor, null);
				if (!StringUtils.isEmpty(headerBorderFocus)) {
					buffer.append(".navbar ul li:focus, .navbar .active {\n");
					buffer.append(MessageFormat.format("border-bottom: 4px solid {0};\n", headerBorderFocus));
					buffer.append("}\n");
				}
				final String headerForeground = app().settings().getString(Keys.web.headerForegroundColor, null);
				if (!StringUtils.isEmpty(headerForeground)) {
					buffer.append(".navbar ul.nav li a {\n");
					buffer.append(MessageFormat.format("color: {0};\n", headerForeground));
					buffer.append("}\n");
					buffer.append(".navbar ul.nav .active a {\n");
					buffer.append(MessageFormat.format("color: {0};\n", headerForeground));
					buffer.append("}\n");
				}
				final String headerHover = app().settings().getString(Keys.web.headerHoverColor, null);
				if (!StringUtils.isEmpty(headerHover)) {
					buffer.append(".navbar ul.nav li a:hover {\n");
					buffer.append(MessageFormat.format("color: {0} !important;\n", headerHover));
					buffer.append("}\n");
				}
				buffer.append("</style>\n");
				response.renderString(buffer.toString());
				}
			}));

		boolean authenticateView = app().settings().getBoolean(Keys.web.authenticateViewPages, false);
		boolean authenticateAdmin = app().settings().getBoolean(Keys.web.authenticateAdminPages, true);
		boolean allowAdmin = app().settings().getBoolean(Keys.web.allowAdministration, true);
		boolean allowLucene = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true);
		boolean isLoggedIn = GitBlitWebSession.get().isLoggedIn();

		if (authenticateAdmin) {
			showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
			// authentication requires state and session
			setStatelessHint(false);
		} else {
			showAdmin = allowAdmin;
			if (authenticateView) {
				// authentication requires state and session
				setStatelessHint(false);
			} else {
				// no authentication required, no state and no session required
				setStatelessHint(true);
			}
		}

		if (authenticateView || authenticateAdmin) {
			if (isLoggedIn) {
				UserMenu userFragment = new UserMenu("userPanel", "userMenuFragment", RootPage.this);
				add(userFragment);
			} else {
				LoginForm loginForm = new LoginForm("userPanel", "loginFormFragment", RootPage.this);
				add(loginForm);
			}
		} else {
			add(new Label("userPanel").setVisible(false));
		}

		// navigation links
		List<NavLink> navLinks = new ArrayList<NavLink>();
		if (!authenticateView || (authenticateView && isLoggedIn)) {
			navLinks.add(new PageNavLink(isLoggedIn ? "gb.myDashboard" : "gb.dashboard", MyDashboardPage.class,
					getRootPageParameters()));
			if (isLoggedIn && app().tickets().isReady()) {
				navLinks.add(new PageNavLink("gb.myTickets", MyTicketsPage.class));
			}
			navLinks.add(new PageNavLink("gb.repositories", RepositoriesPage.class,
					getRootPageParameters()));
			navLinks.add(new PageNavLink("gb.activity", ActivityPage.class, getRootPageParameters()));
			if (allowLucene) {
				navLinks.add(new PageNavLink("gb.search", LuceneSearchPage.class));
			}

			if (!authenticateView || (authenticateView && isLoggedIn)) {
				addDropDownMenus(navLinks);
			}

			UserModel user = UserModel.ANONYMOUS;
			if (isLoggedIn) {
				user = GitBlitWebSession.get().getUser();
			}

			// add nav link extensions
			List<NavLinkExtension> extensions = app().plugins().getExtensions(NavLinkExtension.class);
			for (NavLinkExtension ext : extensions) {
				navLinks.addAll(ext.getNavLinks(user));
			}
		}

		NavigationPanel navPanel = new NavigationPanel("navPanel", getRootNavPageClass(), navLinks);
		add(navPanel);

		// display an error message cached from a redirect
		String cachedMessage = GitBlitWebSession.get().clearErrorMessage();
		if (!StringUtils.isEmpty(cachedMessage)) {
			error(cachedMessage);
		} else if (showAdmin) {
			int pendingProposals = app().federation().getPendingFederationProposals().size();
			if (pendingProposals == 1) {
				info(getString("gb.OneProposalToReview"));
			} else if (pendingProposals > 1) {
				info(MessageFormat.format(getString("gb.nFederationProposalsToReview"),
						pendingProposals));
			}
		}

		super.setupPage(repositoryName, pageName);
	}

	protected Class<? extends BasePage> getRootNavPageClass() {
		return getClass();
	}

	private PageParameters getRootPageParameters() {
		if (reusePageParameters()) {
			PageParameters pp = getPageParameters();
			if (pp != null) {
				PageParameters params = new PageParameters(pp);
				// remove named project parameter
				params.remove("p");

				// remove named repository parameter
				params.remove("r");

				// remove named user parameter
				params.remove("user");

				// remove days back parameter if it is the default value
				if (params.containsKey("db")
						&& params.getInt("db") == app().settings().getInteger(Keys.web.activityDuration, 7)) {
					params.remove("db");
				}
				return params;
			}
		}
		return null;
	}

	protected boolean reusePageParameters() {
		return false;
	}

	private void loginUser(UserModel user) {
		if (user != null) {
			// Set the user into the session
			GitBlitWebSession session = GitBlitWebSession.get();
			// issue 62: fix session fixation vulnerability
			session.replaceSession();
			session.setUser(user);

			// Set Cookie
			if (app().settings().getBoolean(Keys.web.allowCookieAuthentication, false)) {
				WebResponse response = (WebResponse) getRequestCycle().getResponse();
				app().authentication().setCookie(response.getHttpServletResponse(), user);
			}

			if (!session.continueRequest()) {
				PageParameters params = getPageParameters();
				if (params == null) {
					// redirect to this page
					setResponsePage(getClass());
				} else {
					// Strip username and password and redirect to this page
					params.remove("username");
					params.remove("password");
					setResponsePage(getClass(), params);
				}
			}
		}
	}

	protected List<RepositoryModel> getRepositoryModels() {
		if (repositoryModels.isEmpty()) {
			final UserModel user = GitBlitWebSession.get().getUser();
			List<RepositoryModel> repositories = app().repositories().getRepositoryModels(user);
			repositoryModels.addAll(repositories);
			Collections.sort(repositoryModels);
		}
		return repositoryModels;
	}

	protected void addDropDownMenus(List<NavLink> navLinks) {

	}

	protected List<com.gitblit.models.Menu.MenuItem> getRepositoryFilterItems(PageParameters params) {
		final UserModel user = GitBlitWebSession.get().getUser();
		Set<MenuItem> filters = new LinkedHashSet<MenuItem>();
		List<RepositoryModel> repositories = getRepositoryModels();

		// accessible repositories by federation set
		Map<String, AtomicInteger> setMap = new HashMap<String, AtomicInteger>();
		for (RepositoryModel repository : repositories) {
			for (String set : repository.federationSets) {
				String key = set.toLowerCase();
				if (setMap.containsKey(key)) {
					setMap.get(key).incrementAndGet();
				} else {
					setMap.put(key, new AtomicInteger(1));
				}
			}
		}
		if (setMap.size() > 0) {
			List<String> sets = new ArrayList<String>(setMap.keySet());
			Collections.sort(sets);
			for (String set : sets) {
				filters.add(new ToggleMenuItem(MessageFormat.format("{0} ({1})", set,
						setMap.get(set).get()), "set", set, params));
			}
			// divider
			filters.add(new MenuDivider());
		}

		// user's team memberships
		if (user != null && user.teams.size() > 0) {
			List<TeamModel> teams = new ArrayList<TeamModel>(user.teams);
			Collections.sort(teams);
			for (TeamModel team : teams) {
				filters.add(new ToggleMenuItem(MessageFormat.format("{0} ({1})", team.name,
						team.repositories.size()), "team", team.name, params));
			}
			// divider
			filters.add(new MenuDivider());
		}

		// custom filters
		String customFilters = app().settings().getString(Keys.web.customFilters, null);
		if (!StringUtils.isEmpty(customFilters)) {
			boolean addedExpression = false;
			List<String> expressions = StringUtils.getStringsFromValue(customFilters, "!!!");
			for (String expression : expressions) {
				if (!StringUtils.isEmpty(expression)) {
					addedExpression = true;
					filters.add(new ToggleMenuItem(null, "x", expression, params));
				}
			}
			// if we added any custom expressions, add a divider
			if (addedExpression) {
				filters.add(new MenuDivider());
			}
		}
		return new ArrayList<MenuItem>(filters);
	}

	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);
		if (daysBack < 1) {
			daysBack = 7;
		}
		if (daysBack > maxDaysBack) {
			daysBack = maxDaysBack;
		}
		PageParameters clonedParams;
		if (params == null) {
			clonedParams = new PageParameters();
		} else {
			clonedParams = new PageParameters(params);
		}

		if (!clonedParams.containsKey("db")) {
			clonedParams.put("db",  daysBack);
		}

		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));
		}
		List<Integer> choices = new ArrayList<Integer>(choicesSet);
		Collections.sort(choices);
		String lastDaysPattern = getString("gb.lastNDays");
		for (Integer db : choices) {
			if (db == 1) {
				items.add(new ParameterMenuItem(getString("gb.time.today"), "db", db.toString(), clonedParams));
			} else {
				String txt = MessageFormat.format(lastDaysPattern, db);
				items.add(new ParameterMenuItem(txt, "db", db.toString(), clonedParams));
			}
		}
		items.add(new MenuDivider());
		return items;
	}

	protected List<RepositoryModel> getRepositories(PageParameters params) {
		if (params == null) {
			return getRepositoryModels();
		}

		boolean hasParameter = false;
		String projectName = WicketUtils.getProjectName(params);
		String userName = WicketUtils.getUsername(params);
		if (StringUtils.isEmpty(projectName)) {
			if (!StringUtils.isEmpty(userName)) {
				projectName = ModelUtils.getPersonalPath(userName);
			}
		}
		String repositoryName = WicketUtils.getRepositoryName(params);
		String set = WicketUtils.getSet(params);
		String regex = WicketUtils.getRegEx(params);
		String team = WicketUtils.getTeam(params);
		int daysBack = params.getInt("db", 0);
		int maxDaysBack = app().settings().getInteger(Keys.web.activityDurationMaximum, 30);

		List<RepositoryModel> availableModels = getRepositoryModels();
		Set<RepositoryModel> models = new HashSet<RepositoryModel>();

		if (!StringUtils.isEmpty(repositoryName)) {
			// try named repository
			hasParameter = true;
			for (RepositoryModel model : availableModels) {
				if (model.name.equalsIgnoreCase(repositoryName)) {
					models.add(model);
					break;
				}
			}
		}

		if (!StringUtils.isEmpty(projectName)) {
			// try named project
			hasParameter = true;
			if (projectName.equalsIgnoreCase(app().settings().getString(Keys.web.repositoryRootGroupName, "main"))) {
				// root project/group
				for (RepositoryModel model : availableModels) {
					if (model.name.indexOf('/') == -1) {
						models.add(model);
					}
				}
			} else {
				// named project/group
				String group = projectName.toLowerCase() + "/";
				for (RepositoryModel model : availableModels) {
					if (model.name.toLowerCase().startsWith(group)) {
						models.add(model);
					}
				}
			}
		}

		if (!StringUtils.isEmpty(regex)) {
			// filter the repositories by the regex
			hasParameter = true;
			Pattern pattern = Pattern.compile(regex);
			for (RepositoryModel model : availableModels) {
				if (pattern.matcher(model.name).find()) {
					models.add(model);
				}
			}
		}

		if (!StringUtils.isEmpty(set)) {
			// filter the repositories by the specified sets
			hasParameter = true;
			List<String> sets = StringUtils.getStringsFromValue(set, ",");
			for (RepositoryModel model : availableModels) {
				for (String curr : sets) {
					if (model.federationSets.contains(curr)) {
						models.add(model);
					}
				}
			}
		}

		if (!StringUtils.isEmpty(team)) {
			// filter the repositories by the specified teams
			hasParameter = true;
			List<String> teams = StringUtils.getStringsFromValue(team, ",");

			// need TeamModels first
			List<TeamModel> teamModels = new ArrayList<TeamModel>();
			for (String name : teams) {
				TeamModel teamModel = app().users().getTeamModel(name);
				if (teamModel != null) {
					teamModels.add(teamModel);
				}
			}

			// brute-force our way through finding the matching models
			for (RepositoryModel repositoryModel : availableModels) {
				for (TeamModel teamModel : teamModels) {
					if (teamModel.hasRepositoryPermission(repositoryModel.name)) {
						models.add(repositoryModel);
					}
				}
			}
		}

		if (!hasParameter) {
			models.addAll(availableModels);
		}

		// time-filter the list
		if (daysBack > 0) {
			if (maxDaysBack > 0 && daysBack > maxDaysBack) {
				daysBack = maxDaysBack;
			}
			Calendar cal = Calendar.getInstance();
			cal.set(Calendar.HOUR_OF_DAY, 0);
			cal.set(Calendar.MINUTE, 0);
			cal.set(Calendar.SECOND, 0);
			cal.set(Calendar.MILLISECOND, 0);
			cal.add(Calendar.DATE, -1 * daysBack);
			Date threshold = cal.getTime();
			Set<RepositoryModel> timeFiltered = new HashSet<RepositoryModel>();
			for (RepositoryModel model : models) {
				if (model.lastChange.after(threshold)) {
					timeFiltered.add(model);
				}
			}
			models = timeFiltered;
		}

		List<RepositoryModel> list = new ArrayList<RepositoryModel>(models);
		Collections.sort(list);
		return list;
	}

	/**
	 * Inline login form.
	 */
	private class LoginForm extends Fragment {
		private static final long serialVersionUID = 1L;

		public LoginForm(String id, String markupId, MarkupContainer markupProvider) {
			super(id, markupId, markupProvider);
			setRenderBodyOnly(true);

			SessionlessForm<Void> loginForm = new SessionlessForm<Void>("loginForm", RootPage.this.getClass(), getPageParameters()) {

				private static final long serialVersionUID = 1L;

				@Override
				public void onSubmit() {
					String username = RootPage.this.username.getObject();
					char[] password = RootPage.this.password.getObject().toCharArray();

					UserModel user = app().authentication().authenticate(username, password);
					if (user == null) {
						error(getString("gb.invalidUsernameOrPassword"));
					} else if (user.username.equals(Constants.FEDERATION_USER)) {
						// disallow the federation user from logging in via the
						// web ui
						error(getString("gb.invalidUsernameOrPassword"));
						user = null;
					} else {
						loginUser(user);
					}
				}
			};
			TextField<String> unameField = new TextField<String>("username", username);
			WicketUtils.setInputPlaceholder(unameField, markupProvider.getString("gb.username"));
			loginForm.add(unameField);
			PasswordTextField pwField = new PasswordTextField("password", password);
			WicketUtils.setInputPlaceholder(pwField, markupProvider.getString("gb.password"));
			loginForm.add(pwField);
			add(loginForm);
		}
	}

	/**
	 * Menu for the authenticated user.
	 */
	class UserMenu extends Fragment {

		private static final long serialVersionUID = 1L;

		public UserMenu(String id, String markupId, MarkupContainer markupProvider) {
			super(id, markupId, markupProvider);
			setRenderBodyOnly(true);
		}

		@Override
		protected void onInitialize() {
			super.onInitialize();

			GitBlitWebSession session = GitBlitWebSession.get();
			UserModel user = session.getUser();
			boolean editCredentials = app().authentication().supportsCredentialChanges(user);
			boolean standardLogin = session.authenticationType.isStandard();

			if (app().settings().getBoolean(Keys.web.allowGravatar, true)) {
				add(new GravatarImage("username", user, "navbarGravatar", 20, false));
			} else {
				add(new Label("username", user.getDisplayName()));
			}

			List<MenuItem> standardItems = new ArrayList<MenuItem>();
			standardItems.add(new MenuDivider());
			if (user.canAdmin() || user.canCreate()) {
				standardItems.add(new PageLinkMenuItem("gb.newRepository", app().getNewRepositoryPage()));
			}
			standardItems.add(new PageLinkMenuItem("gb.myProfile", UserPage.class,
					WicketUtils.newUsernameParameter(user.username)));
			if (editCredentials) {
				standardItems.add(new PageLinkMenuItem("gb.changePassword", ChangePasswordPage.class));
			}
			standardItems.add(new MenuDivider());
			add(newSubmenu("standardMenu", user.getDisplayName(), standardItems));

			if (showAdmin) {
				// admin menu
				List<MenuItem> adminItems = new ArrayList<MenuItem>();
				adminItems.add(new MenuDivider());
				adminItems.add(new PageLinkMenuItem("gb.users", UsersPage.class));
				adminItems.add(new PageLinkMenuItem("gb.teams", TeamsPage.class));

				boolean showRegistrations = app().federation().canFederate()
						&& app().settings().getBoolean(Keys.web.showFederationRegistrations, false);
				if (showRegistrations) {
					adminItems.add(new PageLinkMenuItem("gb.federation", FederationPage.class));
				}
				adminItems.add(new MenuDivider());

				add(newSubmenu("adminMenu", getString("gb.administration"), adminItems));
			} else {
				add(new Label("adminMenu").setVisible(false));
			}

			// plugin extension items
			List<MenuItem> extensionItems = new ArrayList<MenuItem>();
			List<UserMenuExtension> extensions = app().plugins().getExtensions(UserMenuExtension.class);
			for (UserMenuExtension ext : extensions) {
				List<MenuItem> items = ext.getMenuItems(user);
				extensionItems.addAll(items);
			}

			if (extensionItems.isEmpty()) {
				// no extension items
				add(new Label("extensionsMenu").setVisible(false));
			} else {
				// found extension items
				extensionItems.add(0, new MenuDivider());
				add(newSubmenu("extensionsMenu", getString("gb.extensions"), extensionItems));
				extensionItems.add(new MenuDivider());
			}

			add(new BookmarkablePageLink<Void>("logout",
					LogoutPage.class).setVisible(standardLogin));
		}

		/**
		 * Creates a submenu.  This is not actually submenu because we're using
		 * an older Twitter Bootstrap which is pre-submenu.
		 *
		 * @param wicketId
		 * @param submenuTitle
		 * @param menuItems
		 * @return a submenu fragment
		 */
		private Fragment newSubmenu(String wicketId, String submenuTitle, List<MenuItem> menuItems) {
			Fragment submenu = new Fragment(wicketId, "submenuFragment", this);
			submenu.add(new Label("submenuTitle", submenuTitle).setRenderBodyOnly(true));
			ListDataProvider<MenuItem> menuItemsDp = new ListDataProvider<MenuItem>(menuItems);
			DataView<MenuItem> submenuItems = new DataView<MenuItem>("submenuItem", menuItemsDp) {
				private static final long serialVersionUID = 1L;

				@Override
				public void populateItem(final Item<MenuItem> menuItem) {
					final MenuItem item = menuItem.getModelObject();
					String name = item.toString();
					try {
						// try to lookup translation
						name = getString(name);
					} catch (Exception e) {
					}
					if (item instanceof PageLinkMenuItem) {
						// link to another Wicket page
						PageLinkMenuItem pageLink = (PageLinkMenuItem) item;
						menuItem.add(new LinkPanel("submenuLink", null, null, name, pageLink.getPageClass(),
								pageLink.getPageParameters(), false).setRenderBodyOnly(true));
					} else if (item instanceof ExternalLinkMenuItem) {
						// link to a specified href
						ExternalLinkMenuItem extLink = (ExternalLinkMenuItem) item;
						menuItem.add(new LinkPanel("submenuLink", null, name, extLink.getHref(),
								extLink.openInNewWindow()).setRenderBodyOnly(true));
					} else if (item instanceof MenuDivider) {
						// divider
						menuItem.add(new Label("submenuLink").setRenderBodyOnly(true));
						WicketUtils.setCssClass(menuItem, "divider");
					}
				}
			};
			submenu.add(submenuItems);
			submenu.setRenderBodyOnly(true);
			return submenu;
		}
	}
}