<commit_before>/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* The Apereo Foundation licenses this file to you 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://opensource.org/licenses/ecl2.txt
*
* 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.roster.impl;

import java.util.*;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sakaiproject.api.privacy.PrivacyManager;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.FunctionManager;
import org.sakaiproject.authz.api.GroupProvider;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.authz.api.Role;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.coursemanagement.api.CourseManagementService;
import org.sakaiproject.coursemanagement.api.Enrollment;
import org.sakaiproject.coursemanagement.api.EnrollmentSet;
import org.sakaiproject.coursemanagement.api.Section;
import org.sakaiproject.coursemanagement.api.exception.IdNotFoundException;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.MemoryService;
import org.sakaiproject.memory.api.SimpleConfiguration;
import org.sakaiproject.profile2.logic.ProfileConnectionsLogic;
import org.sakaiproject.profile2.util.ProfileConstants;
import org.sakaiproject.roster.api.RosterEnrollment;
import org.sakaiproject.roster.api.RosterFunctions;
import org.sakaiproject.roster.api.RosterGroup;
import org.sakaiproject.roster.api.RosterMember;
import org.sakaiproject.roster.api.RosterMemberComparator;
import org.sakaiproject.roster.api.RosterSite;
import org.sakaiproject.roster.api.SakaiProxy;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.sitestats.api.SitePresenceTotal;
import org.sakaiproject.sitestats.api.StatsManager;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.util.ResourceLoader;

import lombok.Setter;

/**
 * <code>SakaiProxy</code> acts as a proxy between Roster and Sakai components.
 * 
 * @author <NAME> (<EMAIL>)
 * @author <NAME> (<EMAIL>)
 */
@Setter
public class SakaiProxyImpl implements SakaiProxy, Observer {

	private static final Logger log = LoggerFactory.getLogger(SakaiProxyImpl.class);
		
	private CourseManagementService courseManagementService;
	private EventTrackingService eventTrackingService;
	private FunctionManager functionManager;
	private GroupProvider groupProvider;
	private PrivacyManager privacyManager;
	private MemoryService memoryService;
	private ProfileConnectionsLogic connectionsLogic;
	private SecurityService securityService;
	private ServerConfigurationService serverConfigurationService;
	private SessionManager sessionManager;
	private SiteService siteService;
	private StatsManager statsManager;
	private ToolManager toolManager;
	private UserDirectoryService userDirectoryService;
    private RosterMemberComparator memberComparator;
	
	public void init() {
		
		List<String> registered = functionManager.getRegisteredFunctions();
		
        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_EXPORT)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_EXPORT, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWALL)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWALL, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE, true);
        }
        
        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWSITEVISITS)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWSITEVISITS, true);
        }

        eventTrackingService.addObserver(this);

        memberComparator = new RosterMemberComparator(getFirstNameLastName());
	}
	
	/**
	 * {@inheritDoc}
	 */
	public boolean isSuperUser() {
		return securityService.isSuperUser();
	}
	
	public Site getSite(String siteId) {

		try {
			return siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.warn("site not found: " + e.getId());
			return null;
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getCurrentUserId() {
		return sessionManager.getCurrentSessionUserId();
	}

	/**
	 * {@inheritDoc}
	 */
	public String getCurrentSiteId() {
		return toolManager.getCurrentPlacement().getContext();
	}

	/**
	 * {@inheritDoc}
	 */
	public String getCurrentSiteLocale() {

		String siteId = toolManager.getCurrentPlacement().getContext();
        Site currentSite = getSite(siteId);

        if (currentSite != null) {
            String locale = currentSite.getProperties().getProperty("locale_string");
            if (locale != null) {
                return locale;
            }
        }

        return null;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Integer getDefaultRosterState() {

		return serverConfigurationService.getInt("roster.defaultState",
				DEFAULT_ROSTER_STATE);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getDefaultRosterStateString() {
		
		Integer defaultRosterState = getDefaultRosterState();
		
		if (defaultRosterState > -1 && defaultRosterState < ROSTER_STATES.length - 1) {
			return ROSTER_STATES[defaultRosterState];
		} else {
			return ROSTER_STATES[DEFAULT_ROSTER_STATE];
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getDefaultSortColumn() {
		
		return serverConfigurationService
				.getString("roster.defaultSortColumn", DEFAULT_SORT_COLUMN);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getFirstNameLastName() {

		return serverConfigurationService.getBoolean(
				"roster.display.firstNameLastName", DEFAULT_FIRST_NAME_LAST_NAME);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getHideSingleGroupFilter() {

		return serverConfigurationService.getBoolean(
				"roster.display.hideSingleGroupFilter",
				DEFAULT_HIDE_SINGLE_GROUP_FILTER);
	}
		
	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewEmail() {
		return getViewEmail(getCurrentSiteId());
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewEmail(String siteId) {

		//To view emails it first needs to be enabled in sakai.properties and the user must have the permission.
		if(serverConfigurationService.getBoolean("roster_view_email",DEFAULT_VIEW_EMAIL)) {
			return hasUserSitePermission(getCurrentUserId(), RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, siteId);
		}
		return false;		
	}

	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewConnections() {

		Boolean view_connections = serverConfigurationService.getBoolean("roster_view_connections",
				DEFAULT_VIEW_CONNECTIONS);

		Boolean profile2_connections_enabled = serverConfigurationService.getBoolean("profile2.connections.enabled",
				ProfileConstants.SAKAI_PROP_PROFILE2_CONNECTIONS_ENABLED);
		Boolean profile2_menu_enabled = serverConfigurationService.getBoolean("profile2.menu.enabled",
				ProfileConstants.SAKAI_PROP_PROFILE2_MENU_ENABLED);

		if(!profile2_menu_enabled || !profile2_connections_enabled) {
			view_connections = false;
		}

		return view_connections;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewUserDisplayId() {
		return serverConfigurationService.getBoolean(
				"roster.display.userDisplayId", DEFAULT_VIEW_USER_DISPLAY_ID);
	}

	/**
	 * {@inheritDoc}
	 */
    public Boolean getOfficialPicturesByDefault() {
		return serverConfigurationService.getBoolean(
				"roster.display.officialPicturesByDefault", true);
    }
	
	public RosterMember getMember(String siteId, String userId, String enrollmentSetId) {

        User user = null;
        try {
            user = userDirectoryService.getUser(userId);
        } catch (UserNotDefinedException e) {
			log.error("User '" + userId + "' not found. Returning null ...");
            return null;
        }

		Site site = null;
		try {
			site = siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.error("Site '" + siteId + "' not found. Returning null ...");
            return null;
		}

        if (enrollmentSetId != null) {
            // Get the cache
            Map<String, List<RosterMember>> membersMap = getAndCacheSortedEnrollmentSet(site, enrollmentSetId);

            if (membersMap != null) {
                List<RosterMember> members = membersMap.get(enrollmentSetId + "#all");

                members = filterMembers(site, getCurrentUserId(), members, null);

                if (members != null) {
                    for (RosterMember member : members) {
                        if (member.getUserId().equals(userId)) {
                            return member;
                        }
                    }
                }
            } else {
                log.error("Caching of enrollment set for site '" + siteId + "' and enrollmentset '" + enrollmentSetId + "' failed. Returning null ...");
            }

            return null;
        } else {
            // Get the unfiltered memberships
            List<RosterMember> members = getAndCacheSortedMembership(site, null, null);
            members = filterMembers(site, getCurrentUserId(), members, null);
            for (RosterMember member : members) {
                if (member.getUserId().equals(userId)) {
                    return member;
                }
            }

            return null;
        }
    }

	public List<User> getSiteUsers(String siteId) {

		Site site = null;
		try {
			site = siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.error("Site '" + siteId + "' not found. Returning null ...");
            return null;
		}

        return userDirectoryService.getUsers(site.getUsers());
    }
	
	public List<RosterMember> getMembership(String currentUserId, String siteId, String groupId, String roleId, String enrollmentSetId, String enrollmentStatus) {

        if (currentUserId == null) {
            return null;
        }

		Site site = null;
		try {
			site = siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.error("Site '" + siteId + "' not found. Returning null ...");
            return null;
		}

        if (site.isType("course") && enrollmentSetId != null) {
            return getEnrollmentMembership(site, enrollmentSetId, enrollmentStatus, currentUserId);
        } else {
            List<RosterMember> rosterMembers = getAndCacheSortedMembership(site, groupId, roleId);
            rosterMembers = filterMembers(site, currentUserId, rosterMembers, groupId);
            return rosterMembers;
        }
	}
		
    private Map<String, User> getUserMap(Set<Member> members) {

        Map<String, User> userMap = new HashMap<String, User>();

        Set<String> userIds = new HashSet<String>();

        // Build a map of userId to role
        for (Member member : members) {
            if (member.isActive()) {
				userIds.add(member.getUserId());
	        }
        }

        // Get the user objects
        for (User user : userDirectoryService.getUsers(userIds)) {
            userMap.put(user.getId(), user);
        }

        return userMap;
    }
		
    /**
     * @return A mapping of RosterMember onto eid
     */
	private Map<String, RosterMember> getMembershipMapped(Site site, String groupId) {

		Map<String, RosterMember> rosterMembers = new HashMap<String, RosterMember>();

        String userId = getCurrentUserId();

		Set<Member> membership = getUnfilteredMembers(groupId, site);
		
		if (membership == null) {
			return null;
		}

		Map<String, User> userMap = getUserMap(membership);
		Collection<Group> groups = site.getGroups();
		for (Member member : membership) {
			try {
				RosterMember rosterMember = getRosterMember(userMap, groups, member, site);
				rosterMembers.put(rosterMember.getEid(), rosterMember);
			} catch (UserNotDefinedException e) {
				log.warn("user not found: " + e.getId());
			}
		}

		return rosterMembers;
	}

    private List<RosterMember> filterMembers(Site site, String currentUserId, List<RosterMember> unfiltered, String groupId) {

        List<RosterMember> filtered = new ArrayList<RosterMember>();

		if (isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWALL, site.getReference())) {
			if (groupId == null) {
                filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), site));
            } else {
                Group group = site.getGroup(groupId);
                if (group != null) {
                    // get all members of requested groupId
                    filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), group));
                } else {
                    // assume invalid groupId specified
                    return null;
                }
            }
        } else {
			if (groupId == null) {
				// get all members of groups current user is allowed to view
				for (Group group : site.getGroups()) {
					if (isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, group.getReference())) {
						filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), group));
					}
				}
			} else if (null != site.getGroup(groupId)) {
				// get all members of requested groupId if current user is
				// member
                Group group = site.getGroup(groupId);
				if (isAllowed(currentUserId,
						RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, group.getReference())) {
					filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), group));
				}
			} else {
				// assume invalid groupId specified or user not member
				return null;
			}
		}
		
		if (log.isDebugEnabled()) log.debug("membership.size(): " + filtered.size());
		
		//remove duplicates. Yes, its a Set but there can be dupes because its storing objects and from multiple groups.
		Set<String> check = new HashSet<String>();
		List<RosterMember> cleanedMembers = new ArrayList<RosterMember>();
		for (RosterMember m : filtered) {
			if (check.add(m.getUserId())) {
				cleanedMembers.add(m);
                // Now strip out any unauthorised info
                if (!isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, site.getReference())) {
                    m.setEmail(null);
                }
			}
		}
		
		if (log.isDebugEnabled()) log.debug("cleanedMembers.size(): " + cleanedMembers.size());

		return cleanedMembers;
    }

    @SuppressWarnings("unchecked")
	private List<RosterMember> filterHiddenMembers(List<RosterMember> members,
			String currentUserId, String siteId, AuthzGroup authzGroup) {

		log.debug("filterHiddenMembers");
		
		if (isAllowed(currentUserId,
				RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, authzGroup.getReference())) {
			log.debug("permission to view all, including hidden");
			return members;
		}

		List<RosterMember> filtered = new ArrayList<RosterMember>();
		
		Set<String> userIds = new HashSet<String>();

		for (Iterator<RosterMember> i = members.iterator(); i.hasNext(); ) {
            RosterMember member = i.next();
			String userId = member.getUserId();

			userIds.add(userId);

            // If this member is not in the authzGroup, remove them.
            if (authzGroup.getMember(userId) == null) {
                i.remove();
            }
		}

		Set<String> hiddenUserIds
            = privacyManager.findHidden("/site/" + siteId, userIds);

		//get the list of visible roles, optional config.
		//if set, the only users visible in the tool will be those with their role defined in this list
		String[] visibleRoles = serverConfigurationService.getStrings("roster2.visibleroles");
		
		boolean filterRoles = ArrayUtils.isNotEmpty(visibleRoles);

		if (log.isDebugEnabled()) log.debug("visibleRoles: " + ArrayUtils.toString(visibleRoles));
		if (log.isDebugEnabled()) log.debug("filterRoles: " + filterRoles);
		
		// determine filtered membership
		for (RosterMember member : members) {
			String userId = member.getUserId();
			
			// skip if privacy restricted and not the current user
			if (!userId.equals(currentUserId) && hiddenUserIds.contains(userId)) {
				continue;
			}
			
			// now filter out users based on their role
			if (filterRoles) {
				String memberRoleId = member.getRole();
				if (ArrayUtils.contains(visibleRoles, memberRoleId)){
					filtered.add(member);
				}
			} else {<commit_msg>SAK-31234 Regression roster2<commit_after>/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* The Apereo Foundation licenses this file to you 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://opensource.org/licenses/ecl2.txt
*
* 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.roster.impl;

import java.util.*;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sakaiproject.api.privacy.PrivacyManager;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.FunctionManager;
import org.sakaiproject.authz.api.GroupProvider;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.authz.api.Role;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.coursemanagement.api.CourseManagementService;
import org.sakaiproject.coursemanagement.api.Enrollment;
import org.sakaiproject.coursemanagement.api.EnrollmentSet;
import org.sakaiproject.coursemanagement.api.Section;
import org.sakaiproject.coursemanagement.api.exception.IdNotFoundException;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.MemoryService;
import org.sakaiproject.memory.api.SimpleConfiguration;
import org.sakaiproject.profile2.logic.ProfileConnectionsLogic;
import org.sakaiproject.profile2.util.ProfileConstants;
import org.sakaiproject.roster.api.RosterEnrollment;
import org.sakaiproject.roster.api.RosterFunctions;
import org.sakaiproject.roster.api.RosterGroup;
import org.sakaiproject.roster.api.RosterMember;
import org.sakaiproject.roster.api.RosterMemberComparator;
import org.sakaiproject.roster.api.RosterSite;
import org.sakaiproject.roster.api.SakaiProxy;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.sitestats.api.SitePresenceTotal;
import org.sakaiproject.sitestats.api.StatsManager;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.util.ResourceLoader;

import lombok.Setter;

/**
 * <code>SakaiProxy</code> acts as a proxy between Roster and Sakai components.
 * 
 * @author <NAME> (<EMAIL>)
 * @author <NAME> (<EMAIL>)
 */
@Setter
public class SakaiProxyImpl implements SakaiProxy, Observer {

	private static final Logger log = LoggerFactory.getLogger(SakaiProxyImpl.class);
		
	private CourseManagementService courseManagementService;
	private EventTrackingService eventTrackingService;
	private FunctionManager functionManager;
	private GroupProvider groupProvider;
	private PrivacyManager privacyManager;
	private MemoryService memoryService;
	private ProfileConnectionsLogic connectionsLogic;
	private SecurityService securityService;
	private ServerConfigurationService serverConfigurationService;
	private SessionManager sessionManager;
	private SiteService siteService;
	private StatsManager statsManager;
	private ToolManager toolManager;
	private UserDirectoryService userDirectoryService;
    private RosterMemberComparator memberComparator;
	
	public void init() {
		
		List<String> registered = functionManager.getRegisteredFunctions();
		
        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_EXPORT)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_EXPORT, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWALL)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWALL, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE, true);
        }
        
        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO, true);
        }

        if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWSITEVISITS)) {
            functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWSITEVISITS, true);
        }

        eventTrackingService.addObserver(this);

        memberComparator = new RosterMemberComparator(getFirstNameLastName());
	}
	
	/**
	 * {@inheritDoc}
	 */
	public boolean isSuperUser() {
		return securityService.isSuperUser();
	}
	
	public Site getSite(String siteId) {

		try {
			return siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.warn("site not found: " + e.getId());
			return null;
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getCurrentUserId() {
		return sessionManager.getCurrentSessionUserId();
	}

	/**
	 * {@inheritDoc}
	 */
	public String getCurrentSiteId() {
		return toolManager.getCurrentPlacement().getContext();
	}

	/**
	 * {@inheritDoc}
	 */
	public String getCurrentSiteLocale() {

		String siteId = toolManager.getCurrentPlacement().getContext();
        Site currentSite = getSite(siteId);

        if (currentSite != null) {
            String locale = currentSite.getProperties().getProperty("locale_string");
            if (locale != null) {
                return locale;
            }
        }

        return null;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Integer getDefaultRosterState() {

		return serverConfigurationService.getInt("roster.defaultState",
				DEFAULT_ROSTER_STATE);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getDefaultRosterStateString() {
		
		Integer defaultRosterState = getDefaultRosterState();
		
		if (defaultRosterState > -1 && defaultRosterState < ROSTER_STATES.length - 1) {
			return ROSTER_STATES[defaultRosterState];
		} else {
			return ROSTER_STATES[DEFAULT_ROSTER_STATE];
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getDefaultSortColumn() {
		
		return serverConfigurationService
				.getString("roster.defaultSortColumn", DEFAULT_SORT_COLUMN);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getFirstNameLastName() {

		return serverConfigurationService.getBoolean(
				"roster.display.firstNameLastName", DEFAULT_FIRST_NAME_LAST_NAME);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getHideSingleGroupFilter() {

		return serverConfigurationService.getBoolean(
				"roster.display.hideSingleGroupFilter",
				DEFAULT_HIDE_SINGLE_GROUP_FILTER);
	}
		
	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewEmail() {
		return getViewEmail(getCurrentSiteId());
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewEmail(String siteId) {

		//To view emails it first needs to be enabled in sakai.properties and the user must have the permission.
		if(serverConfigurationService.getBoolean("roster_view_email",DEFAULT_VIEW_EMAIL)) {
			return hasUserSitePermission(getCurrentUserId(), RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, siteId);
		}
		return false;		
	}

	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewConnections() {

		Boolean view_connections = serverConfigurationService.getBoolean("roster_view_connections",
				DEFAULT_VIEW_CONNECTIONS);

		Boolean profile2_connections_enabled = serverConfigurationService.getBoolean("profile2.connections.enabled",
				ProfileConstants.SAKAI_PROP_PROFILE2_CONNECTIONS_ENABLED);
		Boolean profile2_menu_enabled = serverConfigurationService.getBoolean("profile2.menu.enabled",
				ProfileConstants.SAKAI_PROP_PROFILE2_MENU_ENABLED);

		if(!profile2_menu_enabled || !profile2_connections_enabled) {
			view_connections = false;
		}

		return view_connections;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Boolean getViewUserDisplayId() {
		return serverConfigurationService.getBoolean(
				"roster.display.userDisplayId", DEFAULT_VIEW_USER_DISPLAY_ID);
	}

	/**
	 * {@inheritDoc}
	 */
    public Boolean getOfficialPicturesByDefault() {
		return serverConfigurationService.getBoolean(
				"roster.display.officialPicturesByDefault", true);
    }
	
	public RosterMember getMember(String siteId, String userId, String enrollmentSetId) {

        User user = null;
        try {
            user = userDirectoryService.getUser(userId);
        } catch (UserNotDefinedException e) {
			log.error("User '" + userId + "' not found. Returning null ...");
            return null;
        }

		Site site = null;
		try {
			site = siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.error("Site '" + siteId + "' not found. Returning null ...");
            return null;
		}

        if (enrollmentSetId != null) {
            // Get the cache
            Map<String, List<RosterMember>> membersMap = getAndCacheSortedEnrollmentSet(site, enrollmentSetId);

            if (membersMap != null) {
                List<RosterMember> members = membersMap.get(enrollmentSetId + "#all");

                members = filterMembers(site, getCurrentUserId(), members, null);

                if (members != null) {
                    for (RosterMember member : members) {
                        if (member.getUserId().equals(userId)) {
                            return member;
                        }
                    }
                }
            } else {
                log.error("Caching of enrollment set for site '" + siteId + "' and enrollmentset '" + enrollmentSetId + "' failed. Returning null ...");
            }

            return null;
        } else {
            // Get the unfiltered memberships
            List<RosterMember> members = getAndCacheSortedMembership(site, null, null);
            members = filterMembers(site, getCurrentUserId(), members, null);
            for (RosterMember member : members) {
                if (member.getUserId().equals(userId)) {
                    return member;
                }
            }

            return null;
        }
    }

	public List<User> getSiteUsers(String siteId) {

		Site site = null;
		try {
			site = siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.error("Site '" + siteId + "' not found. Returning null ...");
            return null;
		}

        return userDirectoryService.getUsers(site.getUsers());
    }
	
	public List<RosterMember> getMembership(String currentUserId, String siteId, String groupId, String roleId, String enrollmentSetId, String enrollmentStatus) {

        if (currentUserId == null) {
            return null;
        }

		Site site = null;
		try {
			site = siteService.getSite(siteId);
		} catch (IdUnusedException e) {
			log.error("Site '" + siteId + "' not found. Returning null ...");
            return null;
		}

        if (site.isType("course") && enrollmentSetId != null) {
            return getEnrollmentMembership(site, enrollmentSetId, enrollmentStatus, currentUserId);
        } else {
            List<RosterMember> rosterMembers = getAndCacheSortedMembership(site, groupId, roleId);
            rosterMembers = filterMembers(site, currentUserId, rosterMembers, groupId);
            return rosterMembers;
        }
	}
		
    private Map<String, User> getUserMap(Set<Member> members) {

        Map<String, User> userMap = new HashMap<String, User>();

        Set<String> userIds = new HashSet<String>();

        // Build a map of userId to role
        for (Member member : members) {
            if (member.isActive()) {
				userIds.add(member.getUserId());
	        }
        }

        // Get the user objects
        for (User user : userDirectoryService.getUsers(userIds)) {
            userMap.put(user.getId(), user);
        }

        return userMap;
    }
		
    /**
     * @return A mapping of RosterMember onto eid
     */
	private Map<String, RosterMember> getMembershipMapped(Site site, String groupId) {

		Map<String, RosterMember> rosterMembers = new HashMap<String, RosterMember>();

        String userId = getCurrentUserId();

		Set<Member> membership = getUnfilteredMembers(groupId, site);
		
		if (membership == null) {
			return null;
		}

		Map<String, User> userMap = getUserMap(membership);
		Collection<Group> groups = site.getGroups();
		for (Member member : membership) {
			try {
				RosterMember rosterMember = getRosterMember(userMap, groups, member, site);
				rosterMembers.put(rosterMember.getEid(), rosterMember);
			} catch (UserNotDefinedException e) {
				log.warn("user not found: " + e.getId());
			}
		}

		return rosterMembers;
	}

    private List<RosterMember> filterMembers(Site site, String currentUserId, List<RosterMember> unfiltered, String groupId) {

        List<RosterMember> filtered = new ArrayList<RosterMember>();

		if (isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWALL, site.getReference())) {
			if (groupId == null) {
                filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), site));
            } else {
                Group group = site.getGroup(groupId);
                if (group != null) {
                    // get all members of requested groupId
                    filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), group));
                } else {
                    // assume invalid groupId specified
                    return null;
                }
            }
        } else {
			if (groupId == null) {
				// get all members of groups current user is allowed to view
				for (Group group : site.getGroups()) {
					if (isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, group.getReference())) {
						filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), group));
					}
				}
			} else if (null != site.getGroup(groupId)) {
				// get all members of requested groupId if current user is
				// member
                Group group = site.getGroup(groupId);
				if (isAllowed(currentUserId,
						RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, group.getReference())) {
					filtered.addAll(filterHiddenMembers(unfiltered, currentUserId, site.getId(), group));
				}
			} else {
				// assume invalid groupId specified or user not member
				return null;
			}
		}
		
		if (log.isDebugEnabled()) log.debug("membership.size(): " + filtered.size());
		
		//remove duplicates. Yes, its a Set but there can be dupes because its storing objects and from multiple groups.
		Set<String> check = new HashSet<String>();
		List<RosterMember> cleanedMembers = new ArrayList<RosterMember>();
		for (RosterMember m : filtered) {
			if (check.add(m.getUserId())) {
				cleanedMembers.add(m);
                // Now strip out any unauthorised info
                if (!isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, site.getReference())) {
                    m.setEmail(null);
                }
			}
		}
		
		if (log.isDebugEnabled()) log.debug("cleanedMembers.size(): " + cleanedMembers.size());

		return cleanedMembers;
    }

    @SuppressWarnings("unchecked")
	private List<RosterMember> filterHiddenMembers(List<RosterMember> members,
			String currentUserId, String siteId, AuthzGroup authzGroup) {

		log.debug("filterHiddenMembers");
		
		boolean viewHidden = false;
		if (isAllowed(currentUserId,
				RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, authzGroup.getReference())) {
			log.debug("permission to view all, including hidden");
			viewHidden = true;
		}

		List<RosterMember> filtered = new ArrayList<RosterMember>();
		
		Set<String> userIds = new HashSet<String>();

		for (Iterator<RosterMember> i = members.iterator(); i.hasNext(); ) {
            RosterMember member = i.next();
			String userId = member.getUserId();

			userIds.add(userId);

		}

		Set<String> hiddenUserIds
            = privacyManager.findHidden("/site/" + siteId, userIds);

		//get the list of visible roles, optional config.
		//if set, the only users visible in the tool will be those with their role defined in this list
		String[] visibleRoles = serverConfigurationService.getStrings("roster2.visibleroles");
		
		boolean filterRoles = ArrayUtils.isNotEmpty(visibleRoles);

		if (log.isDebugEnabled()) log.debug("visibleRoles: " + ArrayUtils.toString(visibleRoles));
		if (log.isDebugEnabled()) log.debug("filterRoles: " + filterRoles);
		
		// determine filtered membership
		for (RosterMember member : members) {
			String userId = member.getUserId();
			
			// skip if privacy restricted and not the current user
			if (!userId.equals(currentUserId) && (viewHidden || hiddenUserIds.contains(userId)) && authzGroup.getMember(userId) == null) {
				continue;
			}
			
			// now filter out users based on their role
			if (filterRoles && !viewHidden) {
				String memberRoleId = member.getRole();
				if (ArrayUtils.contains(visibleRoles, memberRoleId)){
					filtered.add(member);
				}
			} else {
{'fraction_non_alphanumeric': 0.10105626094570928, 'fraction_numerical': 0.0011492994746059545, 'mean_word_length': 3.4388436778816955, 'pattern_counts': {'":': 0, '<': 89, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4939865', 'n_tokens_mistral': 11255, 'n_tokens_neox': 10298, 'n_words': 2227}