Index: lams_build/lib/lams/lams-central.jar =================================================================== diff -u -rb78ac844426c4d69e6104349bdf7cae2ecc6644d -rf92865513dd2beca642af28ab42096ee4849ac32 Binary files differ Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -rb78ac844426c4d69e6104349bdf7cae2ecc6644d -rf92865513dd2beca642af28ab42096ee4849ac32 Binary files differ Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r606581007abbdaee5483777096f32ffac1c3e3eb -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 606581007abbdaee5483777096f32ffac1c3e3eb) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -87,6 +87,7 @@ index.courseman =Group Mgt index.author =Author index.classman =Manage Subgroups +index.orggroup =Course Groups index.addlesson =Add Lesson index.monitor =Monitor index.participate =Participate @@ -418,5 +419,15 @@ label.questions.choice.select.all =Select all label.questions.choice.missing =Please check at least one question. +index.course.groups.title =Course groups +label.course.groups.edit.title =Please use drag n' drop to move users between groups. +label.course.groups.viewonly.title =Course group view +label.course.groups.prefix =Group +label.course.groups.remove.confirm =Are you sure you want to delete this group? +label.course.groups.remove.empty.confirm =Some groups are new and empty. They will not be saved. Click OK to continue. +label.course.groups.add =Add a new group +label.course.groups.unassigned =Unassigned users +label.course.groups.sort.tooltip =Sort users +label.course.groups.remove.tooltip =Remove group #======= End labels: Exported 404 labels for en AU ===== Index: lams_central/src/java/org/lamsfoundation/lams/web/DisplayGroupAction.java =================================================================== diff -u -rb78ac844426c4d69e6104349bdf7cae2ecc6644d -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_central/src/java/org/lamsfoundation/lams/web/DisplayGroupAction.java (.../DisplayGroupAction.java) (revision b78ac844426c4d69e6104349bdf7cae2ecc6644d) +++ lams_central/src/java/org/lamsfoundation/lams/web/DisplayGroupAction.java (.../DisplayGroupAction.java) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -145,6 +145,12 @@ moreLinks.add(new IndexLinkBean("index.classman", "javascript:openOrgManagement(" + org.getOrganisationId() + ")", "manage-group-button", null)); } + if ((roles.contains(Role.ROLE_GROUP_ADMIN) || roles.contains(Role.ROLE_GROUP_MANAGER) + || roles.contains(Role.ROLE_AUTHOR) || roles.contains(Role.ROLE_MONITOR))) { + moreLinks.add(new IndexLinkBean("index.orggroup", "javascript:showOrgGroupDialog(" + + org.getOrganisationId() + ")", "manage-group-button", null)); + } + if (roles.contains(Role.ROLE_GROUP_MANAGER) || roles.contains(Role.ROLE_MONITOR)) { String name = org.getEnableSingleActivityLessons() ? "index.addlesson.single" : "index.addlesson"; links.add(new IndexLinkBean(name, Index: lams_central/src/java/org/lamsfoundation/lams/web/OrganisationGroupAction.java =================================================================== diff -u --- lams_central/src/java/org/lamsfoundation/lams/web/OrganisationGroupAction.java (revision 0) +++ lams_central/src/java/org/lamsfoundation/lams/web/OrganisationGroupAction.java (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -0,0 +1,179 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +/* $Id$ */ +package org.lamsfoundation.lams.web; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Vector; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.actions.DispatchAction; +import org.apache.tomcat.util.json.JSONArray; +import org.apache.tomcat.util.json.JSONException; +import org.apache.tomcat.util.json.JSONObject; +import org.lamsfoundation.lams.usermanagement.OrganisationGroup; +import org.lamsfoundation.lams.usermanagement.Role; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.exception.UserAccessDeniedException; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.util.WebUtils; + +/** + * @struts.action path = "/OrganisationGroup" parameter = "method" validate = "false" + * @struts.action-forward name = "view" path = "/orgGroup.jsp" + */ +public class OrganisationGroupAction extends DispatchAction { + private static Logger log = Logger.getLogger(OrganisationGroupAction.class); + + private static IUserManagementService userManagementService; + + private static final String MAPPING_VIEW = "view"; + + @SuppressWarnings("unchecked") + public ActionForward view(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws JSONException { + // check if user is allowed to view and edit groups + Integer userId = getUserDTO().getUserID(); + int organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + boolean isGroupSuperuser = getUserManagementService().isUserInRole(userId, organisationId, Role.GROUP_ADMIN) ||getUserManagementService().isUserInRole(userId, organisationId, Role.GROUP_MANAGER); + if (!isGroupSuperuser && !getUserManagementService().isUserInRole(userId, organisationId, Role.AUTHOR) && !getUserManagementService().isUserInRole(userId, organisationId, Role.MONITOR)){ + throw new UserAccessDeniedException("User " + userId + " may not view groups for course " + organisationId); + } + + if(log.isDebugEnabled()) { + log.debug("Displaying course groups for user " + userId + " and organisation " + organisationId); + } + request.setAttribute(AttributeNames.PARAM_ORGANISATION_ID, organisationId); + request.setAttribute("canEdit", isGroupSuperuser); + + // serialize database group objects into JSON + Vector learners = getUserManagementService().getUsersFromOrganisationByRole(organisationId, Role.LEARNER, + false, true); + List groups = getUserManagementService().findByProperty(OrganisationGroup.class, + "organisationId", organisationId); + JSONArray groupsJSON = new JSONArray(); + for (OrganisationGroup group : groups) { + JSONObject groupJSON = new JSONObject(); + groupJSON.put("name", group.getName()); + groupJSON.put("groupId", group.getGroupId()); + for (User groupUser : group.getUsers()) { + JSONObject groupUserJSON = WebUtil.userToJSON(groupUser); + groupJSON.append("users", groupUserJSON); + + // remove the user who is already assigned to a group + learners.remove(groupUser); + } + + groupsJSON.put(groupJSON); + } + request.setAttribute("groups", groupsJSON); + + // all the remaining users are unassigned to any group + JSONArray unassignedUsersJSON = new JSONArray(); + for (User unassignedUser : learners) { + JSONObject unassignedUserJSON = WebUtil.userToJSON(unassignedUser); + unassignedUsersJSON.put(unassignedUserJSON); + } + request.setAttribute("unassignedUsers", unassignedUsersJSON); + + response.setContentType("application/json;charset=utf-8"); + return mapping.findForward(MAPPING_VIEW); + } + + public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws JSONException { + // check if user is allowed to edit groups + Integer userId = getUserDTO().getUserID(); + int organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID); + boolean isGroupSuperuser = getUserManagementService().isUserInRole(userId, organisationId, Role.GROUP_ADMIN) ||getUserManagementService().isUserInRole(userId, organisationId, Role.GROUP_MANAGER); + if (!isGroupSuperuser){ + throw new UserAccessDeniedException("User " + userId + " may not edit groups for course " + organisationId); + } + + if(log.isDebugEnabled()) { + log.debug("Saving course groups for user " + userId + " and organisation " + organisationId); + } + + JSONArray newGroups = new JSONArray(request.getParameter("groups")); + List newGroupObjects = new LinkedList(); + + // iterate over groups + for (int i = 0; i < newGroups.length(); i++) { + JSONObject newGroup = newGroups.getJSONObject(i); + OrganisationGroup newGroupObject = new OrganisationGroup(); + + Long groupId = newGroup.optLong("groupId"); + if (groupId == 0L) { + groupId = null; + } + newGroupObject.setGroupId(groupId); + newGroupObject.setOrganisationId(organisationId); + newGroupObject.setName(newGroup.optString("name", null)); + newGroupObject.setUsers(new HashSet()); + JSONArray newGroupUsers = newGroup.optJSONArray("users"); + if (newGroupUsers != null) { + // find user objects based on delivered IDs + for (int j = 0; j< newGroupUsers.length(); j++) { + Integer learnerId = newGroupUsers.getInt(j); + User user = (User)getUserManagementService().findById(User.class, learnerId); + newGroupObject.getUsers().add(user); + } + } + newGroupObjects.add(newGroupObject); + } + + getUserManagementService().saveOrganisationGroups(organisationId, newGroupObjects); + + return null; + } + + private IUserManagementService getUserManagementService() { + if (userManagementService == null) { + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServlet() + .getServletContext()); + userManagementService = (IUserManagementService) ctx.getBean("userManagementService"); + } + return userManagementService; + } + + + private UserDTO getUserDTO() { + HttpSession ss = SessionManager.getSession(); + return (UserDTO) ss.getAttribute(AttributeNames.USER); + } +} \ No newline at end of file Index: lams_central/web/css/orgGroup.css =================================================================== diff -u --- lams_central/web/css/orgGroup.css (revision 0) +++ lams_central/web/css/orgGroup.css (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -0,0 +1,95 @@ +html, body { + height: 100%; +} + +div#titleDiv { + font-size: small; + font-weight: bold; + padding: 5px 0; + text-align: center; + border-bottom: thin dotted #2E6E9E; +} + +table td { + vertical-align: top; + height: 348px; +} + +td#unassignedUserCell { + width: 200px; + padding: 5px; + border-right: thin dotted #2E6E9E; +} + +#groupTemplate { + display: none; +} + +#newGroupPlaceholder { + border: thin dashed #2E6E9E; + cursor: pointer; +} + +#newGroupPlaceholder > div { + text-align: center; + margin: 60px 5px 0 5px; +} + +.groupContainer { + float: left; + width: 180px; + height: 150px; + margin: 0 10px 10px 0; + padding: 5px; + border: thin solid #2E6E9E; +} + +.userContainerTitle { + font-weight: bold; + text-align: center; +} + +.removeGroupButton, .sortUsersButton { + float: right; + cursor: pointer; +} + +.removeGroupButton { + padding-right: 3px; + width: 16px; + height: 16px; +} + +.userContainer { + overflow: auto; + height: 123px; + margin-top: 5px; +} + +td#unassignedUserCell .userContainer { + height: 100%; +} + +.groupContainer input { + width: 135px; + margin-right: 5px; +} + +.groupContainer .sortUsersButton, .groupContainer .removeGroupButton { + padding-top: 3px; +} + +.draggableUser { + padding: 3px 0px 3px 0px; + cursor: default; +} + +.droppableHighlight { + padding : 1px !important; + border: 5px solid #5c9ccc !important; +} + +.draggableUserSelected { + background-color: #5c9ccc !important; + color: white !important; +} Index: lams_central/web/includes/javascript/groupDisplay.js =================================================================== diff -u -r512eb365864faf5aad4aeadc0766a964b3f3e170 -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_central/web/includes/javascript/groupDisplay.js (.../groupDisplay.js) (revision 512eb365864faf5aad4aeadc0766a964b3f3e170) +++ lams_central/web/includes/javascript/groupDisplay.js (.../groupDisplay.js) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -213,6 +213,32 @@ dialog.dialog('open'); } +function showOrgGroupDialog(orgID) { + var dialog = $('#dialogContainer').dialog({ + 'orgID' : orgID, + 'autoOpen' : false, + 'height' : 460, + 'width' : 880, + 'modal' : true, + 'resizable' : false, + 'hide' : 'fold', + 'title' : LABELS.COURSE_GROUPS_TITLE, + 'open' : function() { + // load contents after opening the dialog + $('#dialogFrame') + .attr('src', LAMS_URL + + 'OrganisationGroup.do?method=view&organisationID=' + + $(this).dialog('option', 'orgID')); + }, + 'beforeClose' : function(){ + $('#dialogFrame').attr('src', null); + }, + 'close' : function() { + $(this).dialog('destroy'); + } + }).dialog('open'); +} + function showAddSingleActivityLessonDialog(orgID, toolID) { $('#dialogContainer').dialog({ 'orgID' : orgID, @@ -464,6 +490,13 @@ .dialog('close'); } +function saveOrgGroups() { + var groupsSaved = document.getElementById('dialogFrame').contentWindow.saveGroups(); + if (groupsSaved) { + $("#dialogContainer").dialog('close'); + } +} + function removeLesson(lessonID) { if (confirm(LABELS.REMOVE_LESSON_CONFIRM1)) { if (confirm(LABELS.REMOVE_LESSON_CONFIRM2)) { Index: lams_central/web/includes/javascript/orgGroup.js =================================================================== diff -u --- lams_central/web/includes/javascript/orgGroup.js (revision 0) +++ lams_central/web/includes/javascript/orgGroup.js (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -0,0 +1,286 @@ +// ********** MAIN FUNCTIONS ********** +var sortOrderAscending = {}; +var lastSelectedUsers = {}; + +$(document).ready(function(){ + // show unassigned users on the left + fillGroup(unassignedUsers, $('#unassignedUserCell')); + $.each(groups, function(){ + // add existing groups + addGroup(this.groupId, this.name, this.users); + }); + + if (canEdit) { + // allow adding new groups + $('#newGroupPlaceholder').click(function(){ + addGroup(null, LABELS.GROUP_PREFIX_LABEL + $('table .groupContainer').length, null); + }); + // move Save button to the titlebar, i.e. outside of this iframe to the enveloping dialog + $('div.ui-dialog-titlebar', window.parent.document).prepend($('#saveButton')); + } +}); + + +/** + * Add a group and fills it with given users. + */ +function addGroup(groupId, name, users) { + var groupCount = $('table .groupContainer').length; + var group = $('#groupTemplate').clone() + .attr({ + 'id' : 'group-' + groupCount, + 'groupId' : groupId + }) + .css('display', null); + group.find('input').val(name); + + // there is no placeholder in read-only mode + if (canEdit) { + group.insertBefore('#newGroupPlaceholder'); + } else { + $('#groupsCell').append(group); + } + + fillGroup(users, group); +} + + +/** + * Makes a list of users and adds drag&drop functionality to them. + */ +function fillGroup(users, container) { + if (users) { + // create user DIVs + $.each(users, function(index, userJSON) { + var userDiv = $('
').attr('userId', userJSON.id) + .addClass('draggableUser') + .text(userJSON.firstName + ' ' + userJSON.lastName + + ' (' + userJSON.login + ')' + ); + + // if teacher can not edit, then no drag&drop is available + if (canEdit) { + userDiv.draggable({ + 'appendTo' : 'body', + 'containment' : 'table', + 'revert' : 'invalid', + 'distance' : 20, + 'scroll' : false, + 'cursor' : 'move', + 'helper' : function(event){ + // include the user from which dragging started + $(this).addClass('draggableUserSelected'); + + // copy selected users + var helperContainer = $('
'); + $(this).siblings('.draggableUserSelected').andSelf().each(function(){ + $(this).clone().appendTo(helperContainer); + }); + return helperContainer; + } + }) + + .click(function(event){ + var wasSelected = $(this).hasClass('draggableUserSelected'); + var parentId = $(this).parent().parent().attr('id'); + // this is needed for shift+click + var lastSelectedUser = lastSelectedUsers[parentId]; + + if (event.shiftKey && lastSelectedUser && lastSelectedUser != this) { + // clear current selection + $(this).siblings().andSelf().removeClass('draggableUserSelected'); + + // find range of users to select + var lastSelectedIndex = $(lastSelectedUser).index(); + var index = $(this).index(); + + var startingElem = lastSelectedIndex > index ? this : lastSelectedUser; + var endingElem = lastSelectedIndex > index ? lastSelectedUser : this; + + $(startingElem).nextUntil(endingElem).andSelf().add(endingElem) + .addClass('draggableUserSelected'); + } else { + if (!event.ctrlKey) { + // clear current sleection + $(this).siblings().andSelf().removeClass('draggableUserSelected'); + } + + if (wasSelected && !event.shiftKey){ + $(this).removeClass('draggableUserSelected'); + lastSelectedUsers[parentId] = null; + } else { + $(this).addClass('draggableUserSelected'); + lastSelectedUsers[parentId] = this; + } + } + }); + } + + $('.userContainer', container).append(userDiv); + }); + + sortUsers(container); + } + + $('.sortUsersButton', container).click(function(){ + sortUsers(container); + }); + + if (canEdit) { + $(container).droppable({ + 'activeClass' : 'droppableHighlight', + 'tolerance' : 'touch', + 'drop' : function (event, draggable) { + var draggableUserContainer = $(draggable.draggable).parent(); + var thisUserContainer = $('.userContainer', this); + // do not do anything if it is the same container + // using "accept" feature breaks the layout + if (draggableUserContainer[0] != thisUserContainer[0]) { + transferUsers(draggableUserContainer, thisUserContainer); + } + } + }); + + + $('.removeGroupButton', container).click(function(){ + removeGroup(container); + }); + } +} + + +function transferUsers(fromContainer, toContainer) { + var selectedUsers = $('.draggableUserSelected', fromContainer); + if (selectedUsers.length > 0){ + // move the selected users + selectedUsers.each(function(){ + $(this).css({'top' : '0px', + 'left' : '0px', + }) + .appendTo(toContainer); + }); + + + // recolour both containers + toContainer.children().removeClass('draggableUserSelected'); + colorDraggableUsers(toContainer); + colorDraggableUsers(fromContainer); + } +} + + +function colorDraggableUsers(container) { + // every second line is different + $(container).find('div.draggableUser').each(function(index, userDiv){ + // exact colour should be defined in CSS, but it's easier this way... + $(this).css('background-color', index % 2 ? '#dfeffc' : 'inherit'); + }); +} + +/** + * Sort users according to their names. + */ +function sortUsers(container) { + var containerId = container.attr('id'); + var users = $('div.draggableUser', container); + if (users.length > 1) { + var sortOrderAsc = sortOrderAscending[containerId]; + + users.each(function(){ + $(this).detach(); + }).sort(function(a, b){ + var keyA = $(a).text().toLowerCase(); + var keyB = $(b).text().toLowerCase(); + var result = keyA > keyB ? 1 : keyA < keyB ? -1 : 0; + return sortOrderAsc ? -result : result; + }).each(function(){ + $(this).appendTo($('.userContainer', container)); + }); + + var button = container.find('.sortUsersButton'); + if (sortOrderAsc) { + button.html('▼'); + sortOrderAscending[containerId] = false; + } else { + button.html('▲'); + sortOrderAscending[containerId] = true; + } + + colorDraggableUsers(container); + } +} + + +function removeGroup(container) { + // no groupId means this group was just added and it was not persisted in DB yet + var executeDelete = !$(container).attr('groupId'); + if (!executeDelete) { + executeDelete = confirm(LABELS.GROUP_REMOVE_LABEL); + } + if (executeDelete) { + $('#unassignedUserCell .userContainer').append($('.userContainer div.draggableUser', container)); + container.remove(); + } +} + + +function saveGroups(){ + if (!canEdit) { + return false; + } + + // ask if removing new, empyt groups is OK + var acceptEmptyGroups = true; + var groupContainers = $('table .groupContainer').not('#newGroupPlaceholder'); + $.each(groupContainers.not('[groupId]'), function(){ + if ($('div.draggableUser', this).length == 0) { + acceptEmptyGroups = false; + return false; + } + }); + if (!acceptEmptyGroups) { + acceptEmptyGroups = confirm(LABELS.EMPTY_GROUP_SAVE_LABEL); + } + if (!acceptEmptyGroups) { + return false; + } + + var groupsSaved = false; + var newGroups = []; + groupContainers.each(function(){ + var groupId = $(this).attr('groupId'); + var users = $('div.draggableUser', this); + if (!groupId && users.length == 0) { + return true; + } + + var userIds = []; + $.each(users, function(){ + userIds.push($(this).attr('userId')); + }); + + newGroups.push({ + 'groupId' : groupId, + 'name' : $('input', this).val(), + 'users' : userIds + }); + }); + + $.ajax({ + async : false, + cache : false, + url : LAMS_URL + 'OrganisationGroup.do', + data : { + 'method' : 'save', + 'organisationID' : orgId, + 'groups' : JSON.stringify(newGroups) + + }, + type : 'POST', + success : function(json) { + groupsSaved = true; + } + }); + + return groupsSaved; +} \ No newline at end of file Index: lams_central/web/main.jsp =================================================================== diff -u -rb78ac844426c4d69e6104349bdf7cae2ecc6644d -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_central/web/main.jsp (.../main.jsp) (revision b78ac844426c4d69e6104349bdf7cae2ecc6644d) +++ lams_central/web/main.jsp (.../main.jsp) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -52,7 +52,8 @@ GRADEBOOK_LESSON_TITLE : '', GRADEBOOK_LEARNER_TITLE : '', CONDITIONS_TITLE : '', - SEARCH_LESSON_TITLE : '' + SEARCH_LESSON_TITLE : '', + COURSE_GROUPS_TITLE : '' } var tabName = '${tab}'; Index: lams_central/web/orgGroup.jsp =================================================================== diff -u --- lams_central/web/orgGroup.jsp (revision 0) +++ lams_central/web/orgGroup.jsp (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -0,0 +1,98 @@ +<%@ page contentType="text/html; charset=utf-8" language="java"%> +<%@ taglib uri="tags-lams" prefix="lams"%> +<%@ taglib uri="tags-fmt" prefix="fmt"%> +<%@ taglib uri="tags-core" prefix="c"%> + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + +
+
+ + +
+
+
+ +
+
+
+
+
+ +
+
+ + readonly="readonly" + + /> + + + + +
+
+
+ +
\ No newline at end of file Index: lams_common/build.xml =================================================================== diff -u -r089204b32b1d164f5cdb701b8cd4d9dc04cc61c5 -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_common/build.xml (.../build.xml) (revision 089204b32b1d164f5cdb701b8cd4d9dc04cc61c5) +++ lams_common/build.xml (.../build.xml) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -35,6 +35,8 @@ + + Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch02040021.sql =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch02040021.sql (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch02040021.sql (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -0,0 +1,29 @@ +-- Turn off autocommit, so nothing is committed if there is an error + +SET AUTOCOMMIT = 0; +SET FOREIGN_KEY_CHECKS=0; + +-- LDEV-3119 Course level groups +CREATE TABLE lams_organisation_group ( + group_id BIGINT(20) NOT NULL AUTO_INCREMENT, + organisation_id BIGINT(20) NOT NULL, + name VARCHAR(255), + PRIMARY KEY (group_id), + CONSTRAINT FK_lams_organisation_group_1 FOREIGN KEY (organisation_id) + REFERENCES lams_organisation (organisation_id) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE lams_user_organisation_group ( + group_id BIGINT(20) NOT NULL, + user_id BIGINT(20) NOT NULL, + PRIMARY KEY (group_id, user_id), + CONSTRAINT FK_lams_user_organisation_group_1 FOREIGN KEY (group_id) + REFERENCES lams_organisation_group (group_id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT FK_lams_user_organisation_group_2 FOREIGN KEY (user_id) + REFERENCES lams_user (user_id) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +-- If there were no errors, commit and restore autocommit to on +SET FOREIGN_KEY_CHECKS=0; +COMMIT; +SET AUTOCOMMIT = 1; Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/OrganisationGroup.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/OrganisationGroup.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/OrganisationGroup.java (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -0,0 +1,130 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ +/* $$Id$$ */ +package org.lamsfoundation.lams.usermanagement; + +import java.io.Serializable; +import java.util.Set; + +import org.apache.commons.lang.builder.HashCodeBuilder; + +/** + * This is a course-level group of learners. + * + * @hibernate.class table="lams_organisation_group" + */ +public class OrganisationGroup implements Serializable { + + /** identifier field */ + private Long groupId; + + /** persistent field */ + private Integer organisationId; + + /** nullable persistent field */ + private String name; + + public OrganisationGroup() { + } + + public OrganisationGroup(Integer organisationId, String name) { + this.organisationId = organisationId; + this.name = name; + } + + /** persistent field */ + private Set users; + + /** + * @hibernate.id generator-class="native" type="java.lang.Long" column="group_id" + */ + public Long getGroupId() { + return groupId; + } + + public void setGroupId(Long groupId) { + this.groupId = groupId; + } + + /** + * @hibernate.property column="organisation_id" + */ + public Integer getOrganisationId() { + return this.organisationId; + } + + public void setOrganisationId(Integer organisationId) { + this.organisationId = organisationId; + } + + /** + * @hibernate.property column="name" + */ + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * @hibernate.set cascade="none" table="lams_user_organisation_group" + * @hibernate.collection-key column="group_id" + * @hibernate.collection-many-to-many column="user_id" class="org.lamsfoundation.lams.usermanagement.User" + */ + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users = users; + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(groupId).toHashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof OrganisationGroup)) { + return false; + } + OrganisationGroup other = (OrganisationGroup) obj; + if (groupId == null) { + if (other.groupId != null) { + return false; + } + } else if (!groupId.equals(other.groupId)) { + return false; + } + return true; + } +} \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java =================================================================== diff -u -rab19db088359a46353cc92e806c40ff5cff818b9 -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision ab19db088359a46353cc92e806c40ff5cff818b9) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -32,6 +32,7 @@ import org.lamsfoundation.lams.themes.Theme; import org.lamsfoundation.lams.usermanagement.ForgotPasswordRequest; import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.OrganisationGroup; import org.lamsfoundation.lams.usermanagement.OrganisationType; import org.lamsfoundation.lams.usermanagement.Role; import org.lamsfoundation.lams.usermanagement.User; @@ -527,4 +528,9 @@ * @return */ public User getUserDTOByOpenidURL(String openidURL); + + /** + * Stores organisation (course) groups and removes the unnecessary ones. + */ + public void saveOrganisationGroups(Integer organisationId, List newGroups); } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java =================================================================== diff -u -r29fac4bd0bdf28bcaaeee7aae4020ccb15b6e520 -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision 29fac4bd0bdf28bcaaeee7aae4020ccb15b6e520) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -45,6 +45,7 @@ import org.lamsfoundation.lams.themes.Theme; import org.lamsfoundation.lams.usermanagement.ForgotPasswordRequest; import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.OrganisationGroup; import org.lamsfoundation.lams.usermanagement.OrganisationType; import org.lamsfoundation.lams.usermanagement.Role; import org.lamsfoundation.lams.usermanagement.User; @@ -184,6 +185,40 @@ } return user; } + + @SuppressWarnings("unchecked") + public void saveOrganisationGroups(Integer organisationId, List newGroups) { + List existingGroups = findByProperty(OrganisationGroup.class, + "organisationId", organisationId); + + for (OrganisationGroup newGroup : newGroups) { + OrganisationGroup existingGroup = null; + // check if group already exists + for (OrganisationGroup existingGroupCandidate : existingGroups) { + if (existingGroupCandidate.equals(newGroup)) { + existingGroup = existingGroupCandidate; + break; + } + } + + if (existingGroup == null) { + // it is a new group, so add it + baseDAO.insert(newGroup); + } else { + // it is an existing group, update it + existingGroups.remove(existingGroup); + + existingGroup.setName(newGroup.getName()); + existingGroup.setUsers(newGroup.getUsers()); + baseDAO.update(existingGroup); + } + } + + // groups which were not listed are meant to be removed + for (OrganisationGroup obsoleteExistingGroup : existingGroups) { + delete(obsoleteExistingGroup); + } + } public void saveAll(Collection objects) { for (Object o : objects) { Index: lams_common/src/java/org/lamsfoundation/lams/util/WebUtil.java =================================================================== diff -u -r216fbff3a88bbd983de02053931906fa476fc3ce -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_common/src/java/org/lamsfoundation/lams/util/WebUtil.java (.../WebUtil.java) (revision 216fbff3a88bbd983de02053931906fa476fc3ce) +++ lams_common/src/java/org/lamsfoundation/lams/util/WebUtil.java (.../WebUtil.java) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -17,8 +17,11 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; +import org.apache.tomcat.util.json.JSONException; +import org.apache.tomcat.util.json.JSONObject; import org.lamsfoundation.lams.tool.ToolAccessMode; import org.lamsfoundation.lams.tool.exception.ToolException; +import org.lamsfoundation.lams.usermanagement.User; /** * helper methods useful for servlets @@ -48,7 +51,7 @@ String valueRequest = req.getParameter(tokenName); WebUtil.log.debug("(Session Token) name : " + tokenName + " value : " + valueSession); WebUtil.log.debug("(Request Token) name : " + tokenName + " value : " + valueRequest); - if (valueSession != null && valueRequest != null) { + if ((valueSession != null) && (valueRequest != null)) { if (valueSession.equals(valueRequest)) { return true; } @@ -74,7 +77,7 @@ */ public static void saveToken(HttpServletRequest req, String tokenName, String tokenValue) { if (req.getSession().getAttribute(tokenName) != null) { - resetToken(req, tokenName); + WebUtil.resetToken(req, tokenName); } req.getSession().setAttribute(tokenName, tokenValue); WebUtil.log.debug("(Save Session Token) name : " + tokenName + " value : " + tokenValue); @@ -117,7 +120,7 @@ throws IllegalArgumentException { try { if (!isOptional) { - checkObject(paramName, paramValue); + WebUtil.checkObject(paramName, paramValue); } String value = paramValue != null ? StringUtils.trimToNull(paramValue) : null; return value != null ? new Integer(value) : null; @@ -136,7 +139,7 @@ throws IllegalArgumentException { try { if (!isOptional) { - checkObject(paramName, paramValue); + WebUtil.checkObject(paramName, paramValue); } String value = paramValue != null ? StringUtils.trimToNull(paramValue) : null; return value != null ? new Long(value) : null; @@ -156,7 +159,7 @@ */ public static long checkLong(String paramName, Long paramValue, boolean isOptional) throws IllegalArgumentException { if (!isOptional) { - checkObject(paramName, paramValue); + WebUtil.checkObject(paramName, paramValue); } return paramValue.longValue(); } @@ -168,7 +171,7 @@ */ public static boolean checkBoolean(String paramName, String paramValue) throws IllegalArgumentException { - checkObject(paramName, paramValue); + WebUtil.checkObject(paramName, paramValue); return Boolean.valueOf(paramValue.trim()).booleanValue(); } @@ -182,7 +185,7 @@ * @return parameter value */ public static int readIntParam(HttpServletRequest req, String paramName) { - return checkInteger(paramName, req.getParameter(paramName), false).intValue(); + return WebUtil.checkInteger(paramName, req.getParameter(paramName), false).intValue(); } /** @@ -196,7 +199,7 @@ * @return parameter value */ public static Integer readIntParam(HttpServletRequest req, String paramName, boolean isOptional) { - return checkInteger(paramName, req.getParameter(paramName), isOptional); + return WebUtil.checkInteger(paramName, req.getParameter(paramName), isOptional); } /** @@ -209,7 +212,7 @@ * @return parameter value */ public static long readLongParam(HttpServletRequest req, String paramName) { - return checkLong(paramName, req.getParameter(paramName), false).longValue(); + return WebUtil.checkLong(paramName, req.getParameter(paramName), false).longValue(); } /** @@ -223,7 +226,7 @@ * @return parameter value */ public static Long readLongParam(HttpServletRequest req, String paramName, boolean isOptional) { - return checkLong(paramName, req.getParameter(paramName), isOptional); + return WebUtil.checkLong(paramName, req.getParameter(paramName), isOptional); } /** @@ -234,7 +237,7 @@ * @return parameter value */ public static String readStrParam(HttpServletRequest req, String paramName) { - return readStrParam(req, paramName, false); + return WebUtil.readStrParam(req, paramName, false); } /** @@ -247,7 +250,7 @@ */ public static String readStrParam(HttpServletRequest req, String paramName, boolean isOptional) { if (!isOptional) { - checkObject(paramName, req.getParameter(paramName)); + WebUtil.checkObject(paramName, req.getParameter(paramName)); } return req.getParameter(paramName); } @@ -262,7 +265,7 @@ * - if valid boolean parameter value is not found */ public static boolean readBooleanParam(HttpServletRequest req, String paramName) throws IllegalArgumentException { - return checkBoolean(paramName, req.getParameter(paramName)); + return WebUtil.checkBoolean(paramName, req.getParameter(paramName)); } /** @@ -276,14 +279,14 @@ */ public static boolean readBooleanParam(HttpServletRequest req, String paramName, boolean defaultValue) { try { - return checkBoolean(paramName, req.getParameter(paramName)); + return WebUtil.checkBoolean(paramName, req.getParameter(paramName)); } catch (IllegalArgumentException e) { return defaultValue; } } public static boolean readBooleanAttr(HttpServletRequest req, String attrName) { - return checkBoolean(attrName, (String) req.getSession().getAttribute(attrName)); + return WebUtil.checkBoolean(attrName, (String) req.getSession().getAttribute(attrName)); } /** @@ -325,7 +328,7 @@ * @return the ToolAccessMode object */ public static ToolAccessMode readToolAccessModeParam(HttpServletRequest request, String param_mode, boolean optional) { - String mode = readStrParam(request, param_mode, optional); + String mode = WebUtil.readStrParam(request, param_mode, optional); if (mode == null) { return null; } else if (mode.equals(ToolAccessMode.AUTHOR.toString())) { @@ -397,7 +400,7 @@ * @return the url with parameter appended. */ public static String appendParameterToURL(String url, String parameterName, String parameterValue) { - return appendParameterDeliminator(url) + parameterName + "=" + parameterValue; + return WebUtil.appendParameterDeliminator(url) + parameterName + "=" + parameterValue; } /** @@ -628,11 +631,23 @@ String[] paramEntries = queryPart.split("&"); for (String paramEntry : paramEntries) { String[] paramEntrySplitted = paramEntry.split("="); - if (paramEntrySplitted.length > 1 && param.equalsIgnoreCase(paramEntrySplitted[0])) { + if ((paramEntrySplitted.length > 1) && param.equalsIgnoreCase(paramEntrySplitted[0])) { return paramEntrySplitted[1]; } } } return null; } + + /** + * Produces JSON object with basic user details. + */ + public static JSONObject userToJSON(User user) throws JSONException { + JSONObject userJSON = new JSONObject(); + userJSON.put("id", user.getUserId()); + userJSON.put("firstName", user.getFirstName()); + userJSON.put("lastName", user.getLastName()); + userJSON.put("login", user.getLogin()); + return userJSON; + } } \ No newline at end of file Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java =================================================================== diff -u -r598f0a6ed628f1bc27a2ffa097e2a122fa18e8a4 -rf92865513dd2beca642af28ab42096ee4849ac32 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java (.../MonitoringAction.java) (revision 598f0a6ed628f1bc27a2ffa097e2a122fa18e8a4) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringAction.java (.../MonitoringAction.java) (revision f92865513dd2beca642af28ab42096ee4849ac32) @@ -38,7 +38,6 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -64,7 +63,6 @@ import org.lamsfoundation.lams.lesson.service.ILessonService; import org.lamsfoundation.lams.lesson.util.LearnerProgressComparator; import org.lamsfoundation.lams.lesson.util.LearnerProgressNameComparator; -import org.lamsfoundation.lams.lesson.util.LessonComparator; import org.lamsfoundation.lams.monitoring.MonitoringConstants; import org.lamsfoundation.lams.monitoring.dto.ContributeActivityDTO; import org.lamsfoundation.lams.monitoring.service.IMonitoringService; @@ -591,13 +589,13 @@ boolean getMonitors = Role.MONITOR.equalsIgnoreCase(role); boolean classOnly = WebUtil.readBooleanParam(request, "classOnly", true); Lesson lesson = getLessonService().getLesson(lessonId); - Set classUsers = (Set) (getMonitors ? lesson.getLessonClass().getStaffGroup().getUsers() : lesson - .getLessonClass().getLearners()); + Set classUsers = getMonitors ? lesson.getLessonClass().getStaffGroup().getUsers() : lesson + .getLessonClass().getLearners(); JSONArray responseJSON = new JSONArray(); // get class members for (User user : classUsers) { - JSONObject userJSON = MonitoringAction.userToJSON(user); + JSONObject userJSON = WebUtil.userToJSON(user); if (!classOnly) { // mark that this user is a class member userJSON.put("classMember", true); @@ -617,7 +615,7 @@ .getOrganisationId(), getMonitors ? Role.MONITOR : Role.LEARNER, false, true); for (User user : orgUsers) { if (!classUsers.contains(user)) { - JSONObject userJSON = MonitoringAction.userToJSON(user); + JSONObject userJSON = WebUtil.userToJSON(user); userJSON.put("classMember", false); responseJSON.put(userJSON); } @@ -736,7 +734,7 @@ return null; } - + public ActionForward getAllCompletedActivities(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -757,7 +755,7 @@ return null; } - + /** * Calls the server to bring up the learner progress page. Assumes destination is a new window. The userid that * comes from Flash is the user id of the learner for which we are calculating the url. This is different to all the @@ -938,7 +936,7 @@ int fromIndex = Math.min((pageNumber - 1) * 10, Math.max(toIndex - 10, 0)); // get just the requested chunk for (LearnerProgress learnerProgress : learnerProgresses.subList(fromIndex, toIndex)) { - responseJSON.append("learners", MonitoringAction.userToJSON(learnerProgress.getUser())); + responseJSON.append("learners", WebUtil.userToJSON(learnerProgress.getUser())); } responseJSON.put("numberActiveLearners", learnerProgresses.size()); @@ -1064,15 +1062,15 @@ for (LearnerProgress learnerProgress : (Set) lesson.getLearnerProgresses()) { User learner = learnerProgress.getUser(); if (learnerProgress.isComplete()) { - JSONObject learnerJSON = MonitoringAction.userToJSON(learner); + JSONObject learnerJSON = WebUtil.userToJSON(learner); // no more details are needed for learners who completed the lesson responseJSON.append("completedLearners", learnerJSON); } else { Activity currentActivity = learnerProgress.getCurrentActivity(); if ((currentActivity != null) && ((branchingActivityId == null) || MonitoringAction.isBranchingChild(branchingActivityId, currentActivity))) { - JSONObject learnerJSON = MonitoringAction.userToJSON(learner); + JSONObject learnerJSON = WebUtil.userToJSON(learner); Long currentActivityId = currentActivity.getActivityId(); // monitoring URL for the given learner String learnerUrl = monitoringService.getLearnerActivityURL(lessonId, currentActivityId, @@ -1306,18 +1304,6 @@ } /** - * Produces JSON object with basic user details. - */ - private static JSONObject userToJSON(User user) throws JSONException { - JSONObject userJSON = new JSONObject(); - userJSON.put("id", user.getUserId()); - userJSON.put("firstName", user.getFirstName()); - userJSON.put("lastName", user.getLastName()); - userJSON.put("login", user.getLogin()); - return userJSON; - } - - /** * Converts an activity in learner progress to a JSON object. */ private JSONObject activityProgressToJSON(ActivityURL activity, Long currentActivityId, Long lessonId,