Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -r70457e488f454253072bec5c33c71ea538eeb363 -r1aff6aeac19473c0089c0f3ff578a183beb92555 Binary files differ Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r248e47a1f8714d3e57be1e4dc12b4fae4f396cb3 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 248e47a1f8714d3e57be1e4dc12b4fae4f396cb3) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -740,5 +740,21 @@ index.kumalive.report =Reports index.kumalive.rubric =Rubrics management +label.import.groups.from.template=Import groups from template +label.import.groups.from.template.description=Download the template spreadsheet to create your groups using all the present learners in this lesson/course. Once you upload the file, the groups will be automatically created according to the spreadsheet. +label.download.template=Download Template +label.upload.group.spreadsheet=Upload Finished Spreadsheet +error.file.required=Grouped Spreadsheet Required +error.file.wrong.format=Grouped Spreadsheet in the wrong format. Please upload a file based on the given template. +label.import.successful=Group Import successful. {0} learners allocated to groups, {1} learners skipped. +label.import.warning.replace.groups=Importing this file will replace the existing groups. Do you want to import this file? +filename.create.grouping.template=create-grouping-template +spreadsheet.column.login=Login +spreadsheet.column.firstname=First Name +spreadsheet.column.lastname=Last Name +spreadsheet.column.groupname=Group Name +error.groups.upload.locked=Cannot update groups as the groups are now in use. +error.branching.upload.must.use.existing.groups=Cannot update branching. Must use the existing names: {0}. + #======= End labels: Exported 732 labels for en AU ===== Index: lams_central/src/java/org/lamsfoundation/lams/web/OrganisationGroupAction.java =================================================================== diff -u -rb67c428939ed96f08f56192d54b8ee55d8ab89d2 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_central/src/java/org/lamsfoundation/lams/web/OrganisationGroupAction.java (.../OrganisationGroupAction.java) (revision b67c428939ed96f08f56192d54b8ee55d8ab89d2) +++ lams_central/src/java/org/lamsfoundation/lams/web/OrganisationGroupAction.java (.../OrganisationGroupAction.java) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -196,6 +196,7 @@ lesson = (Lesson) getUserManagementService().findById(Lesson.class, lessonId); organisationId = lesson.getOrganisation().getOrganisationId(); } + request.setAttribute(AttributeNames.PARAM_LESSON_ID, lessonId); // Needed for the download spreadsheet call. // check if user is allowed to view and edit groups if (!getSecurityService().hasOrgRole(organisationId, userId, Index: lams_central/web/WEB-INF/struts-config.xml =================================================================== diff -u -r60e44b19b8de02a00faa437fba8117928baa3d73 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_central/web/WEB-INF/struts-config.xml (.../struts-config.xml) (revision 60e44b19b8de02a00faa437fba8117928baa3d73) +++ lams_central/web/WEB-INF/struts-config.xml (.../struts-config.xml) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -29,8 +29,11 @@ name="emailForm" type="org.lamsfoundation.lams.web.EmailForm" /> - + @@ -787,6 +790,19 @@ scope="request"> + + + + + + Index: lams_central/web/css/orgGroup.scss =================================================================== diff -u -rb3c871874679a1d784db0d7dfac2fdabe65baf83 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_central/web/css/orgGroup.scss (.../orgGroup.scss) (revision b3c871874679a1d784db0d7dfac2fdabe65baf83) +++ lams_central/web/css/orgGroup.scss (.../orgGroup.scss) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -27,8 +27,9 @@ margin-top: 7px; border-bottom: $border-thin-dotted; } + table#groupsTable { - height: calc(100% - 110px); + height: calc(100% - 280px); } table#groupsTable td { vertical-align: top; @@ -112,13 +113,24 @@ width: 100%; padding: 10px; } + + #upload-group-file-settings { + display: inline-block; + width: 100%; + padding: 10px; + min-height: 150px; // allow a bit extra so the whole accordian is seen on iPad +} + #save-course-grouping-button { margin-top: 10px; } #accordionAdvanced { margin-bottom: 0px; margin-top: 20px; } +#accordionUploadGroupFile { + margin-bottom: 0px; +} .dialogContainer { display: none; -webkit-overflow-scrolling: touch !important; Index: lams_central/web/includes/javascript/orgGroup.js =================================================================== diff -u -rb3c871874679a1d784db0d7dfac2fdabe65baf83 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_central/web/includes/javascript/orgGroup.js (.../orgGroup.js) (revision b3c871874679a1d784db0d7dfac2fdabe65baf83) +++ lams_central/web/includes/javascript/orgGroup.js (.../orgGroup.js) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -90,6 +90,10 @@ fillGroup(users, group); + // if any users are allocated, warn before doing spreadsheet reload + if ( users && users.length > 0 ) + warnBeforeUpload = true; + return group; } @@ -521,8 +525,130 @@ $('.removeGroupButton', container).remove(); // $('input', container).attr('readonly', 'readonly'); $('div.draggableItem', container).off('click').draggable('disable'); + // if any group is locked cannot do the spreadsheet upload + $('#accordionUploadGroupFile').css("display","none"); } +/** + * *************** Import groups from a spreadsheet *************** + */ + function enableButtons() { + // do not disable the file button or the file will be missing on the upload. + $('.btn-disable-on-downupload').prop('disabled', false); + $('a.btn-disable-on-downupload').show(); // links must be hidden, cannot be disabled + + // show the waiting area during the upload + var div = document.getElementById("attachmentArea_Busy"); + if(div != null){ + div.style.display = 'none'; + } + } + function disableButtons() { + // do not disable the file button or the file will be missing on the upload. + $('.btn-disable-on-downupload').prop('disabled', true); + $('a.btn-disable-on-downupload').hide(); // links must be hidden, cannot be disabled + + // show the waiting area during the upload + var div = document.getElementById("attachmentArea_Busy"); + if(div != null){ + div.style.display = 'inline-block'; + } + } + + function importGroupsFromSpreadsheet() { + disableButtons(); + var file = getValidateSpreadsheetFile(); + if ( file != null && ( ! warnBeforeUpload || confirm(LABELS.WARNING_REPLACE_GROUPS_LABEL) ) ) { + var form = $("#uploadForm")[0]; + var formDataUpload = new FormData(form); + formDataUpload.append("organisationID", organisationId); + $.ajax({ + data: formDataUpload, + processData: false, // tell jQuery not to process the data + contentType: false, // tell jQuery not to set contentType + type: 'POST', + url: form.action, + enctype: "multipart/form-data", + dataType : 'json', + success: function (response) { + if ( response.result != 'OK') { + if ( response.error ) { + alert(response.error); + if ( response.reload ) { + window.location.reload(); // do not have another go, look at new data + } else { + enableButtons(); // let them have another go + } + } + else { + // unknown failure on back end. + alert(LABELS.GENERAL_ERROR_LABEL); + var div = document.getElementById("attachmentArea_Busy"); + if(div != null){ + div.style.display = 'none'; + } + } + } else { + var msg = LABELS.LABEL_IMPORT_SUCCESSFUL_LABEL.replace("%1", response.added).replace("%2", response.skipped); + alert(msg); + window.location.reload(); + } + }, + error: function() { + // unknown failure on back end. + alert(LABELS.GENERAL_ERROR_LABEL); + var div = document.getElementById("attachmentArea_Busy"); + if(div != null){ + div.style.display = 'none'; + } + } + }); + } else { + enableButtons(); + } + } + + function getValidateSpreadsheetFile() { + var file = null; + // check file + var fileSelect = document.getElementById('groupUploadFile'); + var files = fileSelect.files; + if (files.length == 0) { + clearFileError(); + var requiredMsg = LABELS.ERROR_FILE_REQUIRED_LABEL; + showFileError(requiredMsg); + } else if ( validateShowErrorSpreadsheetType(files[0], LABELS.ERROR_FILE_WRONG_FORMAT_LABEL, false) ) { + file = files[0]; + } + return file; + } + + var fileDownloadCheckTimer; + + function downloadTemplate() { + disableButtons(); + var token = new Date().getTime(); //use the current timestamp as the token value + + fileDownloadCheckTimer = window.setInterval(function () { + var cookieValue = $.cookie('fileDownloadToken'); + if (cookieValue == token) { + enableButtons(); + } + }, 1000); + + document.location.href = LAMS_URL + "groupingUpload.do?method=getGroupTemplateFile&activityID="+groupingActivityId + +"&organisationID="+organisationId+"&lessonID="+lessonId+"&downloadTokenValue=" + token; + return false; + } + + $(document).ready(function(){ + + //scroll to the bottom of the page on opening Advanced settings + $('#accordionUploadGroupFile').on('shown.bs.collapse', function () { + $("html, body").animate({ scrollTop: 170 }, 1000); + }); + }); + /** * *************** Save as a course grouping dialog *************** */ Index: lams_central/web/orgGroup.jsp =================================================================== diff -u -rb3c871874679a1d784db0d7dfac2fdabe65baf83 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_central/web/orgGroup.jsp (.../orgGroup.jsp) (revision b3c871874679a1d784db0d7dfac2fdabe65baf83) +++ lams_central/web/orgGroup.jsp (.../orgGroup.jsp) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -2,7 +2,14 @@ <%@ taglib uri="tags-lams" prefix="lams"%> <%@ taglib uri="tags-fmt" prefix="fmt"%> <%@ taglib uri="tags-core" prefix="c"%> +<%@ taglib uri="tags-html" prefix="html" %> +<%@ page import="org.lamsfoundation.lams.util.Configuration" %> +<%@ page import="org.lamsfoundation.lams.util.ConfigurationKeys" %> +<%@ page import="org.lamsfoundation.lams.util.FileValidatorUtil" %> +<%=Configuration.get(ConfigurationKeys.UPLOAD_FILE_MAX_SIZE)%> +<%=FileValidatorUtil.formatSize(Configuration.getAsInt(ConfigurationKeys.UPLOAD_FILE_MAX_SIZE))%> + @@ -14,23 +21,27 @@ + + @@ -81,12 +107,12 @@ /> - - + + +
+
+
+ + + +
+
+
+ +
+ +
+
+ + + + <%-- Value is set in Javascript as it comes in the JSON --%> + + + +
+ +
+
+
+
+
+ @@ -169,10 +234,10 @@
- -
Index: lams_common/src/java/org/lamsfoundation/lams/learningdesign/Grouper.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_common/src/java/org/lamsfoundation/lams/learningdesign/Grouper.java (.../Grouper.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_common/src/java/org/lamsfoundation/lams/learningdesign/Grouper.java (.../Grouper.java) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -164,6 +164,41 @@ } /** + * Remove all the learners from the given grouping. Cannot remove learners if any of the groups are already in use + * (i.e. a tool + * session exists) + * + * @param grouping + * (mandatory) + * @param groupID + * (mandatory) + */ + public void removeAllLearnersFromGrouping(Grouping grouping) throws GroupingException { + + boolean canRemove = true; + + for (Group group : grouping.getGroups()) { + if (!group.mayBeDeleted()) { + canRemove = false; + break; + } + } + + if (canRemove) { + for (Group group : grouping.getGroups()) { + if (log.isDebugEnabled()) { + log.debug("Cleared all users from group " + group.getGroupName()); + } + group.getUsers().clear(); + } + } else { + String error = "Tried to clear a group which cannot be removed (tool sessions probably exist). Grouping " + + grouping + ". Not removing the groupings."; + log.error(error); + throw new GroupingException(error); + } + } + /** * Create an empty group for the given grouping. Trims the name of the group before creating the group. If the group * name group name already exists then it appends a datetime string to make the name unique. Gives it 5 attempts to * make it unique then gives up. Index: lams_common/src/java/org/lamsfoundation/lams/lesson/service/ILessonService.java =================================================================== diff -u -rfba2480356aa5ddda0c8308eb917d72d16aa32eb -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_common/src/java/org/lamsfoundation/lams/lesson/service/ILessonService.java (.../ILessonService.java) (revision fba2480356aa5ddda0c8308eb917d72d16aa32eb) +++ lams_common/src/java/org/lamsfoundation/lams/lesson/service/ILessonService.java (.../ILessonService.java) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -183,6 +183,14 @@ void removeLearnersFromGroup(Grouping grouping, Long groupId, List learners) throws LessonServiceException; /** + * Remove all the learners from the given grouping but leave the groups. + * + * @param grouping + * the grouping from which to remove the learners (mandatory) + */ + void removeAllLearnersFromGrouping(Grouping grouping) throws LessonServiceException; + + /** * Create an empty group for the given grouping. If the group name already exists then it will force the name to be * unique. * Index: lams_common/src/java/org/lamsfoundation/lams/lesson/service/LessonService.java =================================================================== diff -u -rfba2480356aa5ddda0c8308eb917d72d16aa32eb -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_common/src/java/org/lamsfoundation/lams/lesson/service/LessonService.java (.../LessonService.java) (revision fba2480356aa5ddda0c8308eb917d72d16aa32eb) +++ lams_common/src/java/org/lamsfoundation/lams/lesson/service/LessonService.java (.../LessonService.java) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -267,6 +267,24 @@ groupingDAO.update(grouping); } } + + @Override + public void removeAllLearnersFromGrouping(Grouping grouping) + throws LessonServiceException { + if (grouping != null) { + // get the real objects, not the CGLIB version + grouping = groupingDAO.getGroupingById(grouping.getGroupingId()); + Grouper grouper = grouping.getGrouper(); + if (grouper != null) { + try { + grouper.removeAllLearnersFromGrouping(grouping); + } catch (GroupingException e) { + throw new LessonServiceException(e); + } + } + groupingDAO.update(grouping); + } + } @Override public Group createGroup(Grouping grouping, String name) throws LessonServiceException { Index: lams_common/src/java/org/lamsfoundation/lams/util/ExcelUtil.java =================================================================== diff -u -r39ceb19b74c579d46fb624c3b3ab02fd8e79683a -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_common/src/java/org/lamsfoundation/lams/util/ExcelUtil.java (.../ExcelUtil.java) (revision 39ceb19b74c579d46fb624c3b3ab02fd8e79683a) +++ lams_common/src/java/org/lamsfoundation/lams/util/ExcelUtil.java (.../ExcelUtil.java) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -29,9 +29,9 @@ import java.util.LinkedHashMap; import org.apache.commons.lang.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.DataFormat; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Row; @@ -71,8 +71,12 @@ public final static String DEFAULT_FONT_NAME = "Calibri-Regular"; + /** - * Create .xlsx file out of provided data and then write out it to an OutputStream. + * Create .xls file out of provided data and then write out it to an OutputStream. It should be saved with the .xls extension. + * Only use if you want to read the file back in again afterwards. + * + * Warning: The styling is untested with this option and may fail. If you want full styling look at createExcel() * * @param out * output stream to which the file written; usually taken from HTTP response @@ -86,10 +90,36 @@ * whether to display title (printed in the first (0,0) cell) * @throws IOException */ + public static void createExcelXLS(OutputStream out, LinkedHashMap dataToExport, + String dateHeader, boolean displaySheetTitle) throws IOException { + Workbook workbook = new HSSFWorkbook(); + create(workbook, out, dataToExport, dateHeader, displaySheetTitle); + } + + /** + * Create .xlsx file out of provided data and then write out it to an OutputStream. It should be saved with the .xlsx extension. + * + * @param out + * output stream to which the file written; usually taken from HTTP response + * @param dataToExport + * array of data to print out; first index of array describes a row, second a column + * @param dateHeader + * text describing current date; if NULL then no date is printed; if not NULL + * then text is written out along with current date in the cell; the date is formatted according to + * {@link #EXPORT_TO_SPREADSHEET_TITLE_DATE_FORMAT} + * @param displaySheetTitle + * whether to display title (printed in the first (0,0) cell) + * @throws IOException + */ public static void createExcel(OutputStream out, LinkedHashMap dataToExport, String dateHeader, boolean displaySheetTitle) throws IOException { Workbook workbook = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk - + create(workbook, out, dataToExport, dateHeader, displaySheetTitle); + } + + private static void create(Workbook workbook, OutputStream out, LinkedHashMap dataToExport, + String dateHeader, boolean displaySheetTitle) throws IOException { + Font defaultFont = workbook.createFont(); defaultFont.setFontName(DEFAULT_FONT_NAME); Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/service/IMonitoringService.java =================================================================== diff -u -r001b71e1b6da336c4adb6376c31ba95d4ace7b28 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/service/IMonitoringService.java (.../IMonitoringService.java) (revision 001b71e1b6da336c4adb6376c31ba95d4ace7b28) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/service/IMonitoringService.java (.../IMonitoringService.java) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -503,6 +503,11 @@ abstract void addUsersToGroup(Long activityID, Long groupID, String learnerIDs[]) throws LessonServiceException; /** + * Add learners to a group based on their logins. Doesn't necessarily check if the user is already in another group. + */ + abstract int addUsersToGroupByLogins(Long activityID, String groupName, Set logins) throws LessonServiceException; + + /** * Remove a user to a group. If the user is not in the group, then nothing is changed. * * @throws LessonServiceException Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/service/MonitoringService.java =================================================================== diff -u -r001b71e1b6da336c4adb6376c31ba95d4ace7b28 -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/service/MonitoringService.java (.../MonitoringService.java) (revision 001b71e1b6da336c4adb6376c31ba95d4ace7b28) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/service/MonitoringService.java (.../MonitoringService.java) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -2110,6 +2110,56 @@ return learners; } + @Override + public int addUsersToGroupByLogins(Long activityID, String groupName, Set logins) throws LessonServiceException { + + ArrayList learners = new ArrayList(); + for (String login : logins) { + User learner = (User) userManagementService.getUserByLogin(login); + if (learner == null) { + MonitoringService.log.warn("Unable to add learner " + login + " for group in related to activity " + + activityID + " as learner cannot be found."); + } else { + learners.add(learner); + } + } + + Activity activity = getActivityById(activityID); + Grouping grouping = getGroupingForActivity(activity, !activity.isChosenBranchingActivity(), + "addUsersToGroupByLogins"); + + Group group = null; + Set otherGroupNames = new HashSet(); + for ( Group checkGroup: grouping.getGroups() ) { + if ( checkGroup.getGroupName().equalsIgnoreCase(groupName) ) { + group = checkGroup; + break; + } else { + otherGroupNames.add(checkGroup.getGroupName()); + } + } + + if ( group == null ) { + // Leave performGrouping to create any new groups as addGroup returns a group without an id, so it could not be + // used by performGrouping. Fix up name afterwards. Clumsy way to find to find the new group but how else? + // It may not be the only new group and hence not the only group with no id. + lessonService.performGrouping(grouping, (Long) null, learners); + for ( Group checkGroup: grouping.getGroups() ) { + if ( ! otherGroupNames.contains(checkGroup.getGroupName()) ) { + group = checkGroup; + break; + } + } + if ( group != null ) { + group.setGroupName(groupName); + } + } else { + lessonService.performGrouping(grouping, group.getGroupId(), learners); + } + + return learners.size(); + } + @SuppressWarnings("unchecked") @Override public void createChosenBranchingGroups(Long branchingActivityID) { Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/FileUploadForm.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/FileUploadForm.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/FileUploadForm.java (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -0,0 +1,65 @@ +/**************************************************************** + * 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 + * **************************************************************** + */ + + +package org.lamsfoundation.lams.monitoring.web; + +import org.apache.struts.action.ActionForm; +import org.apache.struts.upload.FormFile; + +public class FileUploadForm extends ActionForm { + + private static final long serialVersionUID = -2841551690414943725L; + + private Integer organisationID; + private Long lessonID; + private Long activityID; + private FormFile attachmentFile; + public Integer getOrganisationID() { + return organisationID; + } + public void setOrganisationID(Integer organisationID) { + this.organisationID = organisationID; + } + public Long getLessonID() { + return lessonID; + } + public void setLessonID(Long lessonID) { + this.lessonID = lessonID; + } + public Long getActivityID() { + return activityID; + } + public void setActivityID(Long activityID) { + this.activityID = activityID; + } + public FormFile getAttachmentFile() { + return attachmentFile; + } + public void setAttachmentFile(FormFile attachmentFile) { + this.attachmentFile = attachmentFile; + } + + + +} Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingUploadAJAXAction.java =================================================================== diff -u --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingUploadAJAXAction.java (revision 0) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/GroupingUploadAJAXAction.java (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -0,0 +1,403 @@ +/**************************************************************** + * 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 + * **************************************************************** + */ + +package org.lamsfoundation.lams.monitoring.web; + +import java.io.IOException; +import java.security.InvalidParameterException; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.Cell; +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.struts.upload.FormFile; +import org.apache.tomcat.util.json.JSONException; +import org.apache.tomcat.util.json.JSONObject; +import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.GroupComparator; +import org.lamsfoundation.lams.learningdesign.Grouping; +import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.monitoring.service.IMonitoringService; +import org.lamsfoundation.lams.monitoring.service.MonitoringServiceProxy; +import org.lamsfoundation.lams.security.ISecurityService; +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.service.IUserManagementService; +import org.lamsfoundation.lams.util.AlphanumComparator; +import org.lamsfoundation.lams.util.ExcelCell; +import org.lamsfoundation.lams.util.ExcelUtil; +import org.lamsfoundation.lams.util.FileUtil; +import org.lamsfoundation.lams.util.MessageService; +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; + +/** + * The action servlet that provides the support for the AJAX based Chosen Grouping upload from File + * + * @author Fiona Malikoff + */ +public class GroupingUploadAJAXAction extends DispatchAction { + + private static Logger log = Logger.getLogger(GroupingUploadAJAXAction.class); + + private static IUserManagementService userManagementService; + private static ILessonService lessonService; + private static ISecurityService securityService; + private static MessageService centralMessageService; + + /** + * Get the spreadsheet file containing list of the current users, ready for uploading with groups. + * + * @throws Exception + */ + public ActionForward getGroupTemplateFile(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws Exception { + + Integer userId = getUserDTO().getUserID(); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + Lesson lesson = (Lesson) getUserManagementService().findById(Lesson.class, lessonId); + ; + if (organisationId == null) { + // read organisation ID from lesson + organisationId = lesson.getOrganisation().getOrganisationId(); + } + Long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + + // check if user is allowed to view and edit groups + if (!getSecurityService().hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_ADMIN, Role.GROUP_MANAGER, Role.MONITOR, Role.AUTHOR }, + "view organisation groups", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a participant in the organisation"); + return null; + } + + // check for any groups already exist in this grouping + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(getServlet().getServletContext()); + Activity activity = monitoringService.getActivityById(activityId); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) activity).getCreateGrouping(); + + String fileName = new StringBuilder( + getCentralMessageService().getMessage("filename.create.grouping.template").trim()).append(" ") + .append(lesson.getLessonName()).append(".xls").toString().replaceAll(" ", "-"); + fileName = FileUtil.encodeFilenameForDownload(request, fileName); + + response.setContentType("application/x-download"); + response.setHeader("Content-Disposition", "attachment;filename=" + fileName); + ServletOutputStream out = response.getOutputStream(); + + LinkedHashMap dataToExport = exportLearnersForGrouping(lesson, grouping.getGroups()); + + // set cookie that will tell JS script that export has been finished + String downloadTokenValue = WebUtil.readStrParam(request, "downloadTokenValue"); + Cookie fileDownloadTokenCookie = new Cookie("fileDownloadToken", downloadTokenValue); + fileDownloadTokenCookie.setPath("/"); + response.addCookie(fileDownloadTokenCookie); + + ExcelUtil.createExcelXLS(out, dataToExport, null, false); + return null; + } + + private LinkedHashMap exportLearnersForGrouping(Lesson lesson, Set groups) { + + List rowList = new LinkedList(); + int numberOfColumns = 4; + + ExcelCell[] title = new ExcelCell[numberOfColumns]; + title[0] = new ExcelCell(getCentralMessageService().getMessage("spreadsheet.column.login"), false); + title[1] = new ExcelCell(getCentralMessageService().getMessage("spreadsheet.column.firstname"), false); + title[2] = new ExcelCell(getCentralMessageService().getMessage("spreadsheet.column.lastname"), false); + title[3] = new ExcelCell(getCentralMessageService().getMessage("spreadsheet.column.groupname"), false); + rowList.add(title); + + Collection learners = lesson.getLessonClass().getLearners(); + + List groupList = new LinkedList<>(groups); + Collections.sort(groupList, new GroupComparator()); + for (Group group : groupList) { + String groupName = group.getGroupName(); + for (User groupUser : group.getUsers()) { + rowList.add(generateUserRow(numberOfColumns, groupName, groupUser)); + learners.remove(groupUser); + } + } + + // all the remaining users are unassigned to any group + for (User unassignedUser : learners) { + rowList.add(generateUserRow(numberOfColumns, null, unassignedUser)); + } + + ExcelCell[][] summaryData = rowList.toArray(new ExcelCell[][] {}); + LinkedHashMap dataToExport = new LinkedHashMap(); + dataToExport.put(getCentralMessageService().getMessage("label.course.groups.prefix"), summaryData); + return dataToExport; + } + + private ExcelCell[] generateUserRow(int numberOfColumns, String groupName, User groupUser) { + ExcelCell[] userRow = new ExcelCell[numberOfColumns]; + userRow[0] = new ExcelCell(groupUser.getLogin(), false); + userRow[1] = new ExcelCell(groupUser.getFirstName(), false); + userRow[2] = new ExcelCell(groupUser.getLastName(), false); + userRow[3] = new ExcelCell(groupName, false); + return userRow; + } + + /** + * Saves a course grouping. + */ + public ActionForward importLearnersForGrouping(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws JSONException, InvalidParameterException, IOException { + + Integer userId = getUserDTO().getUserID(); + Integer organisationId = WebUtil.readIntParam(request, AttributeNames.PARAM_ORGANISATION_ID, true); + Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); + Long activityId = WebUtil.readLongParam(request, AttributeNames.PARAM_ACTIVITY_ID); + Lesson lesson = (Lesson) getUserManagementService().findById(Lesson.class, lessonId); + + if (organisationId == null) { + // read organisation ID from lesson + organisationId = lesson.getOrganisation().getOrganisationId(); + } + + // check if user is allowed to save grouping + if (!getSecurityService().hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_ADMIN, Role.GROUP_MANAGER }, "save organisation grouping from spreadsheet", + false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a manager or admin in the organisation"); + return null; + } + + @SuppressWarnings("rawtypes") + Hashtable fileElements = form.getMultipartRequestHandler().getFileElements(); + if (fileElements.size() == 0) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "No file provided"); + } + if (GroupingUploadAJAXAction.log.isDebugEnabled()) { + GroupingUploadAJAXAction.log.debug("Saving course groups from spreadsheet for user " + userId + + " and organisation " + organisationId + " filename " + fileElements); + } + + // remove users from current group + IMonitoringService monitoringService = MonitoringServiceProxy + .getMonitoringService(getServlet().getServletContext()); + + Activity activity = monitoringService.getActivityById(activityId); + Grouping grouping = activity.isChosenBranchingActivity() ? activity.getGrouping() + : ((GroupingActivity) activity).getCreateGrouping(); + + Set existingGroupNames = new HashSet(); + // check for any users starting to use the groups since the page was loaded. + for (Group group : grouping.getGroups()) { + existingGroupNames.add(group.getGroupName()); + if (!group.mayBeDeleted()) { + String error = getCentralMessageService().getMessage("error.groups.upload.locked"); + return returnImport(response, error, true); + } + } + + Map> groups = new HashMap>(); + int totalUserSkipped = parseGroupSpreadsheet((FormFile) fileElements.elements().nextElement(), activityId, + groups); + int totalUserAdded = 0; + + // if branching must use the already specified groups or cannot match to a branch! + if (activity.isChosenBranchingActivity()) { + for (Map.Entry> groupEntry : groups.entrySet()) { + if (!existingGroupNames.contains(groupEntry.getKey())) { + StringBuilder groupNamesStrBlder = new StringBuilder(); + for (String name : existingGroupNames) + groupNamesStrBlder.append("'").append(name).append("' "); + String error = getCentralMessageService().getMessage( + "error.branching.upload.must.use.existing.groups", + new String[] { groupNamesStrBlder.toString() }); + return returnImport(response, error.toString(), false); + } + } + } + + // remove all the existing users from their groups + getLessonService().removeAllLearnersFromGrouping(grouping); + + // Now put in the new users groupings + for (Map.Entry> groupEntry : groups.entrySet()) { + int added = monitoringService.addUsersToGroupByLogins(activityId, groupEntry.getKey(), + groupEntry.getValue()); + totalUserAdded += added; + totalUserSkipped += groupEntry.getValue().size() - added; + } + JSONObject responseJSON = new JSONObject(); + responseJSON.put("result", "OK"); + responseJSON.put("added", totalUserAdded); + responseJSON.put("skipped", totalUserSkipped); + + response.getWriter().write(responseJSON.toString()); + return null; + } + + private ActionForward returnImport(HttpServletResponse response, String errorMessage, boolean reload) + throws JSONException, IOException { + JSONObject responseJSON = new JSONObject(); + responseJSON.put("result", "FAIL"); + responseJSON.put("reload", reload); + responseJSON.put("error", errorMessage); + response.getWriter().write(responseJSON.toString()); + return null; + + } + + + /* XLS Version Parse */ + private String parseStringCell(HSSFCell cell) { + if (cell != null) { + cell.setCellType(Cell.CELL_TYPE_STRING); + if (cell.getStringCellValue() != null) { + return cell.getStringCellValue().trim(); + } + } + return null; + } + + public int parseGroupSpreadsheet(FormFile fileItem, Long activityID, Map> groups) + throws IOException { + POIFSFileSystem fs = new POIFSFileSystem(fileItem.getInputStream()); + HSSFWorkbook wb = new HSSFWorkbook(fs); + HSSFSheet sheet = wb.getSheetAt(0); + + int startRow = sheet.getFirstRowNum(); + int endRow = sheet.getLastRowNum(); + int skipped = 0; + + for (int i = startRow + 1; i < (endRow + 1); i++) { + HSSFRow row = sheet.getRow(i); + String login = parseStringCell(row.getCell(0)); + if (login != null) { + login = login.trim(); + if (login.length() > 0) { + String groupName = row.getLastCellNum() > 3 ? parseStringCell(row.getCell(3)) : null; + groupName = groupName != null ? groupName.trim() : null; + if (groupName == null || groupName.length() == 0) { + skipped++; + GroupingUploadAJAXAction.log.warn("Unable to add learner " + login + + " for group in related to activity " + activityID + " as group name is missing."); + } else { + Set users = groups.get(groupName); + if (users == null) { + users = new HashSet(); + groups.put(groupName, users); + } + users.add(login); + } + } + } + } + return skipped; + } + + final Comparator ORG_GROUP_COMPARATOR = new Comparator() { + @Override + public int compare(OrganisationGroup o1, OrganisationGroup o2) { + String grp1Name = o1 != null ? o1.getName() : ""; + String grp2Name = o2 != null ? o2.getName() : ""; + + AlphanumComparator comparator = new AlphanumComparator(); + return comparator.compare(grp1Name, grp2Name); + } + }; + + private UserDTO getUserDTO() { + HttpSession ss = SessionManager.getSession(); + return (UserDTO) ss.getAttribute(AttributeNames.USER); + } + + private IUserManagementService getUserManagementService() { + if (GroupingUploadAJAXAction.userManagementService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServlet().getServletContext()); + GroupingUploadAJAXAction.userManagementService = (IUserManagementService) ctx + .getBean("userManagementService"); + } + return GroupingUploadAJAXAction.userManagementService; + } + + private ILessonService getLessonService() { + if (GroupingUploadAJAXAction.lessonService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServlet().getServletContext()); + GroupingUploadAJAXAction.lessonService = (ILessonService) ctx.getBean("lessonService"); + } + return GroupingUploadAJAXAction.lessonService; + } + + private ISecurityService getSecurityService() { + if (GroupingUploadAJAXAction.securityService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServlet().getServletContext()); + GroupingUploadAJAXAction.securityService = (ISecurityService) ctx.getBean("securityService"); + } + return GroupingUploadAJAXAction.securityService; + } + + private MessageService getCentralMessageService() { + if (GroupingUploadAJAXAction.centralMessageService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServlet().getServletContext()); + GroupingUploadAJAXAction.centralMessageService = (MessageService) ctx.getBean("centralMessageService"); + } + return GroupingUploadAJAXAction.centralMessageService; + } + +} Index: lams_monitoring/web/grouping/chosenGrouping.jsp =================================================================== diff -u -r5848d955870689ca056a8f7ea7c147947d07dd0f -r1aff6aeac19473c0089c0f3ff578a183beb92555 --- lams_monitoring/web/grouping/chosenGrouping.jsp (.../chosenGrouping.jsp) (revision 5848d955870689ca056a8f7ea7c147947d07dd0f) +++ lams_monitoring/web/grouping/chosenGrouping.jsp (.../chosenGrouping.jsp) (revision 1aff6aeac19473c0089c0f3ff578a183beb92555) @@ -34,7 +34,7 @@ iframe { border: none; width: 100%; - height: 425px; + height: 550px; }