Index: roster-api/src/java/org/sakaiproject/api/app/roster/RosterManager.java =================================================================== --- roster-api/src/java/org/sakaiproject/api/app/roster/RosterManager.java (.../trunk) (revision 64756) +++ roster-api/src/java/org/sakaiproject/api/app/roster/RosterManager.java (.../branches/SAK-14744) (revision 64756) @@ -22,8 +22,11 @@ package org.sakaiproject.api.app.roster; import java.util.List; +import java.util.Map; +import java.util.Set; import org.sakaiproject.section.api.coursemanagement.CourseSection; +import org.sakaiproject.site.api.Group; /** @@ -93,4 +96,10 @@ * Display section/group dropdown filter when site has only a single group or section defined: true or false * @return true or false */ + + /** + * Is there a group defined in the site for the group membership link to be displayed? + * @return + */ + public boolean isGroupMembershipViewable(); } Index: roster-api/src/java/org/sakaiproject/api/app/roster/Participant.java =================================================================== --- roster-api/src/java/org/sakaiproject/api/app/roster/Participant.java (.../trunk) (revision 64756) +++ roster-api/src/java/org/sakaiproject/api/app/roster/Participant.java (.../branches/SAK-14744) (revision 64756) @@ -44,4 +44,5 @@ public boolean isOfficialPhotoPreferred(); public boolean isOfficialPhotoPublicAndPreferred(); public boolean isProfilePhotoPublic(); + public String getGroupsString(); } \ No newline at end of file Index: roster-app/src/java/org/sakaiproject/jsf/roster/GroupTextTruncateConverter.java =================================================================== --- roster-app/src/java/org/sakaiproject/jsf/roster/GroupTextTruncateConverter.java (.../trunk) (revision 0) +++ roster-app/src/java/org/sakaiproject/jsf/roster/GroupTextTruncateConverter.java (.../branches/SAK-14744) (revision 64756) @@ -0,0 +1,18 @@ +package org.sakaiproject.jsf.roster; + +import javax.faces.convert.Converter; +import javax.faces.context.FacesContext; +import javax.faces.component.UIComponent; + +public class GroupTextTruncateConverter implements Converter { + + public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String string) { + if(string.length() > 80) return string.substring(0,80) + "..."; + return string; + } + + public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object object) { + if(((String)object).length() > 80) return ((String)object).substring(0,80) +"..."; + return (String)object; + } +} Index: roster-app/src/java/org/sakaiproject/tool/roster/EnrolledParticipant.java =================================================================== --- roster-app/src/java/org/sakaiproject/tool/roster/EnrolledParticipant.java (.../trunk) (revision 64756) +++ roster-app/src/java/org/sakaiproject/tool/roster/EnrolledParticipant.java (.../branches/SAK-14744) (revision 64756) @@ -88,4 +88,8 @@ public boolean isOfficialPhotoPreferred() { return participant.isOfficialPhotoPreferred(); } + + public String getGroupsString() { + return participant.getGroupsString(); + } } Index: roster-app/src/java/org/sakaiproject/tool/roster/RosterGroupMembership.java =================================================================== --- roster-app/src/java/org/sakaiproject/tool/roster/RosterGroupMembership.java (.../trunk) (revision 0) +++ roster-app/src/java/org/sakaiproject/tool/roster/RosterGroupMembership.java (.../branches/SAK-14744) (revision 64756) @@ -0,0 +1,286 @@ +/********************************************************************************** + * $URL: https://source.sakaiproject.org/svn/roster/trunk/roster-app/src/java/org/sakaiproject/tool/roster/RosterOverview.java $ + * $Id: RosterOverview.java 51318 2008-08-24 05:28:47Z csev@umich.edu $ + *********************************************************************************** + * + * Copyright (c) 2007 Sakai Foundation + * + * Licensed under the Educational Community 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.osedu.org/licenses/ECL-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 org.sakaiproject.tool.roster; + +import java.text.Collator; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.Map.Entry; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.sakaiproject.api.app.roster.Participant; +import org.sakaiproject.api.app.roster.RosterFunctions; +import org.sakaiproject.authz.api.Member; +import org.sakaiproject.component.cover.ServerConfigurationService; +import org.sakaiproject.exception.IdUnusedException; +import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterCsv; +import org.sakaiproject.jsf.spreadsheet.SpreadsheetUtil; +import org.sakaiproject.jsf.util.LocaleUtil; +import org.sakaiproject.section.api.coursemanagement.CourseSection; +import org.sakaiproject.site.api.Group; +import org.sakaiproject.site.api.Site; +import org.sakaiproject.site.api.SiteService; + +public class RosterGroupMembership extends BaseRosterPageBean { + private static final Log log = LogFactory.getLog(RosterGroupMembership.class); + + private static final String DISPLAY_ROSTER_PRIVACY_MSG = "roster.privacy.display"; + + // UI method calls + public boolean isGroupedBy() { + return filter.isGroupedBy(); + } + @SuppressWarnings("unchecked") + public Collection getGroupedParticipants() { + List groupedParticipants = null; + Site site = null; + + try { + site = filter.services.siteService.getSite(filter.getSiteReference().substring(6)); + } catch (IdUnusedException e) { + log.error("Unable to find site for: " + getSiteReference() + " " + e.getMessage(), e); + return null; + } + + if (site != null) { + groupedParticipants = new ArrayList(); + Collection groups = (Collection) site.getGroups(); + + //Use a HashSet because we'll have to use .removeAll() on it many times + //.remove() is roughly constant time for a HashSet. + Set unassignedParticipants = new HashSet( + filter.services.rosterManager.getRoster() + ); + + for(Iterator groupIter = groups.iterator(); groupIter.hasNext();) + { + Group group = groupIter.next(); + List roster = filter.services.rosterManager.getRoster(group.getReference()); + + //remove each grouped participant from the 'unassignedParticipants' set + unassignedParticipants.removeAll(roster); + + groupedParticipants.add(new GroupedParticipants(group.getTitle(), roster, roster.size(), getRoleCountMessage(filter.findRoleCounts(roster)))); + } + + // if we have participants who are ungrouped, we add them here to a new one for rendering called "Unassigned" + if (!unassignedParticipants.isEmpty()) + { + String unassigned = LocaleUtil.getLocalizedString(FacesContext.getCurrentInstance(), ServicesBean.MESSAGE_BUNDLE, "roster_group_unassigned"); + groupedParticipants.add( + new GroupedParticipants( + unassigned, + unassignedParticipants, + unassignedParticipants.size(), + getRoleCountMessage(filter.findRoleCounts(unassignedParticipants)) + ) + ); + } + + Collections.sort(groupedParticipants, sortByGroup()); + } + + return groupedParticipants; + } + + public class GroupedParticipants { + Collection groupedParticipants = new ArrayList(); + String groupTitle; + int groupedParticipantCount; + String roleCountMessage; + + public int getGroupedParticipantCount() { + return groupedParticipantCount; + } + public void setGroupedParticipantCount(int groupedParticipantCount) { + this.groupedParticipantCount = groupedParticipantCount; + } + public String getGroupTitle() { + return groupTitle; + } + public void setGroupTitle(String groupTitle) { + this.groupTitle = groupTitle; + } + public Collection getGroupedParticipants() { + return groupedParticipants; + } + public void setGroupedParticipants(Collection groupedParticipants) { + this.groupedParticipants = groupedParticipants; + } + public String getRoleCountMessage() { + return roleCountMessage; + } + public void setRoleCountMessage(String roleCountMessage) { + this.roleCountMessage = roleCountMessage; + } + + public GroupedParticipants() {} + + public GroupedParticipants(String groupTitle, Collection groupedParticipants, int groupedParticipantCount, String roleCountMessage) { + this.groupTitle = groupTitle; + this.groupedParticipants = groupedParticipants; + this.groupedParticipantCount = groupedParticipantCount; + this.roleCountMessage = roleCountMessage; + } + } + + private Comparator sortByGroup() { + Comparator groupComparator = new Comparator() { + public int compare(GroupedParticipants one, GroupedParticipants another) + { + return Collator.getInstance().compare(one.getGroupTitle(),another.getGroupTitle()); + } + }; + return groupComparator; + } + + private String getRoleCountMessage(SortedMap roleCounts) { + if(roleCounts.size() == 0) return ""; + StringBuilder sb = new StringBuilder(); + sb.append("("); + for(Iterator> iter = roleCounts.entrySet().iterator(); iter.hasNext();) { + Entry entry = iter.next(); + String[] params = new String[] {entry.getValue().toString(), entry.getKey()}; + sb.append(getFormattedMessage("role_breakdown_fragment", params)); + if (iter.hasNext()) { + sb.append(", "); + } + } + sb.append(")"); + return sb.toString(); + } + + private String getFormattedMessage(String key, String[] params) { + String rawString = LocaleUtil.getLocalizedString(FacesContext.getCurrentInstance(), ServicesBean.MESSAGE_BUNDLE, key); + MessageFormat format = new MessageFormat(rawString); + return format.format(params); + } + + public boolean isRenderModifyMembersInstructions() { + String siteRef = getSiteReference(); + return filter.services.securityService.unlock(SiteService.SECURE_UPDATE_SITE, siteRef) || + filter.services.securityService.unlock(SiteService.SECURE_UPDATE_SITE_MEMBERSHIP, siteRef); + } + + /** + * Determine whether privacy message should be displayed. Will be shown if + * roster.privacy.display in sakai.properties is "true" and the user does + * not have roster.viewhidden permission + * + * @return + */ + public boolean isRenderPrivacyMessage() { + String msgEnabled = ServerConfigurationService.getString(DISPLAY_ROSTER_PRIVACY_MSG, Boolean.TRUE.toString()); + if (Boolean.TRUE.toString().equalsIgnoreCase(msgEnabled)) { + return ! filter.services.securityService.unlock(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, getSiteReference()); + } else { + return ! filter.services.securityService.unlock(RosterFunctions.ROSTER_FUNCTION_VIEWALL, getSiteReference()); + } + } + + public String getPageTitle() { + filter.services.eventTrackingService.post(filter.services.eventTrackingService.newEvent("roster.view",getSiteReference(),false)); + return LocaleUtil.getLocalizedString(FacesContext.getCurrentInstance(), + ServicesBean.MESSAGE_BUNDLE, "title_group_membership"); + } + + public boolean isExportablePage() { + return filter.services.rosterManager.currentUserHasExportPerm(); + } + public void export(ActionEvent event) { + List> spreadsheetData = new ArrayList>(); + + FacesContext facesContext = FacesContext.getCurrentInstance(); + + // Add the header row + List header = new ArrayList(); + header.add(LocaleUtil.getLocalizedString(facesContext, ServicesBean.MESSAGE_BUNDLE, "facet_name")); + header.add(LocaleUtil.getLocalizedString(facesContext, ServicesBean.MESSAGE_BUNDLE, "facet_userId")); + header.add(LocaleUtil.getLocalizedString(facesContext, ServicesBean.MESSAGE_BUNDLE, "facet_role")); + header.add(LocaleUtil.getLocalizedString(facesContext, ServicesBean.MESSAGE_BUNDLE, "facet_groups")); + + if (isGroupedBy()) + { + for (Iterator gp = getGroupedParticipants().iterator(); gp.hasNext();) + { + GroupedParticipants gpList = gp.next(); + List groupTitleRow = new ArrayList(); + List blankRow = new ArrayList(); + blankRow.add(""); + spreadsheetData.add(blankRow); + groupTitleRow.add(gpList.getGroupTitle()); + spreadsheetData.add(groupTitleRow); + spreadsheetData.add(blankRow); + spreadsheetData.add(header); + for(Iterator participantIter = gpList.getGroupedParticipants().iterator(); participantIter.hasNext();) { + Participant participant = participantIter.next(); + List row = new ArrayList(); + row.add(participant.getUser().getSortName()); + row.add(participant.getUser().getDisplayId()); + row.add(participant.getRoleTitle()); + row.add(participant.getGroupsString()); + spreadsheetData.add(row); + } + } + } + else + { + spreadsheetData.add(header); + for(Iterator participantIter = getParticipants().iterator(); participantIter.hasNext();) { + Participant participant = participantIter.next(); + List row = new ArrayList(); + row.add(participant.getUser().getSortName()); + row.add(participant.getUser().getDisplayId()); + row.add(participant.getRoleTitle()); + row.add(participant.getGroupsString()); + spreadsheetData.add(row); + } + } + + String spreadsheetNameRaw = filter.getCourseFilterTitle(); + if (isGroupedBy()) + { + spreadsheetNameRaw = spreadsheetNameRaw + "_ByGroup"; + } + else + { + spreadsheetNameRaw = spreadsheetNameRaw + "_Ungrouped"; + } + + String spreadsheetName = getDownloadFileName(spreadsheetNameRaw); + SpreadsheetUtil.downloadSpreadsheetData(spreadsheetData, spreadsheetName, new SpreadsheetDataFileWriterCsv()); + } +} Index: roster-app/src/java/org/sakaiproject/tool/roster/FilteredParticipantListingBean.java =================================================================== --- roster-app/src/java/org/sakaiproject/tool/roster/FilteredParticipantListingBean.java (.../trunk) (revision 64756) +++ roster-app/src/java/org/sakaiproject/tool/roster/FilteredParticipantListingBean.java (.../branches/SAK-14744) (revision 64756) @@ -23,6 +23,7 @@ import java.io.Serializable; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.SortedMap; @@ -58,6 +59,7 @@ protected String defaultSearchText; protected String sectionFilter; + protected String groupFilter; // Cache the participants list so we don't have to fetch it twice (once for the list, @@ -123,8 +125,9 @@ return getSearchFilterString() != null && ! getSearchFilterString().equals(defaultSearchText) && ! searchMatches(getSearchFilterString(), participant.getUser()); } - protected SortedMap findRoleCounts(List participants) { + protected SortedMap findRoleCounts(Iterable participants) { SortedMap roleCountMap = new TreeMap(); + for(Iterator iter = participants.iterator(); iter.hasNext();) { Participant participant = iter.next(); String role = participant.getRoleTitle(); @@ -184,6 +187,14 @@ if("true".equals(services.serverConfigurationService.getString("roster.display.firstNameLastName"))) return true; return false; } + + public boolean isGroupedBy() { + String groupFilter = StringUtils.trimToNull(getGroupFilter()); + if (groupFilter == null) + return false; + boolean grouped = Boolean.valueOf(groupFilter); + return grouped; + } public String getSearchFilterString() { return searchFilter.getSearchFilter(); @@ -220,6 +231,17 @@ return "unknown site"; } } + + public void setGroupFilter(String groupFilter) { + // Don't allow this value to be set to the separater line + if(LocaleUtil.getLocalizedString(FacesContext.getCurrentInstance(), + ServicesBean.MESSAGE_BUNDLE, "roster_section_sep_line") + .equals(groupFilter)) { + this.groupFilter = null; + } else { + this.groupFilter = StringUtils.trimToNull(groupFilter); + } + } public void setSectionFilter(String sectionFilter) { // Don't allow this value to be set to the separater line @@ -262,6 +284,24 @@ return format.format(params); } + public String getGroupFilter() { + return groupFilter; + } + + public List getGroupSelectItems() { + List list = new ArrayList(); + + FacesContext facesContext = FacesContext.getCurrentInstance(); + String ungrouped = LocaleUtil.getLocalizedString(facesContext, ServicesBean.MESSAGE_BUNDLE, "roster_group_ungrouped"); + String byGroup = LocaleUtil.getLocalizedString(facesContext, ServicesBean.MESSAGE_BUNDLE, "roster_group_bygroup"); + + // Add the "all" select option and a separator line + list.add(new SelectItem("false", ungrouped)); + list.add(new SelectItem("true", byGroup)); + + return list; + } + public boolean isDisplayingParticipants() { // if we have entries in the roleCounts map, we have participants to display return ! roleCounts.isEmpty(); Index: roster-app/src/java/org/sakaiproject/tool/roster/FilteredStatusListingBean.java =================================================================== --- roster-app/src/java/org/sakaiproject/tool/roster/FilteredStatusListingBean.java (.../trunk) (revision 64756) +++ roster-app/src/java/org/sakaiproject/tool/roster/FilteredStatusListingBean.java (.../branches/SAK-14744) (revision 64756) @@ -125,6 +125,7 @@ public boolean isOfficialPhotoPreferred() {return false;} public boolean isOfficialPhotoPublicAndPreferred() {return false;} public boolean isProfilePhotoPublic() {return false;} + public String getGroupsString() {return "";} }; EnrolledParticipant ep = new EnrolledParticipant(p, statusCodes.get(enr.getEnrollmentStatus()), enr.getCredits()); participants.add(ep); Index: roster-app/src/java/org/sakaiproject/tool/roster/BaseRosterPageBean.java =================================================================== --- roster-app/src/java/org/sakaiproject/tool/roster/BaseRosterPageBean.java (.../trunk) (revision 64756) +++ roster-app/src/java/org/sakaiproject/tool/roster/BaseRosterPageBean.java (.../branches/SAK-14744) (revision 64756) @@ -154,6 +154,7 @@ protected Boolean renderStatusLink; protected Boolean renderPicturesLink; protected Boolean renderProfileLinks; + protected Boolean renderGroupMembershipLink; public boolean isRenderStatusLink() { if(renderStatusLink == null) { @@ -177,6 +178,13 @@ } return renderProfileLinks.booleanValue(); } + + public boolean isRenderGroupMembershipLink() { + if(renderGroupMembershipLink == null) { + renderGroupMembershipLink = filter.services.rosterManager.isGroupMembershipViewable(); + } + return renderGroupMembershipLink.booleanValue(); + } public boolean isOfficialPhotosAvailableToCurrentUser() { if(renderOfficialPhotos == null) { @@ -199,4 +207,10 @@ return rawString.replaceAll("\\W","_")+ "_"+dateString; } + public String groupMembership() + { + // clears section filter for the group membership page + filter.sectionFilter = null; + return "groupMembership"; + } } Index: roster-app/src/bundle/org/sakaiproject/tool/roster/bundle/Messages.properties =================================================================== --- roster-app/src/bundle/org/sakaiproject/tool/roster/bundle/Messages.properties (.../trunk) (revision 64756) +++ roster-app/src/bundle/org/sakaiproject/tool/roster/bundle/Messages.properties (.../branches/SAK-14744) (revision 64756) @@ -5,10 +5,12 @@ navbar_overview=Overview navbar_pics=Pictures navbar_status= Enrollment Status +navbar_group_membership=Group Membership title_overview=Overview title_pictures=Pictures title_status=Enrollment Status +title_group_membership=Group Membership roster_search_text=Name or ID roster_search_button=Find @@ -19,6 +21,10 @@ roster_enrollment_status_all=All +roster_group_ungrouped=Ungrouped +roster_group_bygroup=By Group +roster_group_unassigned=Unassigned + facet_roster_pictures=Roster Pictures currently_displaying_participants=Currently showing {0} participants @@ -84,6 +90,7 @@ facet_role=Role facet_status=Status facet_credits=Credits +facet_groups=Groups view_profile=Profile view_profile_title=View Profile for @@ -95,6 +102,7 @@ view_profile_list_sort_role=Sort by role view_profile_list_sort_sections=Sort by sections or groups title_msg=To add or remove participants from the Roster, visit the Site Info tool. +title_msg_groups=To add or remove participants from Groups, visit the Site Info tool. no_participants = No results found no_participants_msg = No results found for "{0}" no_participants_in_section= No results found for "{0}" in {1} Index: roster-app/src/webapp/WEB-INF/faces-config.xml =================================================================== --- roster-app/src/webapp/WEB-INF/faces-config.xml (.../trunk) (revision 64756) +++ roster-app/src/webapp/WEB-INF/faces-config.xml (.../branches/SAK-14744) (revision 64756) @@ -55,6 +55,11 @@ /roster/status.jsp + + groupMembership + /roster/groupMembership.jsp + + @@ -224,6 +229,20 @@ #{enrollmentStatusPrefs} + + + groupMembership + org.sakaiproject.tool.roster.RosterGroupMembership + request + + filter + #{filter} + + + prefs + #{prefs} + + @@ -252,5 +271,10 @@ textTruncateConverter org.sakaiproject.jsf.roster.TextTruncateConverter + + + groupTextTruncateConverter + org.sakaiproject.jsf.roster.GroupTextTruncateConverter + Index: roster-app/src/webapp/roster/groupMembership.jsp =================================================================== --- roster-app/src/webapp/roster/groupMembership.jsp (.../trunk) (revision 0) +++ roster-app/src/webapp/roster/groupMembership.jsp (.../branches/SAK-14744) (revision 64756) @@ -0,0 +1,168 @@ +<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> +<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> +<%@ taglib uri="http://sakaiproject.org/jsf/sakai" prefix="sakai"%> +<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> +<% +response.setContentType("text/html; charset=UTF-8"); +%> + + + + + + + <%=""%> + + + <%@include file="inc/nav.jspf" %> + + + + + <%@include file="inc/groupsFilter.jspf" %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%-- Messages to display when there are no participants in the table above --%> + + + <%-- No filtering --%> + + + <%-- Filtering on section, but not user --%> + + + + + <%-- Filtering on user, but not section --%> + + + + + <%-- Filtering on section and user --%> + + + + + + + + + + + Index: roster-app/src/webapp/roster/inc/nav.jspf =================================================================== --- roster-app/src/webapp/roster/inc/nav.jspf (.../trunk) (revision 64756) +++ roster-app/src/webapp/roster/inc/nav.jspf (.../branches/SAK-14744) (revision 64756) @@ -1,7 +1,8 @@ - - - + + + + @@ -11,4 +12,4 @@ - + \ No newline at end of file Index: roster-app/src/webapp/roster/inc/groupsFilter.jspf =================================================================== --- roster-app/src/webapp/roster/inc/groupsFilter.jspf (.../trunk) (revision 0) +++ roster-app/src/webapp/roster/inc/groupsFilter.jspf (.../branches/SAK-14744) (revision 64756) @@ -0,0 +1,25 @@ +<%-- Initialize the filter --%> + + + + + + + + + + + + + + + + + + + + + + + + Index: roster-impl/src/java/org/sakaiproject/component/app/roster/ParticipantImpl.java =================================================================== --- roster-impl/src/java/org/sakaiproject/component/app/roster/ParticipantImpl.java (.../trunk) (revision 64756) +++ roster-impl/src/java/org/sakaiproject/component/app/roster/ParticipantImpl.java (.../branches/SAK-14744) (revision 64756) @@ -22,11 +22,15 @@ package org.sakaiproject.component.app.roster; import java.io.Serializable; +import java.util.Iterator; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.api.app.profile.Profile; import org.sakaiproject.api.app.roster.Participant; +import org.sakaiproject.section.api.coursemanagement.CourseSection; +import org.sakaiproject.site.api.Group; import org.sakaiproject.user.api.User; /** @@ -41,6 +45,7 @@ protected User user; protected Profile profile; protected String roleTitle; + protected String groupsString; /** * Constructs a ParticipantImpl. @@ -49,10 +54,11 @@ * @param profile * @param roleTitle */ - public ParticipantImpl(User user, Profile profile, String roleTitle) { + public ParticipantImpl(User user, Profile profile, String roleTitle, String groupsString) { this.user = user; this.profile = profile; this.roleTitle = roleTitle; + this.groupsString = groupsString; } public Profile getProfile() { @@ -100,5 +106,39 @@ if(bool == null) return false; return bool.booleanValue(); } + + public String getGroupsString() { + return groupsString; + } + public void setGroupsString(String groupsString) { + this.groupsString = groupsString; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return user.hashCode(); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + boolean rv = false; + Participant p = null; + + if (this == obj) { + rv = true; + } else if (user != null && obj != null && obj instanceof Participant) { + p = (Participant) obj; + + rv = user.equals(p.getUser()); + } + + return rv; + } } Index: roster-impl/src/java/org/sakaiproject/component/app/roster/RosterManagerImpl.java =================================================================== --- roster-impl/src/java/org/sakaiproject/component/app/roster/RosterManagerImpl.java (.../trunk) (revision 64756) +++ roster-impl/src/java/org/sakaiproject/component/app/roster/RosterManagerImpl.java (.../branches/SAK-14744) (revision 64756) @@ -21,8 +21,11 @@ package org.sakaiproject.component.app.roster; +import java.text.Collator; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -118,7 +121,7 @@ userIds.add(user.getId()); String roleTitle = getUserRoleTitle(user); - return new ParticipantImpl(user, profile, roleTitle); + return new ParticipantImpl(user, profile, roleTitle, null); } @@ -304,6 +307,14 @@ private List buildParticipantList(Map userMap, Map profilesMap) { List participants = new ArrayList(); + Site site = null; + try { + site = siteService().getSite(getSiteId()); + } catch (IdUnusedException e) { + log.error("getGroupsWithMember: " + e.getMessage(), e); + } + Collection groups = site.getGroups(); + for (Iterator> iter = profilesMap.entrySet().iterator(); iter.hasNext();) { Entry entry = iter.next(); String userId = entry.getKey(); @@ -317,11 +328,36 @@ log.warn("A profile exists for non-existent user " + userId); continue; } + + String groupsString = ""; + for (Group group : groups) + { + Member member = group.getMember(userId); + if (member !=null) + { + groupsString = groupsString + group.getTitle() + ", "; + } + } + if (groupsString != "") + { + int endIndex = groupsString.lastIndexOf(", "); + groupsString = groupsString.substring(0, endIndex); + } - participants.add(new ParticipantImpl(userRole.user, profile, userRole.role)); + participants.add(new ParticipantImpl(userRole.user, profile, userRole.role, groupsString)); } return participants; } + + private Comparator sortGroups() { + Comparator groupComparator = new Comparator() { + public int compare(Group one, Group another) + { + return Collator.getInstance().compare(one.getTitle(),another.getTitle()); + } + }; + return groupComparator; + } /** * Builds a map of user IDs to a list of Sections for that user within the current site context. @@ -534,5 +570,17 @@ RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO); } - + public boolean isGroupMembershipViewable() { + Site site; + try { + site = siteService().getSite(getSiteId()); + } catch (IdUnusedException ide) { + log.warn("isGroupMembershipViewable: " + ide); + return false; + } + Collection groups = site.getGroups(); + if (groups.isEmpty()) + return false; + return true; + } }