From 797322eb90a1f5d21166fd691479b050ad7b754b Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 21 Nov 2011 09:24:05 -0500
Subject: [PATCH] Tighter Gravatar integration. New window/tab on most activity links.

---
 src/com/gitblit/wicket/panels/GravatarImage.java      |   68 ++++++++
 src/com/gitblit/wicket/pages/CommitPage.html          |    4 
 src/com/gitblit/wicket/pages/GravatarProfilePage.html |   20 ++
 src/com/gitblit/wicket/panels/GravatarImage.html      |    9 +
 src/com/gitblit/wicket/panels/LinkPanel.java          |   17 +
 /dev/null                                             |   62 -------
 src/com/gitblit/wicket/pages/CommitPage.java          |    2 
 src/com/gitblit/wicket/panels/ActivityPanel.java      |   11 
 src/com/gitblit/utils/ActivityUtils.java              |   66 ++++++++
 src/com/gitblit/wicket/pages/GravatarProfilePage.java |   64 ++++++++
 src/com/gitblit/models/GravatarProfile.java           |   83 ++++++++++
 tests/com/gitblit/tests/ActivityTest.java             |   31 +++
 src/com/gitblit/wicket/GitBlitWebApp.java             |    4 
 src/com/gitblit/wicket/panels/ActivityPanel.html      |    4 
 14 files changed, 369 insertions(+), 76 deletions(-)

diff --git a/src/com/gitblit/models/GravatarProfile.java b/src/com/gitblit/models/GravatarProfile.java
new file mode 100644
index 0000000..aa128ce
--- /dev/null
+++ b/src/com/gitblit/models/GravatarProfile.java
@@ -0,0 +1,83 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Represents a Gravatar profile.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GravatarProfile implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public String id;
+	public String hash;
+	public String requestHash;
+	public String displayName;
+	public String preferredUsername;
+	public String currentLocation;
+	public String aboutMe;
+	
+	public String profileUrl;
+	public String thumbnailUrl;
+	public List<ProfileObject> photos;
+//	public Map<String, String> profileBackground;
+//	public Map<String, String> name;
+
+	public List<ProfileObject> phoneNumbers;
+	public List<ProfileObject> emails;
+	public List<ProfileObject> ims;
+	public List<Account> accounts;
+	public List<ProfileObject> urls;
+
+	public static class ProfileObject implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		public String title;
+		public String type;
+		public String value;
+		public boolean primary;
+
+		@Override
+		public String toString() {
+			return value;
+		}
+	}
+
+	public static class Account implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		public String domain;
+		public String display;
+		public String url;
+		public String username;
+		public String userid;
+		public boolean verified;
+		public String shortname;
+
+		@Override
+		public String toString() {
+			return display;
+		}
+	}
+}
diff --git a/src/com/gitblit/utils/ActivityUtils.java b/src/com/gitblit/utils/ActivityUtils.java
index 8c8a7ec..204fe3c 100644
--- a/src/com/gitblit/utils/ActivityUtils.java
+++ b/src/com/gitblit/utils/ActivityUtils.java
@@ -15,7 +15,11 @@
  */
 package com.gitblit.utils;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Type;
 import java.text.DateFormat;
+import java.text.MessageFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -33,8 +37,10 @@
 import com.gitblit.GitBlit;
 import com.gitblit.models.Activity;
 import com.gitblit.models.Activity.RepositoryCommit;
+import com.gitblit.models.GravatarProfile;
 import com.gitblit.models.RefModel;
 import com.gitblit.models.RepositoryModel;
+import com.google.gson.reflect.TypeToken;
 
 /**
  * Utility class for building activity information from repositories.
@@ -127,4 +133,64 @@
 		}
 		return recentActivity;
 	}
+
+	/**
+	 * Returns the Gravatar profile, if available, for the specified email
+	 * address.
+	 * 
+	 * @param emailaddress
+	 * @return a Gravatar Profile
+	 * @throws IOException
+	 */
+	public static GravatarProfile getGravatarProfileFromAddress(String emailaddress)
+			throws IOException {
+		return getGravatarProfile(StringUtils.getMD5(emailaddress.toLowerCase()));
+	}
+
+	/**
+	 * Creates a Gravatar thumbnail url from the specified email address.
+	 * 
+	 * @param email
+	 *            address to query Gravatar
+	 * @param width
+	 *            size of thumbnail. if width <= 0, the defalt of 60 is used.
+	 * @return
+	 */
+	public static String getGravatarThumbnailUrl(String email, int width) {
+		if (width <= 0) {
+			width = 60;
+		}
+		String emailHash = StringUtils.getMD5(email);
+		String url = MessageFormat.format(
+				"http://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", emailHash, width);
+		return url;
+	}
+
+	/**
+	 * Returns the Gravatar profile, if available, for the specified hashcode.
+	 * address.
+	 * 
+	 * @param hash
+	 *            the hash of the email address
+	 * @return a Gravatar Profile
+	 * @throws IOException
+	 */
+	public static GravatarProfile getGravatarProfile(String hash) throws IOException {
+		String url = MessageFormat.format("http://www.gravatar.com/{0}.json", hash);
+		// Gravatar has a complex json structure
+		Type profileType = new TypeToken<Map<String, List<GravatarProfile>>>() {
+		}.getType();
+		Map<String, List<GravatarProfile>> profiles = null;
+		try {
+			profiles = JsonUtils.retrieveJson(url, profileType);
+		} catch (FileNotFoundException e) {
+		}
+		if (profiles == null || profiles.size() == 0) {
+			return null;
+		}
+		// due to the complex json structure we need to pull out the profile
+		// from a list 2 levels deep
+		GravatarProfile profile = profiles.values().iterator().next().get(0);
+		return profile;
+	}
 }
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java
index 79083ef..e2391d6 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/com/gitblit/wicket/GitBlitWebApp.java
@@ -34,6 +34,7 @@
 import com.gitblit.wicket.pages.CommitPage;
 import com.gitblit.wicket.pages.DocsPage;
 import com.gitblit.wicket.pages.FederationRegistrationPage;
+import com.gitblit.wicket.pages.GravatarProfilePage;
 import com.gitblit.wicket.pages.HistoryPage;
 import com.gitblit.wicket.pages.LogPage;
 import com.gitblit.wicket.pages.MarkdownPage;
@@ -104,8 +105,9 @@
 		// federation urls
 		mount("/proposal", ReviewProposalPage.class, "t");
 		mount("/registration", FederationRegistrationPage.class, "u", "n");
-		
+
 		mount("/activity", ActivityPage.class, "r", "h");
+		mount("/gravatar", GravatarProfilePage.class, "h");
 	}
 
 	private void mount(String location, Class<? extends WebPage> clazz, String... parameters) {
diff --git a/src/com/gitblit/wicket/GravatarImage.java b/src/com/gitblit/wicket/GravatarImage.java
deleted file mode 100644
index 88c97c9..0000000
--- a/src/com/gitblit/wicket/GravatarImage.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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;
-
-import java.text.MessageFormat;
-
-import org.apache.wicket.AttributeModifier;
-import org.apache.wicket.markup.ComponentTag;
-import org.apache.wicket.markup.html.WebComponent;
-import org.apache.wicket.model.Model;
-import org.eclipse.jgit.lib.PersonIdent;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Represents a Gravatar image.
- * 
- * @author James Moger
- * 
- */
-public class GravatarImage extends WebComponent {
-
-	private static final long serialVersionUID = 1L;
-
-	public GravatarImage(String id, PersonIdent person) {
-		this(id, person, 0);
-	}
-	
-	public GravatarImage(String id, PersonIdent person, int width) {
-		super(id);
-		if (width <= 0) {
-			width = 60;
-		}
-		String authorhash = StringUtils.getMD5(person.getEmailAddress().toLowerCase());
-		String url = MessageFormat.format("http://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", authorhash, width);
-		add(new AttributeModifier("src", true, new Model<String>(url)));
-		setVisible(GitBlit.getBoolean(Keys.web.allowGravatar, true));
-		WicketUtils.setCssClass(this, "gravatar");
-	}
-
-	@Override
-	protected void onComponentTag(ComponentTag tag) {
-		super.onComponentTag(tag);
-		checkComponentTag(tag, "img");
-	}
-
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/CommitPage.html b/src/com/gitblit/wicket/pages/CommitPage.html
index 2af05e1..fd2c05c 100644
--- a/src/com/gitblit/wicket/pages/CommitPage.html
+++ b/src/com/gitblit/wicket/pages/CommitPage.html
@@ -16,7 +16,7 @@
 	<div wicket:id="commitHeader">[commit header]</div>
 	
 	<!--  Author Gravatar -->
-	<img style="float:right;vertical-align: top;" wicket:id="authorAvatar" />
+	<span style="float:right;vertical-align: top;" wicket:id="authorAvatar" />
 	
 	<!-- commit info -->
 	<table class="plain">
@@ -57,7 +57,7 @@
 					<tr><td><span class="sha1" wicket:id="authorDate"></span></td></tr>
 				</table>
 				<!--  Note Author Gravatar -->
-				<img style="vertical-align: top;" wicket:id="noteAuthorAvatar" />				
+				<span style="vertical-align: top;" wicket:id="noteAuthorAvatar" />				
 			</td>
 			<td class="message"><span class="sha1" wicket:id="noteContent"></span></td>
 		</tr>
diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java
index bfe03f1..1fd2653 100644
--- a/src/com/gitblit/wicket/pages/CommitPage.java
+++ b/src/com/gitblit/wicket/pages/CommitPage.java
@@ -38,10 +38,10 @@
 import com.gitblit.models.GitNote;
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.utils.JGitUtils;
-import com.gitblit.wicket.GravatarImage;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.CommitLegendPanel;
+import com.gitblit.wicket.panels.GravatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.RefsPanel;
 
diff --git a/src/com/gitblit/wicket/pages/GravatarProfilePage.html b/src/com/gitblit/wicket/pages/GravatarProfilePage.html
new file mode 100644
index 0000000..1719d90
--- /dev/null
+++ b/src/com/gitblit/wicket/pages/GravatarProfilePage.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+	<div class="page-header">
+		<h2>Gravatar<small> / <span wicket:id="username">[username]</span></small></h2>
+	</div>
+	<img class="gravatar" wicket:id="profileImage"></img>
+	<h2 wicket:id="displayName"></h2>
+	<div style="color:#888;"wicket:id="location"></div>
+	<div style="padding-top:5px;" wicket:id="aboutMe"></div>
+	<p></p>
+	<a wicket:id="profileLink">Complete profile on Gravatar.com</a>
+	<p></p>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/GravatarProfilePage.java b/src/com/gitblit/wicket/pages/GravatarProfilePage.java
new file mode 100644
index 0000000..1d702a7
--- /dev/null
+++ b/src/com/gitblit/wicket/pages/GravatarProfilePage.java
@@ -0,0 +1,64 @@
+/*
+ * 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.io.IOException;
+import java.text.MessageFormat;
+
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.image.Image;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.model.Model;
+
+import com.gitblit.models.GravatarProfile;
+import com.gitblit.utils.ActivityUtils;
+import com.gitblit.wicket.WicketUtils;
+
+/**
+ * Gravatar Profile Page shows the Gravatar information, if available.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GravatarProfilePage extends RootPage {
+
+	public GravatarProfilePage(PageParameters params) {
+		super();
+		setupPage("", "");
+		String object = WicketUtils.getObject(params);
+		GravatarProfile profile = null;
+		try {
+			if (object.indexOf('@') > -1) {
+				profile = ActivityUtils.getGravatarProfileFromAddress(object);
+			} else {
+				profile = ActivityUtils.getGravatarProfile(object);
+			}
+		} catch (IOException e) {
+			error(MessageFormat.format("Failed to find Gravatar profile for {0}", object), e, true);
+		}
+
+		add(new Label("displayName", profile.displayName));
+		add(new Label("username", profile.preferredUsername));
+		add(new Label("location", profile.currentLocation));
+		add(new Label("aboutMe", profile.aboutMe));
+		Image image = new Image("profileImage");
+		image.add(new AttributeModifier("src", true, new Model<String>(profile.thumbnailUrl + "?s=256&d=identicon")));
+		add(image);
+		add(new ExternalLink("profileLink", profile.profileUrl));
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.html b/src/com/gitblit/wicket/panels/ActivityPanel.html
index 668e7c9..90e808f 100644
--- a/src/com/gitblit/wicket/panels/ActivityPanel.html
+++ b/src/com/gitblit/wicket/panels/ActivityPanel.html
@@ -19,7 +19,7 @@
 		<td style="width:10em;text-align:left;vertical-align: middle;">
 			<span wicket:id="repository" class="repositorySwatch">[repository link]</span>
 		</td>
-		<td style="width:30px;vertical-align: middle;"><img wicket:id="avatar" style="vertical-align: middle;"></img></td>
+		<td style="width:30px;vertical-align: middle;"><span wicket:id="avatar" style="vertical-align: middle;"></span></td>
 		<td class="author" style="vertical-align: middle;">
 			<img wicket:id="commitIcon" style="vertical-align: middle;"></img>
 			<span wicket:id="message">[shortlog commit link]</span><br/>
@@ -30,7 +30,7 @@
 		</td>
 		<td class="rightAlign" style="width:7em;vertical-align: middle;">
         	<span class="link">
-				<a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
+				<a wicket:id="view" target="_blank"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="diff" target="_blank"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree" target="_blank"><wicket:message key="gb.tree"></wicket:message></a>
 			</span>
 		</td>		
 	</wicket:fragment>
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.java b/src/com/gitblit/wicket/panels/ActivityPanel.java
index e9ec074..80621ae 100644
--- a/src/com/gitblit/wicket/panels/ActivityPanel.java
+++ b/src/com/gitblit/wicket/panels/ActivityPanel.java
@@ -29,7 +29,6 @@
 import com.gitblit.models.Activity.RepositoryCommit;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.GravatarImage;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.pages.CommitDiffPage;
 import com.gitblit.wicket.pages.CommitPage;
@@ -90,27 +89,27 @@
 						String author = commit.getAuthorIdent().getName();
 						LinkPanel authorLink = new LinkPanel("author", "list", author,
 								SearchPage.class, WicketUtils.newSearchParameter(commit.repository,
-										commit.getName(), author, Constants.SearchType.AUTHOR));
+										commit.getName(), author, Constants.SearchType.AUTHOR), true);
 						setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
 						fragment.add(authorLink);
 
 						// repository
 						LinkPanel repositoryLink = new LinkPanel("repository", null,
 								commit.repository, SummaryPage.class,
-								WicketUtils.newRepositoryParameter(commit.repository));
+								WicketUtils.newRepositoryParameter(commit.repository), true);
 						WicketUtils.setCssBackground(repositoryLink, commit.repository);
 						fragment.add(repositoryLink);
 
 						// repository branch
 						LinkPanel branchLink = new LinkPanel("branch", "list", commit.branch,
 								LogPage.class, WicketUtils.newObjectParameter(commit.repository,
-										commit.branch));
+										commit.branch), true);
 						WicketUtils.setCssStyle(branchLink, "color: #008000;");
 						fragment.add(branchLink);
 
 						LinkPanel commitid = new LinkPanel("commitid", "list subject",
 								commit.getShortName(), CommitPage.class,
-								WicketUtils.newObjectParameter(commit.repository, commit.getName()));
+								WicketUtils.newObjectParameter(commit.repository, commit.getName()), true);
 						fragment.add(commitid);
 
 						// message/commit link
@@ -118,7 +117,7 @@
 						String trimmedMessage = StringUtils.trimShortLog(shortMessage);
 						LinkPanel shortlog = new LinkPanel("message", "list subject",
 								trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
-										commit.repository, commit.getName()));
+										commit.repository, commit.getName()), true);
 						if (!shortMessage.equals(trimmedMessage)) {
 							WicketUtils.setHtmlTooltip(shortlog, shortMessage);
 						}
diff --git a/src/com/gitblit/wicket/panels/GravatarImage.html b/src/com/gitblit/wicket/panels/GravatarImage.html
new file mode 100644
index 0000000..9dda795
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/GravatarImage.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en">
+<wicket:panel>
+<a href="#" wicket:id="link"><img wicket:id="image"></img></a>
+</wicket:panel>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/GravatarImage.java b/src/com/gitblit/wicket/panels/GravatarImage.java
new file mode 100644
index 0000000..0dc0502
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/GravatarImage.java
@@ -0,0 +1,68 @@
+/*
+ * 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.panels;
+
+import java.text.MessageFormat;
+
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.image.Image;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+import org.eclipse.jgit.lib.PersonIdent;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.ActivityUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.GravatarProfilePage;
+
+/**
+ * Represents a Gravatar image and links to the Gravatar profile page.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GravatarImage extends Panel {
+
+	private static final long serialVersionUID = 1L;
+
+	public GravatarImage(String id, PersonIdent person) {
+		this(id, person, 0);
+	}
+
+	public GravatarImage(String id, PersonIdent person, int width) {
+		super(id);
+
+		String email = person.getEmailAddress().toLowerCase();
+		String hash = StringUtils.getMD5(email);
+		Link<Void> link = new BookmarkablePageLink<Void>("link", GravatarProfilePage.class,
+				WicketUtils.newObjectParameter(hash));
+		link.add(new SimpleAttributeModifier("target", "_blank"));
+		String url = ActivityUtils.getGravatarThumbnailUrl(email, width);
+		Image image = new Image("image");
+		image.add(new AttributeModifier("src", true, new Model<String>(url)));
+		WicketUtils.setCssClass(image, "gravatar");
+		link.add(image);
+		WicketUtils.setHtmlTooltip(link,
+				MessageFormat.format("View Gravatar profile for {0}", person.getName()));
+		add(link);
+		setVisible(GitBlit.getBoolean(Keys.web.allowGravatar, true));
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/LinkPanel.java b/src/com/gitblit/wicket/panels/LinkPanel.java
index 475a71e..2872d4b 100644
--- a/src/com/gitblit/wicket/panels/LinkPanel.java
+++ b/src/com/gitblit/wicket/panels/LinkPanel.java
@@ -33,16 +33,26 @@
 
 	public LinkPanel(String wicketId, String linkCssClass, String label,
 			Class<? extends WebPage> clazz) {
-		this(wicketId, linkCssClass, new Model<String>(label), clazz, null);
+		this(wicketId, linkCssClass, new Model<String>(label), clazz, null, false);
 	}
 
 	public LinkPanel(String wicketId, String linkCssClass, String label,
 			Class<? extends WebPage> clazz, PageParameters parameters) {
-		this(wicketId, linkCssClass, new Model<String>(label), clazz, parameters);
+		this(wicketId, linkCssClass, new Model<String>(label), clazz, parameters, false);
+	}
+
+	public LinkPanel(String wicketId, String linkCssClass, String label,
+			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
+		this(wicketId, linkCssClass, new Model<String>(label), clazz, parameters, newWindow);
 	}
 
 	public LinkPanel(String wicketId, String linkCssClass, IModel<String> model,
 			Class<? extends WebPage> clazz, PageParameters parameters) {
+		this(wicketId, linkCssClass, model, clazz, parameters, false);
+	}
+
+	public LinkPanel(String wicketId, String linkCssClass, IModel<String> model,
+			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
 		super(wicketId);
 		this.labelModel = model;
 		Link<Void> link = null;
@@ -51,6 +61,9 @@
 		} else {
 			link = new BookmarkablePageLink<Void>("link", clazz, parameters);
 		}
+		if (newWindow) {
+			link.add(new SimpleAttributeModifier("target", "_blank"));
+		}
 		if (linkCssClass != null) {
 			link.add(new SimpleAttributeModifier("class", linkCssClass));
 		}
diff --git a/tests/com/gitblit/tests/ActivityTest.java b/tests/com/gitblit/tests/ActivityTest.java
new file mode 100644
index 0000000..b7e5f7a
--- /dev/null
+++ b/tests/com/gitblit/tests/ActivityTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.tests;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import com.gitblit.models.GravatarProfile;
+import com.gitblit.utils.ActivityUtils;
+
+public class ActivityTest extends TestCase {
+
+	public void testGravatarProfile() throws IOException {		
+		GravatarProfile profile = ActivityUtils.getGravatarProfile("beau@dentedreality.com.au");
+		assertEquals("beau", profile.preferredUsername);
+	}
+}
\ No newline at end of file

--
Gitblit v1.9.1