Index: lams_central/conf/security/Owasp.CsrfGuard.properties =================================================================== diff -u -r3bb66b73292b37e2f3773579947b7ead5017ccea -rf7937adfea85b6a6976eaa98df8c68db93f9f060 --- lams_central/conf/security/Owasp.CsrfGuard.properties (.../Owasp.CsrfGuard.properties) (revision 3bb66b73292b37e2f3773579947b7ead5017ccea) +++ lams_central/conf/security/Owasp.CsrfGuard.properties (.../Owasp.CsrfGuard.properties) (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -60,6 +60,22 @@ org.owasp.csrfguard.protected.centralPortraitDelete=/lams/saveportrait/deletePortrait.do org.owasp.csrfguard.protected.centralPortraitSave=/lams/saveportrait.do +#QB +org.owasp.csrfguard.protected.centralSaveQuestion=/lams/qb/edit/saveOrUpdateQuestion.do +org.owasp.csrfguard.protected.centralSaveQTI=/lams/imsqti/saveQTI.do +org.owasp.csrfguard.protected.centralExportQuestionAsQTI=/lams/imsqti/exportQuestionAsQTI.do +org.owasp.csrfguard.protected.centralExportCollectionAsQTI=/lams/imsqti/exportCollectionAsQTI.do +org.owasp.csrfguard.protected.centralShareCollection=/lams/qb/collection/shareCollection.do +org.owasp.csrfguard.protected.centralUnshareCollection=/lams/qb/collection/unshareCollection.do +org.owasp.csrfguard.protected.centralRemoveCollection=/lams/qb/collection/removeCollection.do +org.owasp.csrfguard.protected.centralChangeCollectionName=/lams/qb/collection/changeCollectionName.do +org.owasp.csrfguard.protected.centralAddCollection=/lams/qb/collection/addCollection.do +org.owasp.csrfguard.protected.centralAddCollectionQuestion=/lams/qb/collection/addCollectionQuestion.do +org.owasp.csrfguard.protected.centralRemoveCollectionQuestion=/lams/qb/collection/removeCollectionQuestion.do +org.owasp.csrfguard.protected.centralMergeQuestions=/lams/qb/stats/merge.do +org.owasp.csrfguard.protected.centralExportQuestionsXml=/lams/xmlQuestions/exportQuestionsXml.do +org.owasp.csrfguard.protected.centralImportQuestionsXml=/lams/xmlQuestions/importQuestionsXml.do + #Author org.owasp.csrfguard.protected.centralAuthorSaveLearningDesign=/lams/authoring/saveLearningDesign.do org.owasp.csrfguard.protected.centralExportLearningDesign=/lams/authoring/exportToolContent/export.do @@ -103,6 +119,7 @@ org.owasp.csrfguard.protected.assessmentAuthoringSave=/lams/tool/laasse10/authoring/updateContent.do org.owasp.csrfguard.protected.assessmentAuthoringDefineLater=/lams/tool/laasse10/authoring/definelater.do +org.owasp.csrfguard.protected.assessmentAuthoringQBSaveQuestion=/lams/tool/laasse10/authoring/saveOrUpdateReference.do org.owasp.csrfguard.protected.assessmentDiscloseCorrectAnswers=/lams/tool/laasse10/monitoring/discloseCorrectAnswers.do org.owasp.csrfguard.protected.assessmentDiscloseGroupsAnswers=/lams/tool/laasse10/monitoring/discloseGroupsAnswers.do org.owasp.csrfguard.protected.assessmentMonitoringSubmissionDeadline=/lams/tool/laasse10/monitoring/setSubmissionDeadline.do @@ -151,8 +168,7 @@ org.owasp.csrfguard.protected.laqaAuthoringSave=/lams/tool/laqa11/authoring/submitAllContent.do org.owasp.csrfguard.protected.laqaAuthoringDefineLater=/lams/tool/laqa11/authoring/definelater.do -org.owasp.csrfguard.protected.laqaAuthoringAddSingleQ=/lams/tool/laqa11/authoring/addSingleQuestion.do -org.owasp.csrfguard.protected.laqaAuthoringSaveSingleQ=/lams/tool/laqa11/authoring/saveSingleQuestion.do +org.owasp.csrfguard.protected.laqaAuthoringSaveQuestion=/lams/tool/laqa11/authoring/saveQuestion.do org.owasp.csrfguard.protected.laqaAuthoringRemoveQ=/lams/tool/laqa11/authoring/removeQuestion.do org.owasp.csrfguard.protected.laqaAuthoringSaveOrUpdateCondition=/lams/tool/laqa11/authoringConditions/saveOrUpdateCondition.do org.owasp.csrfguard.protected.laqaAuthoringRemoveCondition=/lams/tool/laqa11/authoringConditions/removeCondition.do Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/AuthoringController.java =================================================================== diff -u --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/AuthoringController.java (revision 0) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/AuthoringController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,816 @@ +/**************************************************************** + * 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 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.tool.mc.web.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.qb.model.QbOption; +import org.lamsfoundation.lams.qb.model.QbQuestion; +import org.lamsfoundation.lams.qb.service.IQbService; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.mc.McAppConstants; +import org.lamsfoundation.lams.tool.mc.dto.McOptionDTO; +import org.lamsfoundation.lams.tool.mc.dto.McQuestionDTO; +import org.lamsfoundation.lams.tool.mc.model.McContent; +import org.lamsfoundation.lams.tool.mc.model.McQueContent; +import org.lamsfoundation.lams.tool.mc.service.IMcService; +import org.lamsfoundation.lams.tool.mc.util.AuthoringUtil; +import org.lamsfoundation.lams.tool.mc.web.form.McAuthoringForm; +import org.lamsfoundation.lams.util.CommonConstants; +import org.lamsfoundation.lams.util.FileUtil; +import org.lamsfoundation.lams.util.MessageService; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.lamsfoundation.lams.web.util.SessionMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Action class that controls the logic of tool behavior. + * + * @author Ozgur Demirtas + */ +@Controller +@RequestMapping("/authoring") +public class AuthoringController { + + private static Logger logger = Logger.getLogger(AuthoringController.class.getName()); + + @Autowired + private IMcService mcService; + + @Autowired + private IQbService qbService; + + @Autowired + @Qualifier("lamcMessageService") + private MessageService messageService; + + @RequestMapping("/authoring") + public String execute(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request) { + ToolAccessMode mode = WebUtil.readToolAccessModeAuthorDefaulted(request); + return readDatabaseData(mcAuthoringForm, request, mode); + } + + /** + * Set the defineLater flag so that learners cannot use content while we are editing. This flag is released when + * updateContent is called. + */ + @RequestMapping(path = "/definelater", method = RequestMethod.POST) + public String definelater(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request) { + String strToolContentID = request.getParameter(AttributeNames.PARAM_TOOL_CONTENT_ID); + mcService.setDefineLater(strToolContentID, true); + + return readDatabaseData(mcAuthoringForm, request, ToolAccessMode.TEACHER); + } + + /** + * Common method for "unspecified" and "defineLater" + */ + private String readDatabaseData(McAuthoringForm mcAuthoringForm, HttpServletRequest request, ToolAccessMode mode) { + SessionMap sessionMap = new SessionMap<>(); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + String sessionMapId = sessionMap.getSessionID(); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + String contentFolderID = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); + sessionMap.put(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + String strToolContentID = request.getParameter(AttributeNames.PARAM_TOOL_CONTENT_ID); + sessionMap.put(AttributeNames.PARAM_TOOL_CONTENT_ID, strToolContentID); + sessionMap.put(AttributeNames.ATTR_MODE, mode); + + if ((strToolContentID == null) || (strToolContentID.equals(""))) { + return "McErrorBox"; + } + + McContent mcContent = mcService.getMcContent(new Long(strToolContentID)); + + // if mcContent does not exist, try to use default content instead. + if (mcContent == null) { + long defaultContentID = mcService.getToolDefaultContentIdBySignature(McAppConstants.TOOL_SIGNATURE); + mcContent = mcService.getMcContent(new Long(defaultContentID)); + mcContent = McContent.newInstance(mcContent, new Long(strToolContentID)); + } + + // prepare form + mcAuthoringForm.setSln(mcContent.isShowReport() ? "1" : "0"); + mcAuthoringForm.setQuestionsSequenced(mcContent.isQuestionsSequenced() ? "1" : "0"); + mcAuthoringForm.setRandomize(mcContent.isRandomize() ? "1" : "0"); + mcAuthoringForm.setDisplayAnswersFeedback( + mcContent.isDisplayAnswers() ? "answers" : mcContent.isDisplayFeedbackOnly() ? "feedback" : "none"); + mcAuthoringForm.setShowMarks(mcContent.isShowMarks() ? "1" : "0"); + mcAuthoringForm.setUseSelectLeaderToolOuput(mcContent.isUseSelectLeaderToolOuput() ? "1" : "0"); + mcAuthoringForm.setPrefixAnswersWithLetters(mcContent.isPrefixAnswersWithLetters() ? "1" : "0"); + mcAuthoringForm.setRetries(mcContent.isRetries() ? "1" : "0"); + mcAuthoringForm.setPassmark("" + mcContent.getPassMark()); + mcAuthoringForm.setReflect(mcContent.isReflect() ? "1" : "0"); + mcAuthoringForm.setReflectionSubject(mcContent.getReflectionSubject()); + mcAuthoringForm.setTitle(mcContent.getTitle()); + mcAuthoringForm.setInstructions(mcContent.getInstructions()); + mcAuthoringForm.setEnableConfidenceLevels(mcContent.isEnableConfidenceLevels()); + + List questionDtos = AuthoringUtil.buildDefaultQuestions(mcContent); + sessionMap.put(McAppConstants.QUESTION_DTOS, questionDtos); + + List listDeletedQuestionDTOs = new ArrayList<>(); + sessionMap.put(McAppConstants.LIST_DELETED_QUESTION_DTOS, listDeletedQuestionDTOs); + + boolean isMcContentAttempted = mcContent.getUid() == null ? false + : mcService.isMcContentAttempted(mcContent.getMcContentId()); + sessionMap.put(McAppConstants.ATTR_IS_AUTHORING_RESTRICTED, isMcContentAttempted && mode.isTeacher()); + + return "authoring/AuthoringTabsHolder"; + } + + /** + * submits content into the tool database + */ + @RequestMapping(path = "/submitAllContent", method = RequestMethod.POST) + public String submitAllContent(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + String sessionMapId = mcAuthoringForm.getHttpSessionID(); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + ToolAccessMode mode = (ToolAccessMode) sessionMap.get(AttributeNames.ATTR_MODE); + + Long toolContentID = Long.parseLong((String) sessionMap.get(AttributeNames.PARAM_TOOL_CONTENT_ID)); + List questionDTOs = (List) sessionMap.get(McAppConstants.QUESTION_DTOS); + McContent mcContent = mcService.getMcContent(toolContentID); + List deletedQuestionDTOs = (List) sessionMap + .get(McAppConstants.LIST_DELETED_QUESTION_DTOS); + + if (questionDTOs.isEmpty()) { + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + errorMap.add("GLOBAL", messageService.getMessage("questions.none.submitted")); + request.setAttribute("errorMap", errorMap); + AuthoringController.logger.debug("errors saved: " + errorMap); + return "authoring/AuthoringTabsHolder"; + } + + //recalculate results in case content is edited from monitoring and it's been already attempted by a student + boolean isAuthoringRestricted = (boolean) sessionMap.get(McAppConstants.ATTR_IS_AUTHORING_RESTRICTED); + if (isAuthoringRestricted) { + Set oldQuestions = mcContent.getMcQueContents(); + mcService.releaseQuestionsFromCache(mcContent); + + // audit log the teacher has started editing activity in monitor + mcService.auditLogStartEditingActivityInMonitor(toolContentID); + + // recalculate User Answers + mcService.recalculateUserAnswers(mcContent, oldQuestions, questionDTOs); + } + + // remove deleted questions + for (McQuestionDTO deletedQuestionDTO : deletedQuestionDTOs) { + McQueContent removeableQuestion = mcService.getQuestionByUid(deletedQuestionDTO.getUid()); + if (removeableQuestion != null) { + // Set attempts = removeableQuestion.getMcUsrAttempts(); + // Iterator iter = attempts.iterator(); + // while (iter.hasNext()) { + // McUsrAttempt attempt = iter.next(); + // iter.remove(); + // } + // mcService.updateQuestion(removeableQuestion); + mcContent.getMcQueContents().remove(removeableQuestion); + mcService.removeMcQueContent(removeableQuestion); + } + } + + // store content + mcContent = AuthoringUtil.saveOrUpdateMcContent(mcService, request, mode, mcContent, toolContentID, + questionDTOs); + + // store questions + mcContent = mcService.createQuestions(questionDTOs, mcContent); + + if (mcContent != null) { + // sorts the questions by the display order + List sortedQuestions = mcService.getAllQuestionsSorted(mcContent.getUid().longValue()); + int displayOrder = 1; + for (McQueContent question : sortedQuestions) { + McQueContent existingQuestion = mcService.getQuestionByUid(question.getUid()); + existingQuestion.setDisplayOrder(new Integer(displayOrder)); + mcService.saveOrUpdateMcQueContent(existingQuestion); + displayOrder++; + } + } + + request.setAttribute(CommonConstants.LAMS_AUTHORING_SUCCESS_FLAG, Boolean.TRUE); + return "authoring/AuthoringTabsHolder"; + } + + /** + * opens up an new screen within the current page for editing a questionDescription + */ + @RequestMapping("/editQuestionBox") + public String editQuestionBox(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request) { + + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + List questionDtos = (List) sessionMap.get(McAppConstants.QUESTION_DTOS); + + Integer questionIndex = WebUtil.readIntParam(request, "questionIndex", true); + + McQuestionDTO questionDto = null; + //editing existing questionDescription + if (questionIndex != null) { + mcAuthoringForm.setQuestionIndex(questionIndex); + + //find according questionDto + for (McQuestionDTO questionDtoIter : questionDtos) { + Integer displayOrder = questionDtoIter.getDisplayOrder(); + + if ((displayOrder != null) && displayOrder.equals(questionIndex)) { + questionDto = questionDtoIter; + break; + } + } + + //adding new questionDescription + } else { + // prepare questionDescription for adding new questionDescription page + questionDto = new McQuestionDTO(); + + String contentFolderID = FileUtil.generateUniqueContentFolderID(); + questionDto.setContentFolderId(contentFolderID); + + List newOptions = new ArrayList<>(); + McOptionDTO newOption1 = new McOptionDTO(); + newOption1.setCorrect("Correct"); + McOptionDTO newOption2 = new McOptionDTO(); + newOptions.add(newOption1); + newOptions.add(newOption2); + questionDto.setOptionDtos(newOptions); + } + sessionMap.put(McAppConstants.QUESTION_DTO, questionDto); + + return "authoring/editQuestionBox"; + } + + /** + * removes a questionDescription from the questions map + */ + @SuppressWarnings("unchecked") + @RequestMapping("/removeQuestion") + public String removeQuestion(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request) { + + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + Integer questionIndexToDelete = WebUtil.readIntParam(request, "questionIndex"); + mcAuthoringForm.setQuestionIndex(questionIndexToDelete); + + List questionDtos = (List) sessionMap.get(McAppConstants.QUESTION_DTOS); + + //exclude Question with questionIndex From List + List tempQuestionDtos = new LinkedList<>(); + int queIndex = 0; + for (McQuestionDTO questionDto : questionDtos) { + + String name = questionDto.getName(); + Integer displayOrder = questionDto.getDisplayOrder(); + if ((name != null) && !name.isEmpty()) { + + if (!displayOrder.equals(questionIndexToDelete)) { + ++queIndex; + questionDto.setDisplayOrder(queIndex); + tempQuestionDtos.add(questionDto); + + } else { + List deletedQuestionDTOs = (List) sessionMap + .get(McAppConstants.LIST_DELETED_QUESTION_DTOS); + deletedQuestionDTOs.add(questionDto); + sessionMap.put(McAppConstants.LIST_DELETED_QUESTION_DTOS, deletedQuestionDTOs); + } + } + } + questionDtos = tempQuestionDtos; + sessionMap.put(McAppConstants.QUESTION_DTOS, questionDtos); + + return "authoring/itemlist"; + } + + /** + * moves a questionDescription down in the list + */ + @RequestMapping("/moveQuestionDown") + public String moveQuestionDown(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request) { + + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + Integer questionIndex = WebUtil.readIntParam(request, "questionIndex"); + mcAuthoringForm.setQuestionIndex(questionIndex); + + List questionDTOs = (List) sessionMap.get(McAppConstants.QUESTION_DTOS); + questionDTOs = AuthoringController.swapQuestions(questionDTOs, questionIndex, "down"); + questionDTOs = AuthoringController.reorderQuestionDtos(questionDTOs); + sessionMap.put(McAppConstants.QUESTION_DTOS, questionDTOs); + + return "authoring/itemlist"; + } + + @RequestMapping("/moveQuestionUp") + public String moveQuestionUp(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request) { + + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + Integer questionIndex = WebUtil.readIntParam(request, "questionIndex"); + mcAuthoringForm.setQuestionIndex(questionIndex); + + List questionDTOs = (List) sessionMap.get(McAppConstants.QUESTION_DTOS); + questionDTOs = AuthoringController.swapQuestions(questionDTOs, questionIndex, "up"); + questionDTOs = AuthoringController.reorderQuestionDtos(questionDTOs); + sessionMap.put(McAppConstants.QUESTION_DTOS, questionDTOs); + + return "authoring/itemlist"; + } + + /* + * swappes McQuestionDTO questions in the list. Auxiliary method for moveQuestionDown() and moveQuestionUp() + */ + private static List swapQuestions(List questionDTOs, Integer originalQuestionIndex, + String direction) { + + int replacedQuestionIndex = direction.equals("down") ? originalQuestionIndex + 1 : originalQuestionIndex - 1; + + McQuestionDTO mainQuestion = questionDTOs.get(originalQuestionIndex - 1); + McQuestionDTO replacedQuestion = questionDTOs.get(replacedQuestionIndex - 1); + if ((mainQuestion == null) || (replacedQuestion == null)) { + return questionDTOs; + } + + List questionDtos = new LinkedList<>(); + + Iterator iter = questionDTOs.iterator(); + while (iter.hasNext()) { + McQuestionDTO questionDto = iter.next(); + McQuestionDTO tempQuestion = null; + + if ((!questionDto.getDisplayOrder().equals(originalQuestionIndex)) + && !questionDto.getDisplayOrder().equals(replacedQuestionIndex)) { + // normal copy + tempQuestion = questionDto; + + } else if (questionDto.getDisplayOrder().equals(originalQuestionIndex)) { + // move type 1 + tempQuestion = replacedQuestion; + + } else if (questionDto.getDisplayOrder().equals(replacedQuestionIndex)) { + // move type 2 + tempQuestion = mainQuestion; + } + + questionDtos.add(tempQuestion); + } + + return questionDtos; + } + + /* + * Auxiliary method for moveQuestionDown() and moveQuestionUp() + */ + private static List reorderQuestionDtos(List questionDTOs) { + List tempQuestionDtos = new LinkedList<>(); + + int queIndex = 0; + Iterator iter = questionDTOs.iterator(); + while (iter.hasNext()) { + McQuestionDTO questionDto = iter.next(); + + String name = questionDto.getName(); + String description = questionDto.getDescription(); + String feedback = questionDto.getFeedback(); + String mark = questionDto.getMark(); + + List optionDtos = questionDto.getOptionDtos(); + if ((name != null) && (!name.equals(""))) { + ++queIndex; + + questionDto.setName(name); + questionDto.setDescription(description); + questionDto.setDisplayOrder(queIndex); + questionDto.setFeedback(feedback); + questionDto.setOptionDtos(optionDtos); + questionDto.setMark(mark); + tempQuestionDtos.add(questionDto); + } + } + + return tempQuestionDtos; + } + + /** + * Adds multiple QbQuestions, imported from QTI + */ + @RequestMapping(value = "/importQbQuestions", method = RequestMethod.POST) + private String importQbQuestions(HttpServletRequest request, @RequestParam String sessionMapId, + @RequestParam String qbQuestionUids) { + // get a list of QB question UIDs and add each of them to the activity + for (String qbQuestionUid : qbQuestionUids.split(",")) { + if (StringUtils.isNotBlank(qbQuestionUid)) { + importQbQuestion(request, sessionMapId, Long.valueOf(qbQuestionUid)); + } + } + return "authoring/itemlist"; + } + + /** + * Adds QbQuestion, selected in the questionDescription bank, to the current questionDescription list. + */ + @SuppressWarnings("unchecked") + @RequestMapping(value = "/importQbQuestion", method = RequestMethod.POST) + private String importQbQuestion(HttpServletRequest request, @RequestParam String sessionMapId, + @RequestParam Long qbQuestionUid) { + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + List questionDtos = (List) sessionMap.get(McAppConstants.QUESTION_DTOS); + + //check whether this QB question is a duplicate + for (McQuestionDTO questionDto : questionDtos) { + if (qbQuestionUid.equals(questionDto.getQbQuestionUid())) { + //let jsp know it's a duplicate + return "forward:/authoring/showDuplicateQuestionError.do"; + } + } + + //get QbQuestion from DB + QbQuestion qbQuestion = qbService.getQuestionByUid(qbQuestionUid); + + //find max displayOrder + int maxDisplayOrder = 0; + for (McQuestionDTO questionDto : questionDtos) { + int displayOrder = questionDto.getDisplayOrder(); + if (displayOrder > maxDisplayOrder) { + maxDisplayOrder = displayOrder; + } + } + + // build candidate dtos + List optionDtos = new LinkedList<>(); + for (QbOption option : qbQuestion.getQbOptions()) { + McOptionDTO optionDTO = new McOptionDTO(option); + optionDtos.add(optionDTO); + } + + //create new McQuestionDTO and assign imported qbQuestion to it + McQuestionDTO questionDto = new McQuestionDTO(); + questionDto.setName(qbQuestion.getName()); + questionDto.setDescription(qbQuestion.getDescription()); + questionDto.setQbQuestionUid(qbQuestionUid); + questionDto.setFeedback(qbQuestion.getFeedback()); + questionDto.setDisplayOrder(maxDisplayOrder + 1); + questionDto.setOptionDtos(optionDtos); + questionDto.setMark(qbQuestion.getMaxMark() == null ? "1" : String.valueOf(qbQuestion.getMaxMark())); + questionDto.setQbQuestionModified(IQbService.QUESTION_MODIFIED_NONE); + questionDtos.add(questionDto); + + return "authoring/itemlist"; + } + + /** + * Shows "This question has already been added" error message in a browser. + */ + @RequestMapping("/showDuplicateQuestionError") + @ResponseBody + public String showDuplicateQuestionError(HttpServletResponse response) throws IOException { + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("isDuplicated", true); + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + @SuppressWarnings("unchecked") + @RequestMapping("/saveQuestion") + public String saveQuestion(@ModelAttribute McAuthoringForm mcAuthoringForm, HttpServletRequest request) { + + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + String mark = request.getParameter("mark"); + + List options = AuthoringController.repopulateOptionDTOs(request, false); + + //remove blank options + List optionsWithoutEmptyOnes = new LinkedList<>(); + for (McOptionDTO optionDTO : options) { + String optionText = optionDTO.getCandidateAnswer(); + if ((optionText != null) && (optionText.length() > 0)) { + optionsWithoutEmptyOnes.add(optionDTO); + } + } + options = optionsWithoutEmptyOnes; + + List questionDTOs = (List) sessionMap.get(McAppConstants.QUESTION_DTOS); + + String name = request.getParameter("name"); + String description = request.getParameter("description"); + String feedback = request.getParameter("feedback"); + String contentFolderId = request.getParameter("contentFolderId"); + Integer questionIndex = WebUtil.readIntParam(request, "questionIndex", true); + Long qbQuestionUid = WebUtil.readLongParam(request, "qbQuestionUid", true); + mcAuthoringForm.setQuestionIndex(questionIndex); + + if ((name != null) && (name.length() > 0)) { + // adding new questionDescription + if (questionIndex == null) { + + //finding max displayOrder + int maxDisplayOrder = 0; + for (McQuestionDTO questionDTO : questionDTOs) { + int displayOrder = questionDTO.getDisplayOrder(); + if (displayOrder > maxDisplayOrder) { + maxDisplayOrder = displayOrder; + } + } + + McQuestionDTO questionDto = new McQuestionDTO(); + questionDto.setName(name); + questionDto.setDescription(description); + questionDto.setQbQuestionUid(qbQuestionUid); + questionDto.setFeedback(feedback); + questionDto.setDisplayOrder(maxDisplayOrder + 1); + questionDto.setOptionDtos(options); + questionDto.setMark(mark); + questionDto.setContentFolderId(contentFolderId); + questionDto.setQbQuestionModified(IQbService.QUESTION_MODIFIED_ID_BUMP); + request.setAttribute("qbQuestionModified", questionDto.getQbQuestionModified()); + + questionDTOs.add(questionDto); + + // updating existing questionDescription + } else { + McQuestionDTO questionDto = null; + for (McQuestionDTO questionDtoIter : questionDTOs) { + Integer displayOrder = questionDtoIter.getDisplayOrder(); + + if ((displayOrder != null) && displayOrder.equals(questionIndex)) { + questionDto = questionDtoIter; + break; + } + } + + questionDto.setName(name); + questionDto.setDescription(description); + questionDto.setQbQuestionUid(qbQuestionUid); + questionDto.setFeedback(feedback); + questionDto.setDisplayOrder(questionIndex); + questionDto.setOptionDtos(options); + questionDto.setMark(mark); + questionDto.setContentFolderId(contentFolderId); + questionDto.setQbQuestionModified(mcService.isQbQuestionModified(questionDto)); + request.setAttribute("qbQuestionModified", questionDto.getQbQuestionModified()); + } + } else { + // entry blank, not adding + } + + request.setAttribute(McAppConstants.QUESTION_DTOS, questionDTOs); + sessionMap.put(McAppConstants.QUESTION_DTOS, questionDTOs); + + return "authoring/itemlist"; + } + + @RequestMapping("/moveCandidateUp") + public String moveCandidateUp(HttpServletRequest request) { + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + String candidateIndex = request.getParameter("candidateIndex"); + request.setAttribute("candidateIndex", candidateIndex); + + List optionDtos = AuthoringController.repopulateOptionDTOs(request, false); + + //moveAddedCandidateUp + McQuestionDTO questionDto = (McQuestionDTO) sessionMap.get(McAppConstants.QUESTION_DTO); + List listCandidates = new LinkedList<>(); + listCandidates = AuthoringController.swapOptions(optionDtos, candidateIndex, "up"); + questionDto.setOptionDtos(listCandidates); + sessionMap.put(McAppConstants.QUESTION_DTO, questionDto); + + return "authoring/candidateAnswersList"; + } + + @RequestMapping("/moveCandidateDown") + public String moveCandidateDown(HttpServletRequest request) { + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + String candidateIndex = request.getParameter("candidateIndex"); + request.setAttribute("candidateIndex", candidateIndex); + + List optionDtos = AuthoringController.repopulateOptionDTOs(request, false); + + //moveAddedCandidateDown + McQuestionDTO questionDto = (McQuestionDTO) sessionMap.get(McAppConstants.QUESTION_DTO); + List swapedOptions = new LinkedList<>(); + swapedOptions = AuthoringController.swapOptions(optionDtos, candidateIndex, "down"); + questionDto.setOptionDtos(swapedOptions); + sessionMap.put(McAppConstants.QUESTION_DTO, questionDto); + + return "authoring/candidateAnswersList"; + } + + /* + * swaps options in the list + */ + private static List swapOptions(List optionDtos, String optionIndex, String direction) { + + int intOptionIndex = new Integer(optionIndex).intValue(); + int intOriginalOptionIndex = intOptionIndex; + + int replacedOptionIndex = 0; + if (direction.equals("down")) { + replacedOptionIndex = ++intOptionIndex; + } else { + replacedOptionIndex = --intOptionIndex; + } + + McOptionDTO mainOption = AuthoringUtil.getOptionAtDisplayOrder(optionDtos, intOriginalOptionIndex); + McOptionDTO replacedOption = AuthoringUtil.getOptionAtDisplayOrder(optionDtos, replacedOptionIndex); + if ((mainOption == null) || (replacedOption == null)) { + return optionDtos; + } + + List newOptionDtos = new LinkedList<>(); + + int queIndex = 1; + for (McOptionDTO option : optionDtos) { + + McOptionDTO tempOption = new McOptionDTO(); + if ((!new Integer(queIndex).toString().equals(new Integer(intOriginalOptionIndex).toString())) + && !new Integer(queIndex).toString().equals(new Integer(replacedOptionIndex).toString())) { + // normal copy + tempOption = option; + } else if (new Integer(queIndex).toString().equals(new Integer(intOriginalOptionIndex).toString())) { + // move type 1 + tempOption = replacedOption; + } else if (new Integer(queIndex).toString().equals(new Integer(replacedOptionIndex).toString())) { + // move type 2 + tempOption = mainOption; + } + + newOptionDtos.add(tempOption); + queIndex++; + } + + return newOptionDtos; + } + + @RequestMapping("/removeCandidate") + public String removeCandidate(HttpServletRequest request) { + + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + String candidateIndexToRemove = request.getParameter("candidateIndex"); + request.setAttribute("candidateIndex", candidateIndexToRemove); + + // removeAddedCandidate + McQuestionDTO questionDto = (McQuestionDTO) sessionMap.get(McAppConstants.QUESTION_DTO); + + List optionDtos = AuthoringController.repopulateOptionDTOs(request, false); + List listFinalCandidatesDTO = new LinkedList<>(); + int caIndex = 0; + for (McOptionDTO mcOptionDTO : optionDtos) { + caIndex++; + + if (caIndex != new Integer(candidateIndexToRemove).intValue()) { + listFinalCandidatesDTO.add(mcOptionDTO); + } + } + + questionDto.setOptionDtos(listFinalCandidatesDTO); + sessionMap.put(McAppConstants.QUESTION_DTO, questionDto); + + return "authoring/candidateAnswersList"; + } + + @RequestMapping("/newCandidateBox") + public String newCandidateBox(HttpServletRequest request) { + + String sessionMapId = request.getParameter(McAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + request.setAttribute(McAppConstants.ATTR_SESSION_MAP_ID, sessionMapId); + + String candidateIndex = request.getParameter("candidateIndex"); + request.setAttribute("candidateIndex", candidateIndex); + + List optionDtos = AuthoringController.repopulateOptionDTOs(request, true); + + //newAddedCandidateBox + McQuestionDTO questionDto = (McQuestionDTO) sessionMap.get(McAppConstants.QUESTION_DTO); + questionDto.setOptionDtos(optionDtos); + sessionMap.put(McAppConstants.QUESTION_DTO, questionDto); + + return "authoring/candidateAnswersList"; + } + + /** + * repopulateOptionsBox + */ + private static List repopulateOptionDTOs(HttpServletRequest request, boolean isAddBlankOptions) { + + String correct = request.getParameter("correct"); + + /* check this logic again */ + int intCorrect = 0; + if (correct != null) { + intCorrect = new Integer(correct).intValue(); + } + + List optionDtos = new LinkedList<>(); + for (int i = 0; i < McAppConstants.MAX_OPTION_COUNT; i++) { + String optionText = request.getParameter("ca" + i); + Long qbOptionUid = WebUtil.readLongParam(request, "qbOptionUid" + i, true); + + String isCorrect = "Incorrect"; + + if (i == intCorrect) { + isCorrect = "Correct"; + } + + if (optionText != null) { + McOptionDTO optionDTO = new McOptionDTO(); + optionDTO.setCandidateAnswer(optionText); + optionDTO.setCorrect(isCorrect); + optionDTO.setQbOptionUid(qbOptionUid); + optionDtos.add(optionDTO); + } + } + + if (isAddBlankOptions) { + McOptionDTO optionDTO = new McOptionDTO(); + optionDTO.setCandidateAnswer(""); + optionDTO.setCorrect("Incorrect"); + optionDtos.add(optionDTO); + } + + return optionDtos; + } + +} Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/LearningController.java =================================================================== diff -u --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/LearningController.java (revision 0) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/LearningController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,885 @@ +/*************************************************************************** + * 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 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.tool.mc.web.controller; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; +import org.lamsfoundation.lams.qb.model.QbOption; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.exception.DataMissingException; +import org.lamsfoundation.lams.tool.exception.ToolException; +import org.lamsfoundation.lams.tool.mc.McAppConstants; +import org.lamsfoundation.lams.tool.mc.dto.AnswerDTO; +import org.lamsfoundation.lams.tool.mc.dto.McGeneralLearnerFlowDTO; +import org.lamsfoundation.lams.tool.mc.model.McContent; +import org.lamsfoundation.lams.tool.mc.model.McQueContent; +import org.lamsfoundation.lams.tool.mc.model.McQueUsr; +import org.lamsfoundation.lams.tool.mc.model.McSession; +import org.lamsfoundation.lams.tool.mc.model.McUsrAttempt; +import org.lamsfoundation.lams.tool.mc.service.IMcService; +import org.lamsfoundation.lams.tool.mc.util.LearningUtil; +import org.lamsfoundation.lams.tool.mc.util.McComparator; +import org.lamsfoundation.lams.tool.mc.web.form.McLearningForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.DateUtil; +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.lamsfoundation.lams.web.util.SessionMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Ozgur Demirtas + */ +@Controller +@RequestMapping("/learning") +public class LearningController { + + private static Logger logger = Logger.getLogger(LearningController.class.getName()); + + @Autowired + private IMcService mcService; + + @Autowired + @Qualifier("lamcMessageService") + private MessageService messageService; + + @RequestMapping("/displayMc") + public String displayMc(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) throws IOException { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + mcLearningForm.setToolSessionID(toolSessionID); + + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + + String toolContentId = mcSession.getMcContent().getMcContentId().toString(); + mcLearningForm.setToolContentID(toolContentId); + + LearningUtil.saveFormRequestData(request, mcLearningForm); + + if (mcLearningForm.getNextQuestionSelected() != null && !mcLearningForm.getNextQuestionSelected().equals("")) { + mcLearningForm.resetParameters(); + return getNextOptions(mcLearningForm, request, response); + } + if (mcLearningForm.getContinueOptionsCombined() != null) { + return continueOptionsCombined(mcLearningForm, request, response); + } else if (mcLearningForm.getNextOptions() != null) { + return getNextOptions(mcLearningForm, request, response); + } else if (mcLearningForm.getRedoQuestions() != null) { + return redoQuestions(mcLearningForm, request, response); + } else if (mcLearningForm.getViewAnswers() != null) { + return viewAnswers(mcLearningForm, request, response); + } else if (mcLearningForm.getSubmitReflection() != null) { + return submitReflection(mcLearningForm, request, response); + } else if (mcLearningForm.getForwardtoReflection() != null) { + return forwardtoReflection(mcLearningForm, request, response); + } else if (mcLearningForm.getLearnerFinished() != null) { + return endLearning(mcLearningForm, request, response); + } + + return "learning/AnswersContent"; + } + + @RequestMapping("/learning") + public String execute(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) { + + mcLearningForm.setMcService(mcService); + mcLearningForm.setPassMarkApplicable(new Boolean(false).toString()); + mcLearningForm.setUserOverPassMark(new Boolean(false).toString()); + + SessionMap sessionMap = new SessionMap<>(); + List sequentialCheckedCa = new LinkedList<>(); + sessionMap.put(McAppConstants.QUESTION_AND_CANDIDATE_ANSWERS_KEY, sequentialCheckedCa); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + mcLearningForm.setHttpSessionID(sessionMap.getSessionID()); + + sessionMap.put(McAppConstants.CONFIG_KEY_HIDE_TITLES, + Boolean.valueOf(mcService.getConfigValue(McAppConstants.CONFIG_KEY_HIDE_TITLES))); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + mcLearningForm.setToolSessionID(toolSessionID); + + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + + /* + * by now, we made sure that the passed tool session id exists in the db as a new record Make sure we can + * retrieve it and the relavent content + */ + if (mcSession == null) { + return "error"; + } + + /* + * find out what content this tool session is referring to get the content for this tool session Each passed + * tool session id points to a particular content. Many to one mapping. + */ + McContent mcContent = mcSession.getMcContent(); + if (mcContent == null) { + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + errorMap.add("GLOBAL", messageService.getMessage("error.content.doesNotExist")); + request.setAttribute("errorMap", errorMap); + return "error"; + } + + String mode = request.getParameter(McAppConstants.MODE); + request.setAttribute(AttributeNames.ATTR_MODE, mode); + + McQueUsr user = null; + if ((mode != null) && mode.equals(ToolAccessMode.TEACHER.toString())) { + // monitoring mode - user is specified in URL + // user may be null if the user was force completed. + user = getSpecifiedUser(toolSessionID, WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID, false)); + } else { + user = getCurrentUser(toolSessionID); + } + Long userID = user.getQueUsrId(); + mcLearningForm.setUserID(userID); + + /* + * Is there a deadline set? + */ + Date submissionDeadline = mcContent.getSubmissionDeadline(); + if (submissionDeadline != null) { + + HttpSession ss = SessionManager.getSession(); + UserDTO learnerDto = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone learnerTimeZone = learnerDto.getTimeZone(); + Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, submissionDeadline); + Date currentLearnerDate = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, new Date()); + request.setAttribute("submissionDeadline", submissionDeadline); + + // calculate whether submission deadline has passed, and if so forward to "submissionDeadline" + if (currentLearnerDate.after(tzSubmissionDeadline)) { + return "learning/submissionDeadline"; + } + } + + mcLearningForm.setToolContentID(mcContent.getMcContentId().toString()); + + McGeneralLearnerFlowDTO mcGeneralLearnerFlowDTO = LearningUtil.buildMcGeneralLearnerFlowDTO(mcContent); + mcGeneralLearnerFlowDTO.setTotalCountReached(new Boolean(false).toString()); + mcGeneralLearnerFlowDTO.setQuestionIndex(new Integer(1)); + + Boolean displayAnswers = mcContent.isDisplayAnswers(); + mcGeneralLearnerFlowDTO.setDisplayAnswers(displayAnswers.toString()); + mcGeneralLearnerFlowDTO.setDisplayFeedbackOnly(((Boolean) mcContent.isDisplayFeedbackOnly()).toString()); + mcGeneralLearnerFlowDTO.setReflection(new Boolean(mcContent.isReflect()).toString()); + // String reflectionSubject = McUtils.replaceNewLines(mcContent.getReflectionSubject()); + mcGeneralLearnerFlowDTO.setReflectionSubject(mcContent.getReflectionSubject()); + + NotebookEntry notebookEntry = mcService.getEntry(new Long(toolSessionID), CoreNotebookConstants.NOTEBOOK_TOOL, + McAppConstants.TOOL_SIGNATURE, userID.intValue()); + + if (notebookEntry != null) { + // String notebookEntryPresentable = McUtils.replaceNewLines(notebookEntry.getEntry()); + mcGeneralLearnerFlowDTO.setNotebookEntry(notebookEntry.getEntry()); + } + request.setAttribute(McAppConstants.MC_GENERAL_LEARNER_FLOW_DTO, mcGeneralLearnerFlowDTO); + + List learnerAnswerDtos = mcService.getAnswersFromDatabase(mcContent, user); + request.setAttribute(McAppConstants.LEARNER_ANSWER_DTOS, learnerAnswerDtos); + // should we show the marks for each questionDescription - we show the marks if any of the questions + // have a mark > 1. + Boolean showMarks = LearningUtil.isShowMarksOnQuestion(learnerAnswerDtos); + mcGeneralLearnerFlowDTO.setShowMarks(showMarks.toString()); + + /* find out if the content is being modified at the moment. */ + boolean isDefineLater = mcContent.isDefineLater(); + if (isDefineLater == true) { + return "learning/defineLater"; + } + + McQueUsr groupLeader = null; + if (mcContent.isUseSelectLeaderToolOuput()) { + groupLeader = mcService.checkLeaderSelectToolForSessionLeader(user, new Long(toolSessionID)); + + // forwards to the leaderSelection page + if (groupLeader == null && !mode.equals(ToolAccessMode.TEACHER.toString())) { + + Set groupUsers = mcSession.getMcQueUsers();// mcService.getUsersBySession(new + // Long(toolSessionID).longValue()); + request.setAttribute(McAppConstants.ATTR_GROUP_USERS, groupUsers); + request.setAttribute(McAppConstants.TOOL_SESSION_ID, toolSessionID); + request.setAttribute(McAppConstants.ATTR_CONTENT, mcContent); + + return "learning/WaitForLeader"; + } + + // check if leader has submitted all answers + if (groupLeader.isResponseFinalised() && !mode.equals(ToolAccessMode.TEACHER.toString())) { + + // in case user joins the lesson after leader has answers some answers already - we need to make sure + // he has the same scratches as leader + mcService.copyAnswersFromLeader(user, groupLeader); + + user.setResponseFinalised(true); + mcService.updateMcQueUsr(user); + } + } + + sessionMap.put(McAppConstants.ATTR_GROUP_LEADER, groupLeader); + boolean isUserLeader = mcSession.isUserGroupLeader(user); + sessionMap.put(McAppConstants.ATTR_IS_USER_LEADER, isUserLeader); + sessionMap.put(AttributeNames.ATTR_MODE, mode); + sessionMap.put(McAppConstants.ATTR_CONTENT, mcContent); + request.setAttribute("sessionMapID", sessionMap.getSessionID()); + + /* user has already submitted response once OR it's a monitor - go to viewAnswers page. */ + if (user.isResponseFinalised() || mode.equals("teacher")) { + + String redirect = "redirect:/learning/viewAnswers.do"; + redirect = WebUtil.appendParameterToURL(redirect, AttributeNames.PARAM_TOOL_SESSION_ID, toolSessionID); + redirect = WebUtil.appendParameterToURL(redirect, "userID", userID.toString()); + redirect = WebUtil.appendParameterToURL(redirect, McAppConstants.MODE, mode); + redirect = WebUtil.appendParameterToURL(redirect, "httpSessionID", sessionMap.getSessionID()); + return redirect; + } + + return "learning/AnswersContent"; + } + + @RequestMapping("/endLearning") + public String endLearning(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) throws IOException { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + mcLearningForm.setToolSessionID(toolSessionID); + + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + + String toolContentId = mcSession.getMcContent().getMcContentId().toString(); + mcLearningForm.setToolContentID(toolContentId); + + LearningUtil.saveFormRequestData(request, mcLearningForm); + // requested learner finished, the learner should be directed to next activity + + HttpSession ss = SessionManager.getSession(); + UserDTO userDto = (UserDTO) ss.getAttribute(AttributeNames.USER); + + String nextUrl = null; + try { + nextUrl = mcService.leaveToolSession(new Long(toolSessionID), userDto.getUserID().longValue()); + } catch (DataMissingException e) { + LearningController.logger.error("failure getting nextUrl: " + e); + return "learning/AnswersContent"; + } catch (ToolException e) { + LearningController.logger.error("failure getting nextUrl: " + e); + return "learning/AnswersContent"; + } catch (Exception e) { + LearningController.logger.error("unknown exception getting nextUrl: " + e); + return "learning/AnswersContent"; + } + + response.sendRedirect(nextUrl); + + return null; + } + + protected List buildAnswerDtos(List answers, Map confidenceLevels, + McContent content, HttpServletRequest request) { + + List answerDtos = new LinkedList<>(); + + for (McQueContent question : content.getMcQueContents()) { + String questionUid = question.getUid().toString(); + int questionMark = question.getMark().intValue(); + + AnswerDTO answerDto = new AnswerDTO(); + answerDto.setQuestionName(question.getName()); + answerDto.setQuestionDescription(question.getDescription()); + answerDto.setDisplayOrder(String.valueOf(question.getDisplayOrder())); + answerDto.setQuestionUid(question.getUid()); + answerDto.setFeedback(question.getFeedback() != null ? question.getFeedback() : ""); + + //search for according answer + QbOption answerOption = null; + for (String answer : answers) { + int hyphenPosition = answer.indexOf("-"); + String answeredQuestionUid = answer.substring(0, hyphenPosition); + + if (questionUid.equals(answeredQuestionUid)) { + String answeredOptionUid = answer.substring(hyphenPosition + 1); + answerOption = question.getOptionByUID(new Long(answeredOptionUid)); + answerDto.setAnswerOption(answerOption); + break; + } + } + + boolean isCorrect = (answerOption != null) && answerOption.isCorrect(); + answerDto.setAttemptCorrect(isCorrect); + if (isCorrect) { + answerDto.setFeedbackCorrect(question.getFeedback()); + answerDto.setMark(questionMark); + } else { + answerDto.setFeedbackIncorrect(question.getFeedback()); + answerDto.setMark(0); + } + + // handle confidence levels + if (content.isEnableConfidenceLevels()) { + String wantedKey = "confidenceLevel" + question.getUid(); + Integer confidenceLevel = confidenceLevels.get(wantedKey); + if (confidenceLevel != null) { + answerDto.setConfidenceLevel(confidenceLevel); + } + } + + answerDtos.add(answerDto); + } + + return answerDtos; + } + + /** + * responses to learner when they answer all the questions on a single page + */ + @RequestMapping("/continueOptionsCombined") + public String continueOptionsCombined(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) { + + String httpSessionID = mcLearningForm.getHttpSessionID(); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(httpSessionID); + request.getSession().setAttribute(httpSessionID, sessionMap); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + McSession session = mcService.getMcSessionById(new Long(toolSessionID)); + String toolContentId = session.getMcContent().getMcContentId().toString(); + McContent content = mcService.getMcContent(new Long(toolContentId)); + + List answers = LearningController.parseLearnerAnswers(mcLearningForm, request, + content.isQuestionsSequenced()); + + Map learnerConfidenceLevels = null; + if (content.isEnableConfidenceLevels()) { + learnerConfidenceLevels = parseLearnerConfidenceLevels(mcLearningForm, request, + content.isQuestionsSequenced()); + } + + if (content.isQuestionsSequenced()) { + sessionMap.put(McAppConstants.QUESTION_AND_CANDIDATE_ANSWERS_KEY, answers); + if (content.isEnableConfidenceLevels()) { + sessionMap.put(McAppConstants.CONFIDENCE_LEVELS_KEY, learnerConfidenceLevels); + } + } + + mcLearningForm.resetCa(request); + + McQueUsr user = getCurrentUser(toolSessionID); + + //prohibit users from submitting answers after response is finalized but Resubmit button is not pressed (e.g. using 2 browsers) + if (user.isResponseFinalised()) { + return viewAnswers(mcLearningForm, request, response); + } + + /* process the answers */ + List answerDtos = buildAnswerDtos(answers, learnerConfidenceLevels, content, request); + mcService.saveUserAttempt(user, answerDtos); + + //calculate total learner mark + int learnerMark = 0; + for (AnswerDTO answerDto : answerDtos) { + learnerMark += answerDto.getMark(); + } + + Integer numberOfAttempts = user.getNumberOfAttempts() + 1; + user.setNumberOfAttempts(numberOfAttempts); + user.setLastAttemptTotalMark(learnerMark); + user.setResponseFinalised(true); + mcService.updateMcQueUsr(user); + + //if this is a leader finishes, complete all non-leaders as well, also copy leader results to them + if (content.isUseSelectLeaderToolOuput() && session.isUserGroupLeader(user)) { + session.getMcQueUsers().forEach(sessionUser -> { + //finish non-leader + sessionUser.setResponseFinalised(true); + mcService.updateMcQueUsr(user); + + //copy answers from leader to non-leaders + mcService.copyAnswersFromLeader(sessionUser, session.getGroupLeader()); + }); + } + + return viewAnswers(mcLearningForm, request, response); + } + + /** + * takes the learner to the next set of questions + */ + @RequestMapping("/getNextOptions") + public String getNextOptions(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + String toolContentId = mcSession.getMcContent().getMcContentId().toString(); + McContent mcContent = mcService.getMcContent(new Long(toolContentId)); + + McQueUsr user = getCurrentUser(toolSessionID); + + String httpSessionID = mcLearningForm.getHttpSessionID(); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(httpSessionID); + + //prohibit users from submitting answers after response is finalized but Resubmit button is not pressed (e.g. using 2 browsers) + if (user.isResponseFinalised()) { + return viewAnswers(mcLearningForm, request, response); + } + + //parse learner input + List answers = LearningController.parseLearnerAnswers(mcLearningForm, request, + mcContent.isQuestionsSequenced()); + sessionMap.put(McAppConstants.QUESTION_AND_CANDIDATE_ANSWERS_KEY, answers); + + Map learnerConfidenceLevels = null; + if (mcContent.isEnableConfidenceLevels()) { + learnerConfidenceLevels = parseLearnerConfidenceLevels(mcLearningForm, request, + mcContent.isQuestionsSequenced()); + sessionMap.put(McAppConstants.CONFIDENCE_LEVELS_KEY, learnerConfidenceLevels); + } + + //save user attempt + List answerDtos = buildAnswerDtos(answers, learnerConfidenceLevels, mcContent, request); + mcService.saveUserAttempt(user, answerDtos); + + List learnerAnswerDtos = mcService.getAnswersFromDatabase(mcContent, user); + request.setAttribute(McAppConstants.LEARNER_ANSWER_DTOS, learnerAnswerDtos); + + McGeneralLearnerFlowDTO mcGeneralLearnerFlowDTO = LearningUtil.buildMcGeneralLearnerFlowDTO(mcContent); + + Integer totalQuestionCount = mcGeneralLearnerFlowDTO.getTotalQuestionCount(); + Integer questionIndex = mcLearningForm.getQuestionIndex(); + if (totalQuestionCount.equals(questionIndex)) { + mcGeneralLearnerFlowDTO.setTotalCountReached(new Boolean(true).toString()); + } + + mcGeneralLearnerFlowDTO.setReflection(new Boolean(mcContent.isReflect()).toString()); + mcGeneralLearnerFlowDTO.setReflectionSubject(mcContent.getReflectionSubject()); + mcGeneralLearnerFlowDTO.setRetries(new Boolean(mcContent.isRetries()).toString()); + mcGeneralLearnerFlowDTO.setTotalMarksPossible(mcContent.getTotalMarksPossible()); + mcGeneralLearnerFlowDTO.setQuestionIndex(questionIndex); + request.setAttribute(McAppConstants.MC_GENERAL_LEARNER_FLOW_DTO, mcGeneralLearnerFlowDTO); + request.setAttribute(AttributeNames.ATTR_IS_LAST_ACTIVITY, mcService.isLastActivity(new Long(toolSessionID))); + request.setAttribute("sessionMapID", sessionMap.getSessionID()); + + return "learning/AnswersContent"; + } + + /** + * allows the learner to view their answer history + */ + @RequestMapping("/viewAnswers") + public String viewAnswers(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + + String toolContentId = mcSession.getMcContent().getMcContentId().toString(); + McContent mcContent = mcService.getMcContent(new Long(toolContentId)); + + String sessionMapID = mcLearningForm.getHttpSessionID(); + request.setAttribute("sessionMapID", sessionMapID); + + McGeneralLearnerFlowDTO mcGeneralLearnerFlowDTO = LearningUtil.buildMcGeneralLearnerFlowDTO(mcContent); + + Map mapQuestionsUidContent = new TreeMap(new McComparator()); + List questions = mcService.getQuestionsByContentUid(mcContent.getUid()); + + Iterator listIterator = questions.iterator(); + Long mapIndex = new Long(1); + while (listIterator.hasNext()) { + McQueContent mcQueContent = (McQueContent) listIterator.next(); + mapQuestionsUidContent.put(mapIndex.toString(), mcQueContent.getUid()); + mapIndex = new Long(mapIndex.longValue() + 1); + } + + //builds a map to hold all the candidate answers for all the questions by accessing the db + Map mapStartupGeneralOptionsContent = new TreeMap(new McComparator()); + Iterator itMap = mapQuestionsUidContent.entrySet().iterator(); + mapIndex = new Long(1); + while (itMap.hasNext()) { + Map.Entry pairs = (Map.Entry) itMap.next(); + String currentQuestionUid = pairs.getValue().toString(); + List listQuestionOptions = mcService.findOptionsByQuestionUid(new Long(currentQuestionUid)); + + //builds a questions map from questions list + Map mapOptsContent = new TreeMap<>(); + Iterator iter = listQuestionOptions.iterator(); + int mapIndex2 = 0; + while (iter.hasNext()) { + QbOption option = iter.next(); + String stringIndex = mcContent.isPrefixAnswersWithLetters() + ? LearningUtil.formatPrefixLetter(mapIndex2++) + : Integer.toString(++mapIndex2); + mapOptsContent.put(stringIndex, option.getName()); + } + + mapStartupGeneralOptionsContent.put(mapIndex.toString(), mapOptsContent); + mapIndex = new Long(mapIndex.longValue() + 1); + } + mcGeneralLearnerFlowDTO.setMapGeneralOptionsContent(mapStartupGeneralOptionsContent); + mcGeneralLearnerFlowDTO.setQuestions(questions); + + //rebuildFeedbackMapfromDB + Map mapFeedbackContent = new TreeMap(new McComparator()); + int i = 1; + for (McQueContent question : questions) { + String feedback = question.getFeedback(); + mapFeedbackContent.put(String.valueOf(i++), feedback); + } + mcGeneralLearnerFlowDTO.setMapFeedbackContent(mapFeedbackContent); + + McQueUsr user = getSpecifiedUser(toolSessionID, mcLearningForm.getUserID().intValue()); + + Long toolContentUID = mcContent.getUid(); + + //create attemptMap for displaying on jsp + Map attemptMap = new TreeMap(new McComparator()); + for (i = 1; i <= mcContent.getMcQueContents().size(); i++) { + McQueContent question = mcService.getQuestionByDisplayOrder(i, toolContentUID); + + McUsrAttempt userAttempt = mcService.getUserAttemptByQuestion(user.getUid(), question.getUid()); + + if (userAttempt != null) { + attemptMap.put(new Integer(i).toString(), userAttempt); + } + } + mcGeneralLearnerFlowDTO.setAttemptMap(attemptMap); + mcGeneralLearnerFlowDTO.setReflection(new Boolean(mcContent.isReflect()).toString()); + mcGeneralLearnerFlowDTO.setReflectionSubject(mcContent.getReflectionSubject()); + + NotebookEntry notebookEntry = mcService.getEntry(new Long(toolSessionID), CoreNotebookConstants.NOTEBOOK_TOOL, + McAppConstants.TOOL_SIGNATURE, new Integer(user.getQueUsrId().intValue())); + request.setAttribute("notebookEntry", notebookEntry); + if (notebookEntry != null) { + mcGeneralLearnerFlowDTO.setNotebookEntry(notebookEntry.getEntry()); + } + + mcGeneralLearnerFlowDTO.setRetries(new Boolean(mcContent.isRetries()).toString()); + mcGeneralLearnerFlowDTO.setPassMarkApplicable(new Boolean(mcContent.isPassMarkApplicable()).toString()); + mcGeneralLearnerFlowDTO.setUserOverPassMark(new Boolean(user.isLastAttemptMarkPassed()).toString()); + mcGeneralLearnerFlowDTO.setTotalMarksPossible(mcContent.getTotalMarksPossible()); + mcGeneralLearnerFlowDTO.setShowMarks(new Boolean(mcContent.isShowMarks()).toString()); + mcGeneralLearnerFlowDTO.setDisplayAnswers(new Boolean(mcContent.isDisplayAnswers()).toString()); + mcGeneralLearnerFlowDTO.setDisplayFeedbackOnly(((Boolean) mcContent.isDisplayFeedbackOnly()).toString()); + mcGeneralLearnerFlowDTO.setLearnerMark(user.getLastAttemptTotalMark()); + + Object[] markStatistics = null; + if (mcContent.isShowMarks()) { + markStatistics = mcService.getMarkStatistics(mcSession); + } + if (markStatistics != null) { + mcGeneralLearnerFlowDTO + .setLowestMark(markStatistics[0] != null ? ((Float) markStatistics[0]).intValue() : 0); + mcGeneralLearnerFlowDTO + .setAverageMark(markStatistics[1] != null ? ((Float) markStatistics[1]).intValue() : 0); + mcGeneralLearnerFlowDTO.setTopMark(markStatistics[2] != null ? ((Float) markStatistics[2]).intValue() : 0); + } else { + mcGeneralLearnerFlowDTO.setLowestMark(0); + mcGeneralLearnerFlowDTO.setAverageMark(0); + mcGeneralLearnerFlowDTO.setTopMark(0); + } + + request.setAttribute(McAppConstants.MC_GENERAL_LEARNER_FLOW_DTO, mcGeneralLearnerFlowDTO); + request.setAttribute(AttributeNames.ATTR_IS_LAST_ACTIVITY, mcService.isLastActivity(new Long(toolSessionID))); + return "learning/ViewAnswers"; + } + + @RequestMapping("/redoQuestions") + public String redoQuestions(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + String toolContentId = mcSession.getMcContent().getMcContentId().toString(); + McContent mcContent = mcService.getMcContent(new Long(toolContentId)); + McQueUsr mcQueUsr = getCurrentUser(toolSessionID); + + //in order to track whether redo button is pressed store this info + mcQueUsr.setResponseFinalised(false); + mcService.updateMcQueUsr(mcQueUsr); + + //clear sessionMap + String httpSessionID = mcLearningForm.getHttpSessionID(); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(httpSessionID); + List sequentialCheckedCa = new LinkedList<>(); + sessionMap.put(McAppConstants.QUESTION_AND_CANDIDATE_ANSWERS_KEY, sequentialCheckedCa); + + List learnerAnswerDtos = mcService.getAnswersFromDatabase(mcContent, mcQueUsr); + request.setAttribute(McAppConstants.LEARNER_ANSWER_DTOS, learnerAnswerDtos); + + McGeneralLearnerFlowDTO mcGeneralLearnerFlowDTO = LearningUtil.buildMcGeneralLearnerFlowDTO(mcContent); + mcGeneralLearnerFlowDTO.setQuestionIndex(new Integer(1)); + mcGeneralLearnerFlowDTO.setReflection(new Boolean(mcContent.isReflect()).toString()); + mcGeneralLearnerFlowDTO.setReflectionSubject(mcContent.getReflectionSubject()); + mcGeneralLearnerFlowDTO.setRetries(new Boolean(mcContent.isRetries()).toString()); + + String passMarkApplicable = new Boolean(mcContent.isPassMarkApplicable()).toString(); + mcGeneralLearnerFlowDTO.setPassMarkApplicable(passMarkApplicable); + mcLearningForm.setPassMarkApplicable(passMarkApplicable); + + String userOverPassMark = Boolean.FALSE.toString(); + mcGeneralLearnerFlowDTO.setUserOverPassMark(userOverPassMark); + mcLearningForm.setUserOverPassMark(userOverPassMark); + + mcGeneralLearnerFlowDTO.setTotalMarksPossible(mcContent.getTotalMarksPossible()); + + // should we show the marks for each questionDescription - we show the marks if any of the questions + // have a mark > 1. + Boolean showMarks = LearningUtil.isShowMarksOnQuestion(learnerAnswerDtos); + mcGeneralLearnerFlowDTO.setShowMarks(showMarks.toString()); + + request.setAttribute("sessionMapID", mcLearningForm.getHttpSessionID()); + + request.setAttribute(McAppConstants.MC_GENERAL_LEARNER_FLOW_DTO, mcGeneralLearnerFlowDTO); + return "learning/AnswersContent"; + } + + /** + * checks Leader Progress + */ + @RequestMapping("/checkLeaderProgress") + @ResponseBody + public String checkLeaderProgress(HttpServletRequest request, HttpServletResponse response) throws IOException { + + Long toolSessionId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); + + McSession session = mcService.getMcSessionById(toolSessionId); + McQueUsr leader = session.getGroupLeader(); + + boolean isLeaderResponseFinalized = leader.isResponseFinalised(); + + ObjectNode ObjectNode = JsonNodeFactory.instance.objectNode(); + ObjectNode.put("isLeaderResponseFinalized", isLeaderResponseFinalized); + response.setContentType("application/json;charset=UTF-8"); + return ObjectNode.toString(); + } + + @RequestMapping("/submitReflection") + public String submitReflection(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) throws IOException { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + mcLearningForm.setToolSessionID(toolSessionID); + + Long userID = mcLearningForm.getUserID(); + + String reflectionEntry = request.getParameter(McAppConstants.ENTRY_TEXT); + NotebookEntry notebookEntry = mcService.getEntry(new Long(toolSessionID), CoreNotebookConstants.NOTEBOOK_TOOL, + McAppConstants.TOOL_SIGNATURE, userID.intValue()); + + if (notebookEntry != null) { + notebookEntry.setEntry(reflectionEntry); + notebookEntry.setLastModified(new Date()); + mcService.updateEntry(notebookEntry); + } else { + mcService.createNotebookEntry(new Long(toolSessionID), CoreNotebookConstants.NOTEBOOK_TOOL, + McAppConstants.TOOL_SIGNATURE, userID.intValue(), reflectionEntry); + } + + return endLearning(mcLearningForm, request, response); + } + + @RequestMapping("/forwardtoReflection") + public String forwardtoReflection(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + + McContent mcContent = mcSession.getMcContent(); + + McGeneralLearnerFlowDTO mcGeneralLearnerFlowDTO = new McGeneralLearnerFlowDTO(); + mcGeneralLearnerFlowDTO.setActivityTitle(mcContent.getTitle()); + mcGeneralLearnerFlowDTO.setReflectionSubject(mcContent.getReflectionSubject()); + + Long userID = mcLearningForm.getUserID(); + + // attempt getting notebookEntry + NotebookEntry notebookEntry = mcService.getEntry(new Long(toolSessionID), CoreNotebookConstants.NOTEBOOK_TOOL, + McAppConstants.TOOL_SIGNATURE, userID.intValue()); + + if (notebookEntry != null) { + String notebookEntryPresentable = notebookEntry.getEntry(); + mcLearningForm.setEntryText(notebookEntryPresentable); + + } + + request.setAttribute(McAppConstants.MC_GENERAL_LEARNER_FLOW_DTO, mcGeneralLearnerFlowDTO); + + return "learning/Notebook"; + } + + /** + * auto saves responses + */ + @RequestMapping("/autoSaveAnswers") + public String autoSaveAnswers(@ModelAttribute McLearningForm mcLearningForm, HttpServletRequest request, + HttpServletResponse response) { + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionID)); + McContent mcContent = mcSession.getMcContent(); + + Long userID = mcLearningForm.getUserID(); + McQueUsr user = mcService.getMcUserBySession(userID, mcSession.getUid()); + + //prohibit users from autosaving answers after response is finalized but Resubmit button is not pressed (e.g. using 2 browsers) + if (user.isResponseFinalised()) { + return null; + } + + List answers = LearningController.parseLearnerAnswers(mcLearningForm, request, + mcContent.isQuestionsSequenced()); + Map learnerConfidenceLevels = null; + if (mcContent.isEnableConfidenceLevels()) { + learnerConfidenceLevels = parseLearnerConfidenceLevels(mcLearningForm, request, + mcContent.isQuestionsSequenced()); + } + + List answerDtos = buildAnswerDtos(answers, learnerConfidenceLevels, mcContent, request); + mcService.saveUserAttempt(user, answerDtos); + + return null; + } + + private static List parseLearnerAnswers(McLearningForm mcLearningForm, HttpServletRequest request, + boolean isQuestionsSequenced) { + String httpSessionID = mcLearningForm.getHttpSessionID(); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(httpSessionID); + + List answers = new LinkedList<>(); + if (isQuestionsSequenced) { + + List previousAnswers = (List) sessionMap + .get(McAppConstants.QUESTION_AND_CANDIDATE_ANSWERS_KEY); + answers.addAll(previousAnswers); + + /* checkedCa refers to candidate answers */ + String[] checkedCa = mcLearningForm.getCheckedCa(); + + if (checkedCa != null) { + for (int i = 0; i < checkedCa.length; i++) { + String currentCa = checkedCa[i]; + answers.add(currentCa); + } + } + + } else { + Map parameters = request.getParameterMap(); + Iterator iter = parameters.keySet().iterator(); + while (iter.hasNext()) { + String key = iter.next(); + if (key.startsWith("checkedCa")) { + String currentCheckedCa = request.getParameter(key); + if (currentCheckedCa != null) { + answers.add(currentCheckedCa); + } + } + } + } + + return answers; + } + + private Map parseLearnerConfidenceLevels(McLearningForm mcLearningForm, HttpServletRequest request, + boolean isQuestionsSequenced) { + String httpSessionID = mcLearningForm.getHttpSessionID(); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(httpSessionID); + + Map confidenceLevels = new HashMap<>(); + if (isQuestionsSequenced) { + Map previousConfidenceLevels = (Map) sessionMap + .get(McAppConstants.CONFIDENCE_LEVELS_KEY); + if (previousConfidenceLevels != null) { + confidenceLevels.putAll(previousConfidenceLevels); + } + } + + Map parameters = request.getParameterMap(); + Iterator iter = parameters.keySet().iterator(); + while (iter.hasNext()) { + String key = iter.next(); + if (key.startsWith("confidenceLevel")) { + confidenceLevels.put(key, WebUtil.readIntParam(request, key)); + } + } + return confidenceLevels; + } + + private McQueUsr getCurrentUser(String toolSessionId) { + + // get back login user DTO + HttpSession ss = SessionManager.getSession(); + UserDTO toolUser = (UserDTO) ss.getAttribute(AttributeNames.USER); + Long userId = new Long(toolUser.getUserID().longValue()); + + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionId)); + McQueUsr qaUser = mcService.getMcUserBySession(userId, mcSession.getUid()); + if (qaUser == null) { + qaUser = mcService.createMcUser(new Long(toolSessionId)); + } + + return mcService.getMcUserBySession(userId, mcSession.getUid()); + } + + private McQueUsr getSpecifiedUser(String toolSessionId, Integer userId) { + McSession mcSession = mcService.getMcSessionById(new Long(toolSessionId)); + McQueUsr qaUser = mcService.getMcUserBySession(new Long(userId.intValue()), mcSession.getUid()); + if (qaUser == null) { + logger.error("Unable to find specified user for Q&A activity. Screens are likely to fail. SessionId=" + + new Long(toolSessionId) + " UserId=" + userId); + } + return qaUser; + } +} Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/MonitoringController.java =================================================================== diff -u --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/MonitoringController.java (revision 0) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/MonitoringController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,559 @@ +/*************************************************************************** + * 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 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.tool.mc.web.controller; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; +import org.lamsfoundation.lams.qb.dto.QbStatsActivityDTO; +import org.lamsfoundation.lams.qb.service.IQbService; +import org.lamsfoundation.lams.tool.mc.McAppConstants; +import org.lamsfoundation.lams.tool.mc.dto.LeaderResultsDTO; +import org.lamsfoundation.lams.tool.mc.dto.McGeneralLearnerFlowDTO; +import org.lamsfoundation.lams.tool.mc.dto.McGeneralMonitoringDTO; +import org.lamsfoundation.lams.tool.mc.dto.McUserMarkDTO; +import org.lamsfoundation.lams.tool.mc.dto.ReflectionDTO; +import org.lamsfoundation.lams.tool.mc.dto.SessionDTO; +import org.lamsfoundation.lams.tool.mc.model.McContent; +import org.lamsfoundation.lams.tool.mc.model.McOptsContent; +import org.lamsfoundation.lams.tool.mc.model.McQueContent; +import org.lamsfoundation.lams.tool.mc.model.McQueUsr; +import org.lamsfoundation.lams.tool.mc.model.McSession; +import org.lamsfoundation.lams.tool.mc.model.McUsrAttempt; +import org.lamsfoundation.lams.tool.mc.service.IMcService; +import org.lamsfoundation.lams.tool.mc.util.McSessionComparator; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.JsonUtil; +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.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.util.HtmlUtils; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * * @author Ozgur Demirtas + */ +@Controller +@RequestMapping("/monitoring") +public class MonitoringController { + + private static Logger logger = Logger.getLogger(MonitoringController.class.getName()); + + @Autowired + private IMcService mcService; + + @Autowired + @Qualifier("lamcMessageService") + private MessageService messageService; + + @Autowired + private IQbService qbService; + + @RequestMapping("/monitoring") + public String execute(HttpServletRequest request) { + + McGeneralMonitoringDTO mcGeneralMonitoringDTO = new McGeneralMonitoringDTO(); + + String toolContentID = null; + try { + Long toolContentIDLong = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID, false); + toolContentID = toolContentIDLong.toString(); + } catch (IllegalArgumentException e) { + logger.error("Unable to start monitoring as tool content id is missing"); + throw (e); + } + + McContent mcContent = mcService.getMcContent(new Long(toolContentID)); + mcGeneralMonitoringDTO.setToolContentID(toolContentID); + mcGeneralMonitoringDTO.setActivityTitle(mcContent.getTitle()); + mcGeneralMonitoringDTO.setActivityInstructions(mcContent.getInstructions()); + + //set up sessionDTOs list + Set sessions = new TreeSet<>(new McSessionComparator()); + sessions.addAll(mcContent.getMcSessions()); + List sessionDtos = new LinkedList<>(); + for (McSession session : sessions) { + SessionDTO sessionDto = new SessionDTO(); + sessionDto.setSessionId(session.getMcSessionId()); + sessionDto.setSessionName(session.getSession_name()); + + sessionDtos.add(sessionDto); + } + request.setAttribute(McAppConstants.SESSION_DTOS, sessionDtos); + + // setting up the advanced summary + + request.setAttribute(McAppConstants.ATTR_CONTENT, mcContent); + request.setAttribute("questionsSequenced", mcContent.isQuestionsSequenced()); + request.setAttribute("enableConfidenceLevels", mcContent.isEnableConfidenceLevels()); + request.setAttribute("showMarks", mcContent.isShowMarks()); + request.setAttribute("useSelectLeaderToolOuput", mcContent.isUseSelectLeaderToolOuput()); + request.setAttribute("prefixAnswersWithLetters", mcContent.isPrefixAnswersWithLetters()); + request.setAttribute("randomize", mcContent.isRandomize()); + request.setAttribute("displayAnswers", mcContent.isDisplayAnswers()); + request.setAttribute("displayFeedbackOnly", mcContent.isDisplayFeedbackOnly()); + request.setAttribute("retries", mcContent.isRetries()); + request.setAttribute("reflect", mcContent.isReflect()); + request.setAttribute("reflectionSubject", mcContent.getReflectionSubject()); + request.setAttribute("passMark", mcContent.getPassMark()); + request.setAttribute("toolContentID", mcContent.getMcContentId()); + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, + WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID)); + + // setting up Date and time restriction in activities + HttpSession ss = SessionManager.getSession(); + Date submissionDeadline = mcContent.getSubmissionDeadline(); + if (submissionDeadline != null) { + UserDTO learnerDto = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone learnerTimeZone = learnerDto.getTimeZone(); + Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, submissionDeadline); + request.setAttribute(McAppConstants.ATTR_SUBMISSION_DEADLINE, tzSubmissionDeadline.getTime()); + // use the unconverted time, as convertToStringForJSON() does the timezone conversion if needed + request.setAttribute(McAppConstants.ATTR_SUBMISSION_DEADLINE_DATESTRING, + DateUtil.convertToStringForJSON(submissionDeadline, request.getLocale())); + } + + boolean isGroupedActivity = mcService.isGroupedActivity(new Long(mcContent.getMcContentId())); + request.setAttribute("isGroupedActivity", isGroupedActivity); + + /* this section is needed for Edit Activity screen, from here... */ + + mcGeneralMonitoringDTO.setDisplayAnswers(new Boolean(mcContent.isDisplayAnswers()).toString()); + mcGeneralMonitoringDTO.setDisplayFeedbackOnly(new Boolean(mcContent.isDisplayFeedbackOnly()).toString()); + + List reflectionsContainerDTO = mcService.getReflectionList(mcContent, null); + request.setAttribute(McAppConstants.REFLECTIONS_CONTAINER_DTO, reflectionsContainerDTO); + + request.setAttribute(McAppConstants.MC_GENERAL_MONITORING_DTO, mcGeneralMonitoringDTO); + + //count users + int countSessionComplete = 0; + int countAllUsers = 0; + Iterator iteratorSession = mcContent.getMcSessions().iterator(); + while (iteratorSession.hasNext()) { + McSession mcSession = (McSession) iteratorSession.next(); + + if (mcSession != null) { + + if (mcSession.getSessionStatus().equals(McAppConstants.COMPLETED)) { + countSessionComplete++; + } + countAllUsers += mcSession.getMcQueUsers().size(); + } + } + mcGeneralMonitoringDTO.setCountAllUsers(new Integer(countAllUsers)); + mcGeneralMonitoringDTO.setCountSessionComplete(new Integer(countSessionComplete)); + + return "monitoring/MonitoringMaincontent"; + } + + /** + * Turn on displayAnswers + */ + @RequestMapping("/displayAnswers") + public String displayAnswers(HttpServletRequest request) { + + String strToolContentID = request.getParameter(AttributeNames.PARAM_TOOL_CONTENT_ID); + String contentFolderID = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); + + McContent mcContent = mcService.getMcContent(new Long(strToolContentID)); + mcContent.setDisplayAnswers(new Boolean(true)); + mcContent.setDisplayFeedbackOnly(new Boolean(false)); + mcService.updateMc(mcContent); + + // use redirect to prevent resubmition of the same request + String redirect = "redirect:/monitoring/monitoring.do"; + redirect = WebUtil.appendParameterToURL(redirect, McAppConstants.TOOL_CONTENT_ID, strToolContentID); + redirect = WebUtil.appendParameterToURL(redirect, AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + return redirect; + } + + /** + * Turn on displayFeedbackOnly + */ + @RequestMapping("/displayFeedbackOnly") + public String displayFeedbackOnly(HttpServletRequest request) { + + String strToolContentID = request.getParameter(AttributeNames.PARAM_TOOL_CONTENT_ID); + String contentFolderID = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); + + McContent mcContent = mcService.getMcContent(new Long(strToolContentID)); + mcContent.setDisplayFeedbackOnly(new Boolean(true)); + mcService.updateMc(mcContent); + + // use redirect to prevent resubmition of the same request + String redirect = "redirect:/monitoring/monitoring.do"; + redirect = WebUtil.appendParameterToURL(redirect, McAppConstants.TOOL_CONTENT_ID, strToolContentID); + redirect = WebUtil.appendParameterToURL(redirect, AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + return redirect; + } + + /** + * allows viewing users reflection data + */ + @RequestMapping("/openNotebook") + public String openNotebook(HttpServletRequest request) { + + String userId = request.getParameter("userId"); + String userName = request.getParameter("userName"); + String sessionId = request.getParameter("sessionId"); + NotebookEntry notebookEntry = mcService.getEntry(new Long(sessionId), CoreNotebookConstants.NOTEBOOK_TOOL, + McAppConstants.TOOL_SIGNATURE, new Integer(userId)); + + McGeneralLearnerFlowDTO mcGeneralLearnerFlowDTO = new McGeneralLearnerFlowDTO(); + if (notebookEntry != null) { + // String notebookEntryPresentable = McUtils.replaceNewLines(notebookEntry.getEntry()); + mcGeneralLearnerFlowDTO.setNotebookEntry(notebookEntry.getEntry()); + mcGeneralLearnerFlowDTO.setUserName(userName); + } + + request.setAttribute(McAppConstants.MC_GENERAL_LEARNER_FLOW_DTO, mcGeneralLearnerFlowDTO); + + return "monitoring/LearnerNotebook"; + } + + /** + * downloadMarks + */ + @RequestMapping(path = "/downloadMarks", method = RequestMethod.POST) + public String downloadMarks(HttpServletRequest request, HttpServletResponse response) throws IOException { + Long toolContentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID, false); + McContent mcContent = mcService.getMcContent(toolContentID); + + byte[] spreadsheet = null; + try { + spreadsheet = mcService.prepareSessionDataSpreadsheet(mcContent); + } catch (Exception e) { + logger.error("Error preparing spreadsheet: ", e); + request.setAttribute("errorName", messageService.getMessage("error.monitoring.spreadsheet.download")); + request.setAttribute("errorMessage", e); + return "error"; + } + + // 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); + + // construct download file response header + OutputStream out = response.getOutputStream(); + String fileName = "lams_mcq.xls"; + String mineType = "application/vnd.ms-excel"; + String header = "attachment; filename=\"" + fileName + "\";"; + response.setContentType(mineType); + response.setHeader("Content-Disposition", header); + + // write response + try { + out.write(spreadsheet); + out.flush(); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + } + } + + return null; + } + + /** + * Set Submission Deadline + */ + @RequestMapping(path = "/setSubmissionDeadline", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String setSubmissionDeadline(HttpServletRequest request) { + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + McContent mcContent = mcService.getMcContent(contentID); + + Long dateParameter = WebUtil.readLongParam(request, McAppConstants.ATTR_SUBMISSION_DEADLINE, true); + Date tzSubmissionDeadline = null; + String formattedDate = ""; + if (dateParameter != null) { + Date submissionDeadline = new Date(dateParameter); + HttpSession ss = SessionManager.getSession(); + org.lamsfoundation.lams.usermanagement.dto.UserDTO teacher = (org.lamsfoundation.lams.usermanagement.dto.UserDTO) ss + .getAttribute(AttributeNames.USER); + TimeZone teacherTimeZone = teacher.getTimeZone(); + tzSubmissionDeadline = DateUtil.convertFromTimeZoneToDefault(teacherTimeZone, submissionDeadline); + formattedDate = DateUtil.convertToStringForJSON(tzSubmissionDeadline, request.getLocale()); + } + mcContent.setSubmissionDeadline(tzSubmissionDeadline); + mcService.updateMc(mcContent); + + return formattedDate; + } + + /** + * Set tool's activityEvaluation + */ + @RequestMapping("/setActivityEvaluation") + @ResponseBody + public String setActivityEvaluation(HttpServletRequest request, HttpServletResponse response) throws IOException { + + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + String activityEvaluation = WebUtil.readStrParam(request, McAppConstants.ATTR_ACTIVITY_EVALUATION); + mcService.setActivityEvaluation(contentID, activityEvaluation); + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("success", "true"); + response.setContentType("application/json;charset=UTF-8"); + return responseJSON.toString(); + } + + /** + * Populate user jqgrid table on summary page. + */ + @RequestMapping("/userMasterDetail") + public String userMasterDetail(HttpServletRequest request) { + + Long userUid = WebUtil.readLongParam(request, McAppConstants.USER_UID); + McQueUsr user = mcService.getMcUserByUID(userUid); + List userAttempts = mcService.getFinalizedUserAttempts(user); + + // Escapes all characters that may brake JS code on assigning Java value to JS String variable (particularly + // escapes all quotes in the following way \"). + if (userAttempts != null) { + for (McUsrAttempt userAttempt : userAttempts) { + McQueContent question = userAttempt.getMcQueContent(); + McOptsContent option = userAttempt.getMcOptionsContent(); + + String description = question.getDescription(); + if (description != null) { + String escapedDescription = StringEscapeUtils.escapeJavaScript(description); + question.setEscapedDescription(escapedDescription); + } + + String optionText = option.getQbOption().getName(); + if (optionText != null) { + String escapedOptionText = StringEscapeUtils.escapeJavaScript(optionText); + option.setEscapedOptionText(escapedOptionText); + } + } + } + + request.setAttribute(McAppConstants.ATTR_CONTENT, user.getMcSession().getMcContent()); + request.setAttribute(McAppConstants.USER_ATTEMPTS, userAttempts); + request.setAttribute(McAppConstants.TOOL_SESSION_ID, user.getMcSession().getMcSessionId()); + return "monitoring/masterDetailLoadUp"; + } + + /** + * Return paged users for jqGrid. + */ + @RequestMapping("/getPagedUsers") + @ResponseBody + public String getPagedUsers(HttpServletRequest request, HttpServletResponse response) { + + Long sessionId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); + McSession session = mcService.getMcSessionById(sessionId); + //find group leader, if any + McQueUsr groupLeader = session.getGroupLeader(); + + // Getting the params passed in from the jqGrid + int page = WebUtil.readIntParam(request, AttributeNames.PARAM_PAGE); + int rowLimit = WebUtil.readIntParam(request, AttributeNames.PARAM_ROWS); + String sortOrder = WebUtil.readStrParam(request, AttributeNames.PARAM_SORD); + String sortBy = WebUtil.readStrParam(request, AttributeNames.PARAM_SIDX, true); + if (StringUtils.isEmpty(sortBy)) { + sortBy = "userName"; + } + String searchString = WebUtil.readStrParam(request, "userName", true); + + List userDtos = new ArrayList<>(); + int countVisitLogs = 0; + //in case of UseSelectLeaderToolOuput - display only one user + if (groupLeader != null) { + + Integer totalMark = groupLeader.getLastAttemptTotalMark(); + Long portraitId = mcService.getPortraitId(groupLeader.getQueUsrId()); + + McUserMarkDTO userDto = new McUserMarkDTO(); + userDto.setQueUsrId(groupLeader.getUid().toString()); + userDto.setUserId(groupLeader.getQueUsrId().toString()); + userDto.setFullName(groupLeader.getFullname()); + userDto.setTotalMark(totalMark != null ? totalMark.longValue() : null); + userDto.setPortraitId(portraitId == null ? null : portraitId.toString()); + userDtos.add(userDto); + countVisitLogs = 1; + + } else { + userDtos = mcService.getPagedUsersBySession(sessionId, page - 1, rowLimit, sortBy, sortOrder, searchString); + countVisitLogs = mcService.getCountPagedUsersBySession(sessionId, searchString); + } + + int totalPages = new Double( + Math.ceil(new Integer(countVisitLogs).doubleValue() / new Integer(rowLimit).doubleValue())).intValue(); + + ArrayNode rows = JsonNodeFactory.instance.arrayNode(); + int i = 1; + for (McUserMarkDTO userDto : userDtos) { + + ArrayNode visitLogData = JsonNodeFactory.instance.arrayNode(); + Long userUid = Long.parseLong(userDto.getQueUsrId()); + visitLogData.add(userUid); + visitLogData.add(userDto.getUserId()); + + String fullName = HtmlUtils.htmlEscape(userDto.getFullName()); + if (groupLeader != null && groupLeader.getUid().equals(userUid)) { + fullName += " (" + mcService.getLocalizedMessage("label.monitoring.group.leader") + ")"; + } + + visitLogData.add(fullName); + Long totalMark = (userDto.getTotalMark() == null) ? 0 : userDto.getTotalMark(); + visitLogData.add(totalMark); + + visitLogData.add(userDto.getPortraitId()); + + ObjectNode userRow = JsonNodeFactory.instance.objectNode(); + userRow.put("id", i++); + userRow.set("cell", visitLogData); + + rows.add(userRow); + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("total", totalPages); + responseJSON.put("page", page); + responseJSON.put("records", countVisitLogs); + responseJSON.set("rows", rows); + response.setContentType("application/json;charset=UTF-8"); + return responseJSON.toString(); + } + + @RequestMapping(path = "/saveUserMark", method = RequestMethod.POST) + public String saveUserMark(HttpServletRequest request) { + + if ((request.getParameter(McAppConstants.PARAM_NOT_A_NUMBER) == null) + && !StringUtils.isEmpty(request.getParameter(McAppConstants.PARAM_USER_ATTEMPT_UID))) { + + Long userAttemptUid = WebUtil.readLongParam(request, McAppConstants.PARAM_USER_ATTEMPT_UID); + Integer newGrade = Integer.valueOf(request.getParameter(McAppConstants.PARAM_GRADE)); + mcService.changeUserAttemptMark(userAttemptUid, newGrade); + } + + return null; + } + + /** + * Get the mark summary with data arranged in bands. Can be displayed graphically or in a table. + */ + @RequestMapping("/getMarkChartData") + @ResponseBody + public String getMarkChartData(HttpServletRequest request, HttpServletResponse response) throws IOException { + + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + McContent mcContent = mcService.getMcContent(contentID); + List results = null; + + if (mcContent != null) { + if (mcContent.isUseSelectLeaderToolOuput()) { + results = mcService.getMarksArrayForLeaders(contentID); + } else { + Long sessionID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); + results = mcService.getMarksArray(sessionID); + } + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + if (results != null) { + responseJSON.set("data", JsonUtil.readArray(results)); + } else { + responseJSON.set("data", JsonUtil.readArray(new Float[0])); + } + response.setContentType("application/json;charset=UTF-8"); + return responseJSON.toString(); + + } + + @RequestMapping("/statistic") + public String statistic(HttpServletRequest request) { + + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, contentID); + McContent mcContent = mcService.getMcContent(contentID); + if (mcContent != null) { + if (mcContent.isUseSelectLeaderToolOuput()) { + LeaderResultsDTO leaderDto = mcService.getLeaderResultsDTOForLeaders(contentID); + request.setAttribute("leaderDto", leaderDto); + } else { + List sessionDtos = mcService.getSessionDtos(contentID, true); + request.setAttribute("sessionDtos", sessionDtos); + } + request.setAttribute("useSelectLeaderToolOutput", mcContent.isUseSelectLeaderToolOuput()); + + List qbStats = new LinkedList<>(); + for (McQueContent question : mcContent.getMcQueContents()) { + QbStatsActivityDTO questionStats = qbService.getActivityStatsByContentId(mcContent.getMcContentId(), + question.getQbQuestion().getUid()); + questionStats.setQbQuestion(question.getQbQuestion()); + qbStats.add(questionStats); + } + request.setAttribute("qbStats", qbStats); + } + + // prepare toolOutputDefinitions and activityEvaluation + List toolOutputDefinitions = new ArrayList<>(); + toolOutputDefinitions.add(McAppConstants.OUTPUT_NAME_LEARNER_MARK); + toolOutputDefinitions.add(McAppConstants.OUTPUT_NAME_LEARNER_ALL_CORRECT); + String activityEvaluation = mcService.getActivityEvaluation(contentID); + request.setAttribute(McAppConstants.ATTR_TOOL_OUTPUT_DEFINITIONS, toolOutputDefinitions); + request.setAttribute(McAppConstants.ATTR_ACTIVITY_EVALUATION, activityEvaluation); + + return "monitoring/parts/statsPart"; + } + +} Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/PedagogicalPlannerController.java =================================================================== diff -u --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/PedagogicalPlannerController.java (revision 0) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/PedagogicalPlannerController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,182 @@ +/**************************************************************** + * 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 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.tool.mc.web.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.lamsfoundation.lams.qb.model.QbOption; +import org.lamsfoundation.lams.qb.model.QbQuestion; +import org.lamsfoundation.lams.tool.mc.McAppConstants; +import org.lamsfoundation.lams.tool.mc.dto.McOptionDTO; +import org.lamsfoundation.lams.tool.mc.model.McContent; +import org.lamsfoundation.lams.tool.mc.model.McQueContent; +import org.lamsfoundation.lams.tool.mc.service.IMcService; +import org.lamsfoundation.lams.tool.mc.web.form.McPedagogicalPlannerForm; +import org.lamsfoundation.lams.util.MessageService; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/pedagogicalPlanner") +public class PedagogicalPlannerController { + + @Autowired + private IMcService mcService; + + @Autowired + @Qualifier("lamcMessageService") + private MessageService messageService; + + protected String unspecified(@ModelAttribute McPedagogicalPlannerForm plannerForm, HttpServletRequest request) { + return initPedagogicalPlannerForm(plannerForm, request); + } + + @RequestMapping("/initPedagogicalPlannerForm") + public String initPedagogicalPlannerForm(@ModelAttribute McPedagogicalPlannerForm plannerForm, + HttpServletRequest request) { + + Long toolContentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + McContent mcContent = mcService.getMcContent(toolContentID); + plannerForm.fillForm(mcContent, mcService); + return "authoring/pedagogicalPlannerForm"; + } + + @RequestMapping("/saveOrUpdatePedagogicalPlannerForm") + public String saveOrUpdatePedagogicalPlannerForm(@ModelAttribute McPedagogicalPlannerForm plannerForm, + HttpServletRequest request) throws IOException { + + MultiValueMap errorMap = plannerForm.validate(request); + + if (errorMap.isEmpty()) { + McContent mcContent = mcService.getMcContent(plannerForm.getToolContentID()); + int questionIndex = 1; + String question = null; + + do { + question = plannerForm.getDescription(questionIndex - 1); + List candidateAnswerDTOList = plannerForm.extractCandidateAnswers(request, questionIndex); + boolean removeQuestion = true; + if (!StringUtils.isEmpty(question)) { + if (candidateAnswerDTOList != null) { + for (McOptionDTO answer : candidateAnswerDTOList) { + if (answer != null && !StringUtils.isEmpty(answer.getCandidateAnswer())) { + removeQuestion = false; + break; + } + } + } + } + if (removeQuestion) { + plannerForm.removeQuestion(questionIndex - 1); + } else { + if (questionIndex <= mcContent.getMcQueContents().size()) { + McQueContent mcQueContent = mcService.getQuestionByDisplayOrder(questionIndex, + mcContent.getUid()); + mcQueContent.setDescription(question); + int candidateAnswerDTOIndex = 0; + List candidateAnswers = mcQueContent.getQbQuestion().getQbOptions(); + Iterator candidateAnswerIter = candidateAnswers.iterator(); + while (candidateAnswerIter.hasNext()) { + QbOption candidateAnswer = candidateAnswerIter.next(); + if (candidateAnswerDTOIndex >= candidateAnswerDTOList.size()) { + candidateAnswerIter.remove(); + } else { + McOptionDTO answerDTO = candidateAnswerDTOList.get(candidateAnswerDTOIndex); + candidateAnswer.setCorrect(McAppConstants.CORRECT.equals(answerDTO.getCorrect())); + candidateAnswer.setName(answerDTO.getCandidateAnswer()); + mcService.updateQbOption(candidateAnswer); + } + candidateAnswerDTOIndex++; + } + mcService.saveOrUpdateMcQueContent(mcQueContent); + } else { + McQueContent mcQueContent = new McQueContent(); + mcQueContent.setQbQuestion(new QbQuestion()); + // TODO Set questionDescription ID and version + mcQueContent.setDisplayOrder(questionIndex); + mcQueContent.setMcContent(mcContent); + mcQueContent.setDescription(question); + mcQueContent.setMark(McAppConstants.QUESTION_DEFAULT_MARK); + List candidateAnswers = mcQueContent.getQbQuestion().getQbOptions(); + for (int candidateAnswerDTOIndex = 0; candidateAnswerDTOIndex < candidateAnswerDTOList + .size(); candidateAnswerDTOIndex++) { + McOptionDTO answerDTO = candidateAnswerDTOList.get(candidateAnswerDTOIndex); + QbOption qbOption = new QbOption(); + qbOption.setName(answerDTO.getCandidateAnswer()); + qbOption.setCorrect(McAppConstants.CORRECT.equals(answerDTO.getCorrect())); + qbOption.setDisplayOrder(candidateAnswerDTOIndex + 1); + candidateAnswers.add(qbOption); + } + mcService.saveOrUpdateMcQueContent(mcQueContent); + mcContent.getMcQueContents().add(mcQueContent); + } + questionIndex++; + } + } while (questionIndex <= plannerForm.getQuestionCount()); + for (; questionIndex <= mcContent.getMcQueContents().size(); questionIndex++) { + McQueContent mcQueContent = mcService.getQuestionByDisplayOrder(questionIndex, mcContent.getUid()); + mcContent.getMcQueContents().remove(mcQueContent); + mcService.removeMcQueContent(mcQueContent); + } + plannerForm.fillForm(mcContent, mcService); + } else { + request.setAttribute("errorMap", errorMap); + } + return "authoring/pedagogicalPlannerForm"; + } + + @RequestMapping("/createPedagogicalPlannerQuestion") + public String createPedagogicalPlannerQuestion(@ModelAttribute McPedagogicalPlannerForm plannerForm, + HttpServletRequest request) { + + int questionDisplayOrder = plannerForm.getQuestionCount().intValue() + 1; + plannerForm.setCandidateAnswerCount(new ArrayList(plannerForm.getQuestionCount())); + Map paramMap = request.getParameterMap(); + for (int questionIndex = 1; questionIndex < questionDisplayOrder; questionIndex++) { + String[] param = paramMap.get(McAppConstants.CANDIDATE_ANSWER_COUNT + questionIndex); + int count = NumberUtils.toInt(param[0]); + plannerForm.getCandidateAnswerCount().add(count); + } + plannerForm.setDescription(questionDisplayOrder - 1, ""); + plannerForm.getCandidateAnswerCount().add(McAppConstants.CANDIDATE_ANSWER_DEFAULT_COUNT); + plannerForm.setCorrect(questionDisplayOrder - 1, "1"); + + return "authoring/pedagogicalPlannerForm"; + } + +} \ No newline at end of file Index: lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/AuthoringConditionController.java =================================================================== diff -u --- lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/AuthoringConditionController.java (revision 0) +++ lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/AuthoringConditionController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,437 @@ +/**************************************************************** + * 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.tool.qa.web.controller; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.lamsfoundation.lams.learningdesign.TextSearchConditionComparator; +import org.lamsfoundation.lams.tool.qa.QaAppConstants; +import org.lamsfoundation.lams.tool.qa.model.QaCondition; +import org.lamsfoundation.lams.tool.qa.model.QaQueContent; +import org.lamsfoundation.lams.tool.qa.service.IQaService; +import org.lamsfoundation.lams.tool.qa.util.QaQuestionComparator; +import org.lamsfoundation.lams.tool.qa.web.form.QaConditionForm; +import org.lamsfoundation.lams.util.MessageService; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.util.SessionMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Auxiliary action in author mode. It contains operations with QaCondition. The + * rest of operations are located in QaAction action. + * + * @author Marcin Cieslak + * @see org.lamsfoundation.lams.tool.qa.web.controller.QaAction + */ +@Controller +@RequestMapping("/authoringConditions") +public class AuthoringConditionController { + + @Autowired + private IQaService qaService; + + @Autowired + @Qualifier("qaMessageService") + private MessageService messageService; + + /** + * Display empty page for a new condition. + * + * @param QaConditionForm + * @param request + * @return + */ + @RequestMapping("/newConditionInit") + private String newConditionInit(@ModelAttribute("QaConditionForm") QaConditionForm QaConditionForm, + HttpServletRequest request) { + + populateFormWithPossibleItems(QaConditionForm, request); + QaConditionForm.setOrderId(-1); + return "authoring/addCondition"; + } + + /** + * Display edit page for an existing condition. + * + * @param QaConditionForm + * @param request + * @return + */ + @RequestMapping("/editCondition") + private String editCondition(@ModelAttribute("QaConditionForm") QaConditionForm QaConditionForm, + HttpServletRequest request) { + + String sessionMapID = QaConditionForm.getSessionMapID(); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(sessionMapID); + + int orderId = NumberUtils.stringToInt(request.getParameter(QaAppConstants.PARAM_ORDER_ID), -1); + QaCondition condition = null; + if (orderId != -1) { + SortedSet conditionSet = getConditions(sessionMap); + List conditionList = new ArrayList<>(conditionSet); + condition = conditionList.get(orderId); + if (condition != null) { + populateConditionToForm(orderId, condition, QaConditionForm, request); + } + } + + populateFormWithPossibleItems(QaConditionForm, request); + return condition == null ? null : "authoring/addCondition"; + } + + /** + * This method will get necessary information from condition form and save + * or update into HttpSession condition list. Notice, this + * save is not persist them into database, just save + * HttpSession temporarily. Only they will be persist when + * the entire authoring page is being persisted. + * + * @param QaConditionForm + * @param request + * @return + * @throws ServletException + */ + @RequestMapping(path = "/saveOrUpdateCondition", method = RequestMethod.POST) + private String saveOrUpdateCondition(@ModelAttribute("QaConditionForm") QaConditionForm QaConditionForm, + HttpServletRequest request) { + + MultiValueMap errorMap = validateQaCondition(QaConditionForm, request); + + if (!errorMap.isEmpty()) { + populateFormWithPossibleItems(QaConditionForm, request); + request.setAttribute("errorMap", errorMap); + return "authoring/addCondition"; + } + + try { + extractFormToQaCondition(request, QaConditionForm); + } catch (Exception e) { + + errorMap.add("GLOBAL", messageService.getMessage("error.condition", new Object[] { e.getMessage() })); + if (!errorMap.isEmpty()) { + populateFormWithPossibleItems(QaConditionForm, request); + request.setAttribute("errorMap", errorMap); + return "authoring/addCondition"; + } + } + + request.setAttribute(QaAppConstants.ATTR_SESSION_MAP_ID, QaConditionForm.getSessionMapID()); + + return "authoring/conditionList"; + } + + /** + * Remove condition from HttpSession list and update page display. As + * authoring rule, all persist only happen when user submit whole page. So + * this remove is just impact HttpSession values. + * + * @param request + * @return + */ + @RequestMapping(path = "/removeCondition", method = RequestMethod.POST) + private String removeCondition(HttpServletRequest request) { + + // get back sessionMAP + String sessionMapID = WebUtil.readStrParam(request, QaAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(sessionMapID); + + int orderId = NumberUtils.stringToInt(request.getParameter(QaAppConstants.PARAM_ORDER_ID), -1); + if (orderId != -1) { + SortedSet conditionSet = getConditions(sessionMap); + List conditionList = new ArrayList<>(conditionSet); + QaCondition condition = conditionList.remove(orderId); + for (QaCondition otherCondition : conditionSet) { + if (otherCondition.getOrderId() > orderId) { + otherCondition.setOrderId(otherCondition.getOrderId() - 1); + } + } + conditionSet.clear(); + conditionSet.addAll(conditionList); + // add to delList + List deletedList = AuthoringConditionController.getDeletedQaConditionList(sessionMap); + deletedList.add(condition); + } + + request.setAttribute(QaAppConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "authoring/conditionList"; + } + + /** + * Move up current item. + * + * @param request + * @return + */ + @RequestMapping("/upCondition") + private String upCondition(HttpServletRequest request) { + return switchItem(request, true); + } + + /** + * Move down current item. + * + * @param request + * @return + */ + @RequestMapping("/downCondition") + private String downCondition(HttpServletRequest request) { + return switchItem(request, false); + } + + private String switchItem(HttpServletRequest request, boolean up) { + // get back sessionMAP + String sessionMapID = WebUtil.readStrParam(request, QaAppConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(sessionMapID); + + int orderId = NumberUtils.stringToInt(request.getParameter(QaAppConstants.PARAM_ORDER_ID), -1); + if (orderId != -1) { + SortedSet conditionSet = getConditions(sessionMap); + List conditionList = new ArrayList<>(conditionSet); + // get current and the target item, and switch their sequnece + QaCondition condition = conditionList.get(orderId); + QaCondition repCondition; + if (up) { + repCondition = conditionList.get(--orderId); + } else { + repCondition = conditionList.get(++orderId); + } + int upSeqId = repCondition.getOrderId(); + repCondition.setOrderId(condition.getOrderId()); + condition.setOrderId(upSeqId); + + // put back list, it will be sorted again + conditionSet.clear(); + conditionSet.addAll(conditionList); + } + + request.setAttribute(QaAppConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "authoring/conditionList"; + } + + // ************************************************************************************* + // Private methods for internal needs + // ************************************************************************************* + + /** + * List save current taskList items. + * + * @param request + * @return + */ + @SuppressWarnings("unchecked") + public static SortedSet getConditions(SessionMap sessionMap) { + SortedSet list = (SortedSet) sessionMap.get(QaAppConstants.ATTR_CONDITION_SET); + if (list == null) { + list = new TreeSet<>(new TextSearchConditionComparator()); + sessionMap.put(QaAppConstants.ATTR_CONDITION_SET, list); + } + return list; + } + + /** + * List save current taskList items. + * + * @param request + * @return + */ + @SuppressWarnings("unchecked") + private SortedSet getQuestions(SessionMap sessionMap) { + SortedSet list = (SortedSet) sessionMap.get(QaAppConstants.LIST_QUESTIONS); + if (list == null) { + list = new TreeSet<>(new QaQuestionComparator()); + sessionMap.put(QaAppConstants.LIST_QUESTIONS, list); + } + return list; + } + + /** + * Get the deleted condition list, which could be persisted or non-persisted + * items. + * + * @param request + * @return + */ + @SuppressWarnings("unchecked") + public static List getDeletedQaConditionList(SessionMap sessionMap) { + List list = (List) sessionMap.get(QaAppConstants.ATTR_DELETED_CONDITION_LIST); + if (list == null) { + list = new ArrayList<>(); + sessionMap.put(QaAppConstants.ATTR_DELETED_CONDITION_LIST, list); + } + return list; + } + + /** + * This method will populate condition information to its form for edit use. + * + * @param orderId + * @param condition + * @param form + * @param request + */ + private void populateConditionToForm(int orderId, QaCondition condition, QaConditionForm form, + HttpServletRequest request) { + form.populateForm(condition); + if (orderId >= 0) { + form.setOrderId(orderId + 1); + } + } + + /** + * This method will populate questions to choose to the form for edit use. + * + * @param sequenceId + * @param condition + * @param form + * @param request + */ + private void populateFormWithPossibleItems(@ModelAttribute("QaConditionForm") QaConditionForm QaConditionForm, + HttpServletRequest request) { + // get back sessionMAP + String sessionMapID = QaConditionForm.getSessionMapID(); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(sessionMapID); + + SortedSet questions = getQuestions(sessionMap); + + // Initialise the LabelValueBeans in the possibleOptions array. + Map possibleItems = new HashMap<>(questions.size()); + int i = 0; + for (QaQueContent question : questions) { + String nonHTMLQuestion = question.getQbQuestion().getName(); + if (nonHTMLQuestion != null) { + nonHTMLQuestion = WebUtil.removeHTMLtags(nonHTMLQuestion); + // we don't want to cite the whole question, so we just leave some first characters; it should be enough + // to recognise the question by a teacher + if (nonHTMLQuestion.length() > QaAppConstants.QUESTION_CUTOFF_INDEX) { + nonHTMLQuestion = nonHTMLQuestion.substring(0, QaAppConstants.QUESTION_CUTOFF_INDEX) + "..."; + } + } + possibleItems.put(nonHTMLQuestion, new Integer(question.getDisplayOrder()).toString()); + } + QaConditionForm.setPossibleItems(possibleItems); + } + + /** + * Extract form content to QaCondition. + * + * @param request + * @param form + * @throws QaException + */ + private void extractFormToQaCondition(HttpServletRequest request, QaConditionForm form) throws Exception { + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(form.getSessionMapID()); + // check whether it is "edit(old item)" or "add(new item)" + SortedSet conditionSet = getConditions(sessionMap); + int orderId = form.getOrderId(); + QaCondition condition = null; + + if (orderId == -1) { // add + String properConditionName = qaService.createConditionName(conditionSet); + condition = form.extractCondition(); + condition.setName(properConditionName); + int maxOrderId = 1; + if (conditionSet != null && conditionSet.size() > 0) { + QaCondition last = conditionSet.last(); + maxOrderId = last.getOrderId() + 1; + } + condition.setOrderId(maxOrderId); + conditionSet.add(condition); + } else { // edit + List conditionList = new ArrayList<>(conditionSet); + condition = conditionList.get(orderId - 1); + form.extractCondition(condition); + } + + Integer[] selectedItems = form.getSelectedItems(); + SortedSet questions = getQuestions(sessionMap); + + condition.temporaryQaQuestions.clear(); + for (Integer selectedItem : selectedItems) { + for (QaQueContent question : questions) { + if (selectedItem.equals(new Integer(question.getDisplayOrder()))) { + condition.temporaryQaQuestions.add(question); + } + } + } + + } + + /** + * Validate QaCondition + * + * @param QaConditionForm + * @return + */ + private MultiValueMap validateQaCondition(QaConditionForm QaConditionForm, + HttpServletRequest request) { + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + + String formConditionName = QaConditionForm.getDisplayName(); + if (StringUtils.isBlank(formConditionName)) { + + errorMap.add("GLOBAL", messageService.getMessage("error.condition.name.blank")); + } else { + + Integer formConditionOrderId = QaConditionForm.getOrderId(); + + String sessionMapID = QaConditionForm.getSessionMapID(); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(sessionMapID); + SortedSet conditionSet = getConditions(sessionMap); + for (QaCondition condition : conditionSet) { + if (formConditionName.equals(condition.getDisplayName()) + && !formConditionOrderId.equals(condition.getOrderId())) { + + errorMap.add("GLOBAL", messageService.getMessage("error.condition.duplicated.name")); + break; + } + } + } + + // should be selected at least one question + Integer[] selectedItems = QaConditionForm.getSelectedItems(); + if (selectedItems == null || selectedItems.length == 0) { + errorMap.add("GLOBAL", messageService.getMessage("error.condition.no.questions.selected")); + } + + return errorMap; + } + +} Index: lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/AuthoringController.java =================================================================== diff -u --- lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/AuthoringController.java (revision 0) +++ lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/AuthoringController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,641 @@ +/**************************************************************** + * 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 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.tool.qa.web.controller; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.qb.QbUtils; +import org.lamsfoundation.lams.qb.form.QbQuestionForm; +import org.lamsfoundation.lams.qb.model.QbQuestion; +import org.lamsfoundation.lams.qb.service.IQbService; +import org.lamsfoundation.lams.rating.model.RatingCriteria; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.qa.QaAppConstants; +import org.lamsfoundation.lams.tool.qa.model.QaCondition; +import org.lamsfoundation.lams.tool.qa.model.QaContent; +import org.lamsfoundation.lams.tool.qa.model.QaQueContent; +import org.lamsfoundation.lams.tool.qa.service.IQaService; +import org.lamsfoundation.lams.tool.qa.util.QaQuestionComparator; +import org.lamsfoundation.lams.tool.qa.web.form.QaAuthoringForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.util.CommonConstants; +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.lamsfoundation.lams.web.util.SessionMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Q&A Tool's authoring methods. Additionally, there is one more method that initializes authoring and it's located in + * QaStarterAction.java. + * + * @author Ozgur Demirtas + */ +@Controller +@RequestMapping("/authoring") +public class AuthoringController implements QaAppConstants { + private static Logger logger = Logger.getLogger(AuthoringController.class.getName()); + + @Autowired + private IQaService qaService; + + @Autowired + private IQbService qbService; + + @Autowired + private IUserManagementService userManagementService; + + @Autowired + @Qualifier("qaMessageService") + private MessageService messageService; + + @RequestMapping("/authoring") + public String execute(@ModelAttribute("authoringForm") QaAuthoringForm form, HttpServletRequest request, + @RequestParam Long toolContentID) throws IOException, ServletException { + ToolAccessMode mode = WebUtil.readToolAccessModeAuthorDefaulted(request); + return readDatabaseData(form, request, mode); + } + + /** + * Set the defineLater flag so that learners cannot use content while we are editing. This flag is released when + * updateContent is called. + */ + @RequestMapping(path = "/definelater", method = RequestMethod.POST) + public String definelater(@ModelAttribute("authoringForm") QaAuthoringForm form, + HttpServletRequest request) { + Long toolContentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + qaService.setDefineLater(toolContentID, true); + + return readDatabaseData(form, request, ToolAccessMode.TEACHER); + } + + /** + * Common method for "unspecified" and "defineLater" + */ + private String readDatabaseData(QaAuthoringForm form, HttpServletRequest request, ToolAccessMode mode) { + Long toolContentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + String contentFolderID = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); + form.setContentFolderID(contentFolderID); + + form.setToolContentID(toolContentID.toString()); + + SessionMap sessionMap = new SessionMap<>(); + sessionMap.put(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + sessionMap.put(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentID); + sessionMap.put(AttributeNames.ATTR_MODE, mode); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + form.setSessionMapID(sessionMap.getSessionID()); + + QaContent qa = qaService.getQaContent(toolContentID); + if (qa == null) { + /* fetch default content */ + long defaultContentID = qaService.getToolDefaultContentIdBySignature(QaAppConstants.MY_SIGNATURE); + qa = qaService.getQaContent(defaultContentID); + qa = QaContent.newInstance(qa, toolContentID); + } + + form.setQa(qa); + + /* + * get the existing question content + */ + Set qaQuestions = getQuestions(sessionMap); + qaQuestions.clear(); + qaQuestions.addAll(qa.getQaQueContents()); + + request.setAttribute(AttributeNames.ATTR_MODE, mode.toString()); + + //process conditions + for (QaCondition condition : qa.getConditions()) { + for (QaQueContent qaQuestion : qaQuestions) { + for (QaQueContent conditionQuestion : condition.getQuestions()) { + if (qaQuestion.getDisplayOrder() == conditionQuestion.getDisplayOrder()) { + condition.temporaryQaQuestions.add(qaQuestion); + } + } + } + } + SortedSet conditionList = AuthoringConditionController.getConditions(sessionMap); + conditionList.clear(); + conditionList.addAll(qa.getConditions()); + + // get rating criterias from DB + List ratingCriterias = qaService.getRatingCriterias(qa.getQaContentId()); + sessionMap.put(AttributeNames.ATTR_RATING_CRITERIAS, ratingCriterias); + + return "authoring/AuthoringTabsHolder"; + } + + /** + * submits content into the tool database + * + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + @RequestMapping(path = "/submitAllContent", method = RequestMethod.POST) + public String submitAllContent(@ModelAttribute("authoringForm") QaAuthoringForm form, HttpServletRequest request) + throws IOException, ServletException, IllegalAccessException, InvocationTargetException, + NoSuchMethodException { + SessionMap sessionMap = getSessionMap(form, request); + ToolAccessMode mode = WebUtil.readToolAccessModeAuthorDefaulted(request); + request.setAttribute(AttributeNames.ATTR_MODE, mode.toString()); + Long toolContentID = form.getQa().getQaContentId(); + QaContent qa = form.getQa(); + QaContent qaPO = qaService.getQaContent(toolContentID); + + if (qaPO == null) { + // new Scratchie, create it. + qaPO = qa; + qaPO.setCreationDate(new Timestamp(new Date().getTime())); + qaPO.setUpdateDate(new Timestamp(new Date().getTime())); + + } else { + // copyProperties() below sets qaPO's conditions to empty collection + // but the conditions still exist in Hibernate cache, so we need to evict them now + for (QaCondition condition : qaPO.getConditions()) { + qaService.releaseFromCache(condition); + } + + qaPO.getQaQueContents().clear(); + qaPO.getConditions().clear(); + Long uid = qaPO.getUid(); + PropertyUtils.copyProperties(qaPO, qa); + + // set back UID + qaPO.setUid(uid); + + // if it is Teacher (from monitor) - change define later status + if (mode.isTeacher()) { + qaPO.setDefineLater(false); + + // audit log the teacher has started editing activity in monitor + qaService.auditLogStartEditingActivityInMonitor(toolContentID); + + // recalculate User Answers - not required, as long as any question modification is minor and does not lead to changing question version or uid + } + + qaPO.setUpdateDate(new Timestamp(new Date().getTime())); + } + + // *******************************Handle user******************* + UserDTO toolUser = (UserDTO) SessionManager.getSession().getAttribute(AttributeNames.USER); + long userId = 0; + if (toolUser != null) { + userId = toolUser.getUserID().longValue(); + } else { + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + if (user != null) { + userId = user.getUserID().longValue(); + } else { + userId = 0; + } + } + qaPO.setCreatedBy(userId); + qaService.saveOrUpdateQaContent(qaPO); + + // ************************* Handle Q&A questions ******************* + Set items = new LinkedHashSet<>(); + Set newItems = getQuestions(sessionMap); + for (QaQueContent question : newItems) { + question.setQaContent(qaPO); + question.setToolContentId(qaPO.getQaContentId()); + qaService.saveOrUpdate(question); + items.add(question); + } + qaPO.setQaQueContents(items); + + // ************************* Handle Q&A conditions ******************* + SortedSet conditions = AuthoringConditionController.getConditions(sessionMap); + for (QaCondition condition : conditions) { + condition.setQuestions(new TreeSet<>(new QaQuestionComparator())); + for (QaQueContent qaQuestion : condition.temporaryQaQuestions) { + for (QaQueContent question : newItems) { + if (qaQuestion.getDisplayOrder() == question.getDisplayOrder()) { + condition.getQuestions().add(question); + } + } + } + } + qaPO.setConditions(conditions); + + //reorder questions so displayOrder numbers come in strictly sequential order (it's required for proper work of learning) + int displayOrder = 1; + for (QaQueContent question : newItems) { + question.setDisplayOrder(displayOrder++); + qaService.saveOrUpdate(question); + } + + qaService.saveOrUpdateQaContent(qaPO); + + // ************************* Handle rating criterias ******************* + List oldCriterias = (List) sessionMap.get(AttributeNames.ATTR_RATING_CRITERIAS); + qaService.saveRatingCriterias(request, oldCriterias, toolContentID); + + request.setAttribute(AttributeNames.ATTR_MODE, mode.toString()); + request.setAttribute(CommonConstants.LAMS_AUTHORING_SUCCESS_FLAG, Boolean.TRUE); + + // remove deleted questions + List deletedQuestions = getDeletedQuestions(sessionMap); + for (QaQueContent deletedQuestion : deletedQuestions) { + QaQueContent removeableQuestion = qaService.getQuestionByUid(deletedQuestion.getUid()); + if (removeableQuestion != null) { + qaService.removeQuestion(removeableQuestion); + } + } + + // delete conditions from database + List delConditionList = AuthoringConditionController.getDeletedQaConditionList(sessionMap); + Iterator delIter = delConditionList.iterator(); + while (delIter.hasNext()) { + QaCondition condition = delIter.next(); + delIter.remove(); + qaService.deleteCondition(condition); + } + + return "authoring/AuthoringTabsHolder"; + } + + /** + * Adds QbQuestion, selected in the question bank, to the current question list. + */ + @RequestMapping(value = "/importQbQuestion", method = RequestMethod.POST) + private String importQbQuestion(@ModelAttribute("newQuestionForm") QaAuthoringForm form, HttpServletRequest request, + @RequestParam String sessionMapID, @RequestParam Long qbQuestionUid) { + SessionMap sessionMap = getSessionMap(form, request); + SortedSet qaQuestions = getQuestions(sessionMap); + + //check whether this QB question is a duplicate + for (QaQueContent qaQuestion : qaQuestions) { + if (qbQuestionUid.equals(qaQuestion.getQbQuestion().getUid())) { + //let jsp know it's a duplicate + return "forward:/authoring/showDuplicateQuestionError.do"; + } + } + + //get QbQuestion from DB + QbQuestion qbQuestion = qbService.getQuestionByUid(qbQuestionUid); + + //create new ScratchieItem and assign imported qbQuestion to it + QaQueContent item = new QaQueContent(); + item.setQbQuestion(qbQuestion); + int maxSeq = 1; + if (qaQuestions != null && qaQuestions.size() > 0) { + QaQueContent last = qaQuestions.last(); + maxSeq = last.getDisplayOrder() + 1; + } + item.setDisplayOrder(maxSeq); + qaQuestions.add(item); + + return "authoring/itemlist"; + } + + /** + * Shows "This question has already been added" error message in a browser. + */ + @RequestMapping("/showDuplicateQuestionError") + @ResponseBody + public String showDuplicateQuestionError(HttpServletResponse response) throws IOException { + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("isDuplicated", true); + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * saveQuestion + */ + @RequestMapping(value = "/saveQuestion", method = RequestMethod.POST) + public String saveQuestion(@ModelAttribute("newQuestionForm") QbQuestionForm form, HttpServletRequest request) + throws IOException, ServletException { + SessionMap sessionMap = getSessionMap(form, request); + SortedSet qaQuestions = getQuestions(sessionMap); + int itemIdx = NumberUtils.toInt(form.getItemIndex(), -1); + + // check whether it is "edit(old Question)" or "add(new Question)" + QbQuestion qbQuestion; + QaQueContent qaQuestion; + final boolean isAddingQuestion = itemIdx == -1; + if (isAddingQuestion) { // add + + String title = form.getTitle(); + boolean duplicates = checkDuplicateQuestions(qaQuestions, title); + if (duplicates) { + return "authoring/itemlist"; + } + + qbQuestion = new QbQuestion(); + qbQuestion.setType(QbQuestion.TYPE_ESSAY); + qbQuestion.setQuestionId(form.getQuestionId()); + qbQuestion.setContentFolderId(form.getContentFolderID()); + + qaQuestion = new QaQueContent(); + int maxSeq = 1; + if (qaQuestions != null && qaQuestions.size() > 0) { + QaQueContent last = qaQuestions.last(); + maxSeq = last.getDisplayOrder() + 1; + } + qaQuestion.setDisplayOrder(maxSeq); + qaQuestions.add(qaQuestion); + + // edit + } else { + List rList = new ArrayList<>(qaQuestions); + qaQuestion = rList.get(itemIdx); + qbQuestion = qbService.getQuestionByUid(qaQuestion.getQbQuestion().getUid()); + qbService.releaseFromCache(qbQuestion); + } + + QbQuestion oldQbQuestion = qbQuestion.clone(); + // evict everything manually as we do not use DTOs, just real entities + // without eviction changes would be saved immediately into DB + qaService.releaseFromCache(oldQbQuestion); + + qbQuestion.setName(form.getTitle()); + qbQuestion.setDescription(form.getDescription()); + qbQuestion.setAnswerRequired(form.isAnswerRequired()); + qbQuestion.setMinWordsLimit(form.getMinWordsLimit()); + qbQuestion.setFeedback(form.getFeedback()); + + int isQbQuestionModified = qbQuestion.isQbQuestionModified(oldQbQuestion); + QbQuestion updatedQuestion = null; + switch (isQbQuestionModified) { + case IQbService.QUESTION_MODIFIED_VERSION_BUMP: { + // impossible scenario as long as ESSAY question type can't have version + throw new RuntimeException( + "Impossible scenario as long as ESSAY question type can't have new versions"); + } + case IQbService.QUESTION_MODIFIED_ID_BUMP: { + // new question gets created + updatedQuestion = qbQuestion.clone(); + updatedQuestion.clearID(); + updatedQuestion.setVersion(1); + updatedQuestion.setCreateDate(new Date()); + } + break; + case IQbService.QUESTION_MODIFIED_NONE: { + // save the old question anyway, as it may contain some minor changes (like title or description change) + updatedQuestion = qbQuestion; + } + break; + } + userManagementService.save(updatedQuestion); + qaQuestion.setQbQuestion(updatedQuestion); + request.setAttribute("qbQuestionModified", isQbQuestionModified); + + //take care about question's collections. add to collection first + Long oldCollectionUid = form.getOldCollectionUid(); + Long newCollectionUid = form.getNewCollectionUid(); + if (isAddingQuestion || !newCollectionUid.equals(oldCollectionUid)) { + qbService.addQuestionToCollection(newCollectionUid, updatedQuestion.getQuestionId(), false); + } + //remove from the old collection, if needed + if (!isAddingQuestion && !newCollectionUid.equals(oldCollectionUid)) { + qbService.removeQuestionFromCollectionByQuestionId(oldCollectionUid, updatedQuestion.getQuestionId(), + false); + } + + return "authoring/itemlist"; + } + + /** + * Ajax call, will add one more input line for new resource item instruction. + */ + @RequestMapping("/newQuestionBox") + private String newQuestionBox(@ModelAttribute("newQuestionForm") QbQuestionForm form, HttpServletRequest request, + @RequestParam String sessionMapID) { + form.setSessionMapID(sessionMapID); + String questionContentFolderID = FileUtil.generateUniqueContentFolderID(); + form.setContentFolderID(questionContentFolderID); + form.setQuestionId(qbService.generateNextQuestionId()); // generate a new question ID right away, so another user won't "take it" + + QbUtils.fillFormWithUserCollections(qbService, form, null); + return "authoring/newQuestionBox"; + } + + /** + * Display edit page for existed scratchie item. + */ + @RequestMapping("/newEditableQuestionBox") + private String newEditableQuestionBox(@ModelAttribute("newQuestionForm") QbQuestionForm form, + HttpServletRequest request, @RequestParam String sessionMapID, @RequestParam Integer questionIndex) { + // get back sessionMAP + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + String contentFolderID = (String) sessionMap.get(AttributeNames.PARAM_CONTENT_FOLDER_ID); + Set qaQuestions = getQuestions(sessionMap); + + List rList = new ArrayList<>(qaQuestions); + QaQueContent qaQuestion = rList.get(questionIndex); + QbQuestion qbQuestion = qaQuestion.getQbQuestion(); + + form.setQuestionId(qbQuestion.getQuestionId()); + form.setTitle(qbQuestion.getName()); + form.setDescription(qbQuestion.getDescription()); + if (questionIndex >= 0) { + form.setItemIndex(String.valueOf(questionIndex)); + } + form.setAnswerRequired(qbQuestion.isAnswerRequired()); + form.setMinWordsLimit(qbQuestion.getMinWordsLimit()); + form.setFeedback(qbQuestion.getFeedback()); + + form.setContentFolderID(qbQuestion.getContentFolderId()); + form.setSessionMapID(sessionMapID); + QbUtils.fillFormWithUserCollections(qbService, form, qbQuestion.getUid()); + + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + return "authoring/newQuestionBox"; + } + + /** + * removes a question from the questions map + */ + @RequestMapping(path = "/removeQuestion", method = RequestMethod.POST) + public String removeQuestion(@ModelAttribute("newQuestionForm") QaAuthoringForm form, HttpServletRequest request) + throws IOException, ServletException { + SessionMap sessionMap = getSessionMap(form, request); + int questionIndex = NumberUtils.toInt(request.getParameter("questionIndex"), -1); + Set qaQuestions = getQuestions(sessionMap); + + if (questionIndex != -1) { + List rList = new ArrayList<>(qaQuestions); + QaQueContent questionToDelete = rList.remove(questionIndex); + qaQuestions.clear(); + qaQuestions.addAll(rList); + + // add to delList + List delList = getDeletedQuestions(sessionMap); + delList.add(questionToDelete); + + //take care about conditions + SortedSet conditions = AuthoringConditionController.getConditions(sessionMap); + Iterator conditionIter = conditions.iterator(); + while (conditionIter.hasNext()) { + QaCondition condition = conditionIter.next(); + Iterator questionToDeleteIter = condition.temporaryQaQuestions.iterator(); + while (questionToDeleteIter.hasNext()) { + if (questionToDeleteIter.next() == questionToDelete) { + questionToDeleteIter.remove(); + } + } + if (condition.temporaryQaQuestions.isEmpty()) { + conditionIter.remove(); + } + } + } + + return "authoring/itemlist"; + } + + /** + * moves a question down in the list + */ + @RequestMapping("/moveQuestionDown") + public String moveQuestionDown(@ModelAttribute("newQuestionForm") QaAuthoringForm form, HttpServletRequest request) + throws IOException, ServletException { + swapQuestions(form, request, "down"); + + return "authoring/itemlist"; + } + + /** + * moves a question up in the list + */ + @RequestMapping("/moveQuestionUp") + public String moveQuestionUp(@ModelAttribute("newQuestionForm") QaAuthoringForm form, HttpServletRequest request) + throws IOException, ServletException { + swapQuestions(form, request, "up"); + + return "authoring/itemlist"; + } + + private Set swapQuestions(QaAuthoringForm form, HttpServletRequest request, String direction) { + SessionMap sessionMap = getSessionMap(form, request); + Set questions = getQuestions(sessionMap); + int originalQuestionIndex = WebUtil.readIntParam(request, "questionIndex"); + int replacedQuestionIndex = direction.equals("down") ? originalQuestionIndex + 1 : originalQuestionIndex - 1; + + List rList = new ArrayList<>(questions); + // get current and the target item, and switch their sequence + QaQueContent originalQuestion = rList.get(originalQuestionIndex); + QaQueContent replacedQuestion = rList.get(replacedQuestionIndex); + + int upSeqId = replacedQuestion.getDisplayOrder(); + replacedQuestion.setDisplayOrder(originalQuestion.getDisplayOrder()); + originalQuestion.setDisplayOrder(upSeqId); + + // put back list, it will be sorted again + questions.clear(); + questions.addAll(rList); + + return questions; + } + + private boolean checkDuplicateQuestions(Set questions, String newQuestion) { + for (QaQueContent qaQuestion : questions) { + if (qaQuestion.getQbQuestion().getName().equals(newQuestion)) { + return true; + } + } + return false; + } + + /** + * List current Q&A questions. + * + * @param request + * @return + */ + @SuppressWarnings("unchecked") + private SortedSet getQuestions(SessionMap sessionMap) { + SortedSet list = (SortedSet) sessionMap.get(QaAppConstants.LIST_QUESTIONS); + if (list == null) { + list = new TreeSet<>(new QaQuestionComparator()); + sessionMap.put(QaAppConstants.LIST_QUESTIONS, list); + } + return list; + } + + /** + * List save deleted scratchie items, which could be persisted or non-persisted items. + * + * @param request + * @return + */ + @SuppressWarnings("unchecked") + private List getDeletedQuestions(SessionMap sessionMap) { + List list = (List) sessionMap.get(QaAppConstants.LIST_DELETED_QUESTIONS); + if (list == null) { + list = new ArrayList<>(); + sessionMap.put(QaAppConstants.LIST_DELETED_QUESTIONS, list); + } + return list; + } + + @SuppressWarnings("unchecked") + private SessionMap getSessionMap(QaAuthoringForm form, HttpServletRequest request) { + String sessionMapID = form.getSessionMapID(); + request.setAttribute(QaAppConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return (SessionMap) request.getSession().getAttribute(sessionMapID); + } + + @SuppressWarnings("unchecked") + private SessionMap getSessionMap(QbQuestionForm form, HttpServletRequest request) { + String sessionMapID = form.getSessionMapID(); + request.setAttribute(QaAppConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return (SessionMap) request.getSession().getAttribute(sessionMapID); + } +} Index: lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/LearningController.java =================================================================== diff -u --- lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/LearningController.java (revision 0) +++ lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/LearningController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,1318 @@ +/*************************************************************************** +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 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.tool.qa.web.controller; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; +import org.lamsfoundation.lams.rating.dto.ItemRatingCriteriaDTO; +import org.lamsfoundation.lams.rating.dto.ItemRatingDTO; +import org.lamsfoundation.lams.rating.dto.RatingCommentDTO; +import org.lamsfoundation.lams.rating.model.LearnerItemRatingCriteria; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.exception.ToolException; +import org.lamsfoundation.lams.tool.qa.QaAppConstants; +import org.lamsfoundation.lams.tool.qa.dto.GeneralLearnerFlowDTO; +import org.lamsfoundation.lams.tool.qa.dto.QaQuestionDTO; +import org.lamsfoundation.lams.tool.qa.model.QaContent; +import org.lamsfoundation.lams.tool.qa.model.QaQueContent; +import org.lamsfoundation.lams.tool.qa.model.QaQueUsr; +import org.lamsfoundation.lams.tool.qa.model.QaSession; +import org.lamsfoundation.lams.tool.qa.model.QaUsrResp; +import org.lamsfoundation.lams.tool.qa.service.IQaService; +import org.lamsfoundation.lams.tool.qa.util.LearningUtil; +import org.lamsfoundation.lams.tool.qa.util.QaComparator; +import org.lamsfoundation.lams.tool.qa.util.QaStringComparator; +import org.lamsfoundation.lams.tool.qa.web.form.QaLearningForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.MessageService; +import org.lamsfoundation.lams.util.ValidationUtil; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.lamsfoundation.lams.web.util.SessionMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Ozgur Demirtas + */ +@Controller +@RequestMapping("/learning") +public class LearningController implements QaAppConstants { + + @Autowired + private IQaService qaService; + + @Autowired + @Qualifier("qaMessageService") + private MessageService messageService; + + @RequestMapping("/") + public String unspecified() throws IOException, ServletException, ToolException { + return null; + } + + @RequestMapping("/learning") + public String execute(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, HttpServletRequest request) + throws IOException, ServletException { + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + qaLearningForm.setToolSessionID(toolSessionID); + String mode = request.getParameter(MODE); + qaLearningForm.setMode(mode); + + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + QaContent qaContent = qaSession.getQaContent(); + + QaQueUsr user = null; + if ((mode != null) && mode.equals(ToolAccessMode.TEACHER.toString())) { + // monitoring mode - user is specified in URL + // assessmentUser may be null if the user was force completed. + user = getSpecifiedUser(toolSessionID, WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID, false)); + } else { + user = getCurrentUser(toolSessionID); + } + Long userId = user.getQueUsrId(); + qaLearningForm.setUserID(user.getQueUsrId().toString()); + request.setAttribute("qaLearningForm", qaLearningForm); + QaQueUsr groupLeader = null; + if (qaContent.isUseSelectLeaderToolOuput()) { + groupLeader = qaService.checkLeaderSelectToolForSessionLeader(user, new Long(toolSessionID).longValue()); + + // forwards to the leaderSelection page + if (groupLeader == null && !mode.equals(ToolAccessMode.TEACHER.toString())) { + + List groupUsers = qaService.getUsersBySessionId(new Long(toolSessionID).longValue()); + request.setAttribute(ATTR_GROUP_USERS, groupUsers); + request.setAttribute(TOOL_SESSION_ID, toolSessionID); + request.setAttribute(ATTR_CONTENT, qaContent); + + return "learning/WaitForLeader"; + } + + // check if leader has submitted all answers + if (groupLeader.isResponseFinalized() && !mode.equals(ToolAccessMode.TEACHER.toString())) { + + // in case user joins the lesson after leader has answers some answers already - we need to make sure + // he has the same scratches as leader + qaService.copyAnswersFromLeader(user, groupLeader); + qaLearningForm.setRefreshAnswers("reload"); + + user.setResponseFinalized(true); + qaService.updateUser(user); + } + } else { + qaLearningForm.setRefreshAnswers("refresh"); + } + + /* holds the question contents for a given tool session and relevant content */ + Map mapQuestionStrings = new TreeMap<>(new QaComparator()); + Map mapQuestions = new TreeMap<>(); + + String sessionMapID = qaLearningForm.getSessionMapID(); + SessionMap sessionMap = sessionMapID == null ? null + : (SessionMap) request.getSession().getAttribute(sessionMapID); + if (sessionMap == null) { + sessionMap = new SessionMap<>(); + Map mapSequentialAnswers = new HashMap(); + sessionMap.put(MAP_SEQUENTIAL_ANSWERS_KEY, mapSequentialAnswers); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + qaLearningForm.setSessionMapID(sessionMap.getSessionID()); + + sessionMap.put(AttributeNames.ATTR_LEARNER_CONTENT_FOLDER, + qaService.getLearnerContentFolder(new Long(toolSessionID), user.getQueUsrId())); + } + String sessionMapId = sessionMap.getSessionID(); + sessionMap.put(IS_DISABLED, qaContent.isLockWhenFinished() && user.isLearnerFinished() + || (mode != null) && mode.equals(ToolAccessMode.TEACHER.toString())); + + sessionMap.put(ATTR_GROUP_LEADER, groupLeader); + boolean isUserLeader = qaService.isUserGroupLeader(user.getQueUsrId(), new Long(toolSessionID)); + boolean lockWhenFinished = qaContent.isLockWhenFinished(); + sessionMap.put(ATTR_IS_USER_LEADER, isUserLeader); + sessionMap.put(AttributeNames.ATTR_MODE, mode); + sessionMap.put(ATTR_CONTENT, qaContent); + sessionMap.put(AttributeNames.USER, user); + + GeneralLearnerFlowDTO generalLearnerFlowDTO = LearningUtil.buildGeneralLearnerFlowDTO(qaService, qaContent); + generalLearnerFlowDTO.setUserUid(user.getQueUsrId().toString()); + generalLearnerFlowDTO.setSessionMapID(sessionMapId); + generalLearnerFlowDTO.setToolSessionID(toolSessionID); + generalLearnerFlowDTO.setToolContentID(qaContent.getQaContentId().toString()); + + generalLearnerFlowDTO.setLockWhenFinished(new Boolean(lockWhenFinished).toString()); + generalLearnerFlowDTO.setNoReeditAllowed(qaContent.isNoReeditAllowed()); + generalLearnerFlowDTO.setReflection(new Boolean(qaContent.isReflect()).toString()); + generalLearnerFlowDTO.setReflectionSubject(qaContent.getReflectionSubject()); + + NotebookEntry notebookEntry = qaService.getEntry(new Long(toolSessionID), CoreNotebookConstants.NOTEBOOK_TOOL, + MY_SIGNATURE, userId.intValue()); + if (notebookEntry != null) { + //String notebookEntryPresentable = QaUtils.replaceNewLines(notebookEntry.getEntry()); + qaLearningForm.setEntryText(notebookEntry.getEntry()); + generalLearnerFlowDTO.setNotebookEntry(notebookEntry.getEntry()); + } + + /* + * Is the tool activity been checked as Define Later in the property inspector? + */ + if (qaContent.isDefineLater()) { + return "learning/defineLater"; + } + + sessionMap.put(AttributeNames.ATTR_IS_LAST_ACTIVITY, qaService.isLastActivity(Long.parseLong(toolSessionID))); + + /* + * fetch question content from content + */ + Iterator contentIterator = qaContent.getQaQueContents().iterator(); + while (contentIterator.hasNext()) { + QaQueContent qaQuestion = contentIterator.next(); + if (qaQuestion != null) { + int displayOrder = qaQuestion.getDisplayOrder(); + + if (displayOrder != 0) { + /* + * add the question to the questions Map in the displayOrder + */ + QaQuestionDTO questionDTO = new QaQuestionDTO(qaQuestion); + mapQuestions.put(displayOrder, questionDTO); + + mapQuestionStrings.put(String.valueOf(displayOrder), qaQuestion.getQbQuestion().getName()); + } + } + } + generalLearnerFlowDTO.setMapQuestions(mapQuestionStrings); + generalLearnerFlowDTO.setMapQuestionContentLearner(mapQuestions); + generalLearnerFlowDTO.setTotalQuestionCount(mapQuestions.size()); + qaLearningForm.setTotalQuestionCount(String.valueOf(mapQuestions.size())); + + String feedBackType = ""; + if (qaContent.isQuestionsSequenced()) { + feedBackType = FEEDBACK_TYPE_SEQUENTIAL; + } else { + feedBackType = FEEDBACK_TYPE_COMBINED; + } + String userFeedback = feedBackType + generalLearnerFlowDTO.getTotalQuestionCount() + QUESTIONS; + generalLearnerFlowDTO.setUserFeedback(userFeedback); + + generalLearnerFlowDTO.setRemainingQuestionCount(generalLearnerFlowDTO.getTotalQuestionCount().toString()); + generalLearnerFlowDTO.setInitialScreen(Boolean.TRUE.toString()); + + request.setAttribute(GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + + /* + * check if the mode is teacher + */ + if (mode.equals("teacher")) { + /* + * the report should have the all entries for the users in this tool session, + * and display under the "my answers" section the answers for the user id in the url + */ + + LearningController.refreshSummaryData(request, qaContent, qaSession, qaService, sessionMapId, user, + generalLearnerFlowDTO); + request.setAttribute(QaAppConstants.GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + + return "learning/LearnerRep"; + } + + //check if there is submission deadline + Date submissionDeadline = qaContent.getSubmissionDeadline(); + if (submissionDeadline != null) { + // store submission deadline to sessionMap + sessionMap.put(QaAppConstants.ATTR_SUBMISSION_DEADLINE, submissionDeadline); + + HttpSession ss = SessionManager.getSession(); + UserDTO learnerDto = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone learnerTimeZone = learnerDto.getTimeZone(); + Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, submissionDeadline); + Date currentLearnerDate = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, new Date()); + + // calculate whether submission deadline has passed, and if so forward to "submissionDeadline" + if (currentLearnerDate.after(tzSubmissionDeadline)) { + + //if ShowOtherAnswersAfterDeadline is enabled - show others answers + if (qaContent.isShowOtherAnswersAfterDeadline()) { + generalLearnerFlowDTO.setLockWhenFinished(Boolean.TRUE.toString()); + generalLearnerFlowDTO.setNoReeditAllowed(true); + //only for ActionForward refreshAllResults(..) method + sessionMap.put("noRefresh", true); + /* + * the report should have all the users' entries OR the report should have only the current + * session's entries + */ + + LearningController.refreshSummaryData(request, qaContent, qaSession, qaService, sessionMapId, + user, generalLearnerFlowDTO); + + generalLearnerFlowDTO.setIsLearnerFinished(user.isLearnerFinished()); + return "learning/learnerRep"; + + // show submissionDeadline page otherwise + } else { + return "learning/submissionDeadline"; + } + } + } + + /* + * Verify that userId does not already exist in the db. + * If it does exist and the passed tool session id exists in the db, that means the user already responded to + * the content and + * his answers must be displayed read-only + * + * if the user's tool session id AND user id exists in the tool tables go to learner's report. + */ + /* + * if the 'All Responses' has been clicked no more user entry is accepted, and isResponseFinalized() returns + * true + */ + + //if Response is Finalized + if (user.isResponseFinalized()) { + QaSession checkSession = user.getQaSession(); + + if (checkSession != null) { + Long checkQaSessionId = checkSession.getQaSessionId(); + + if (checkQaSessionId.toString().equals(toolSessionID)) { + + // the learner is in the same session and has already responsed to this content + + generalLearnerFlowDTO.setLockWhenFinished(String.valueOf(qaContent.isLockWhenFinished())); + generalLearnerFlowDTO.setNoReeditAllowed(qaContent.isNoReeditAllowed()); + /* + * the report should have all the users' entries OR the report should have only the current + * session's entries + */ + + LearningController.refreshSummaryData(request, qaContent, qaSession, qaService, sessionMapId, + user, generalLearnerFlowDTO); + + generalLearnerFlowDTO.setIsLearnerFinished(user.isLearnerFinished()); + return "learning/LearnerRep"; + } + } + } + + //**---- showing AnswersContent.jsp ----** + LearningUtil.populateAnswers(sessionMap, qaContent, user, mapQuestions, generalLearnerFlowDTO, qaService); + + return "learning/AnswersContent"; + } + + private QaQueUsr getSpecifiedUser(String toolSessionId, Integer userId) { + QaQueUsr qaUser = qaService.getUserByIdAndSession(userId.longValue(), new Long(toolSessionId)); + if (qaUser == null) { + qaUser = qaService.createUser(new Long(toolSessionId), userId); + } + return qaUser; + } + + /** + * submits users responses + */ + @RequestMapping("/submitAnswersContent") + public String submitAnswersContent(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request) throws IOException, ServletException { + + LearningUtil.saveFormRequestData(request, qaLearningForm); + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + QaContent qaContent = qaSession.getQaContent(); + + QaQueUsr qaQueUsr = getCurrentUser(toolSessionID); + //prohibit users from submitting answers after response is finalized but Resubmit button is not pressed (e.g. using 2 browsers) + if (qaQueUsr.isResponseFinalized()) { + String redirectURL = "redirect:/learning/learning.do"; + redirectURL = WebUtil.appendParameterToURL(redirectURL, AttributeNames.PARAM_TOOL_SESSION_ID, + toolSessionID.toString()); + redirectURL = WebUtil.appendParameterToURL(redirectURL, QaAppConstants.MODE, "learner"); + return redirectURL; + } + + GeneralLearnerFlowDTO generalLearnerFlowDTO = LearningUtil.buildGeneralLearnerFlowDTO(qaService, qaContent); + + String totalQuestionCount = generalLearnerFlowDTO.getTotalQuestionCount().toString(); + int intTotalQuestionCount = new Integer(totalQuestionCount).intValue(); + + String questionListingMode = generalLearnerFlowDTO.getQuestionListingMode(); + + Map mapAnswers = new TreeMap(new QaComparator()); + Map mapAnswersPresentable = new TreeMap(new QaComparator()); + + String forwardName = QaAppConstants.INDIVIDUAL_LEARNER_RESULTS; + + String sessionMapID = qaLearningForm.getSessionMapID(); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + /* if the listing mode is QUESTION_LISTING_MODE_COMBINED populate the answers here */ + if (questionListingMode.equalsIgnoreCase(QaAppConstants.QUESTION_LISTING_MODE_COMBINED)) { + + for (int questionIndex = QaAppConstants.INITIAL_QUESTION_COUNT + .intValue(); questionIndex <= intTotalQuestionCount; questionIndex++) { + // TestHarness can not send "answerX" fields, so stick to the original, unfiltered field + boolean isTestHarness = Boolean.valueOf(request.getParameter("testHarness")); + String answerParamName = "answer" + questionIndex + (isTestHarness ? "__textarea" : ""); + String answer = request.getParameter(answerParamName); + + Integer questionIndexInteger = new Integer(questionIndex); + mapAnswers.put(questionIndexInteger.toString(), answer); + mapAnswersPresentable.put(questionIndexInteger.toString(), answer); + + //validate + errorMap = validateQuestionAnswer(answer, questionIndexInteger, generalLearnerFlowDTO); + + // store + if (errorMap.isEmpty()) { + qaService.updateResponseWithNewAnswer(answer, toolSessionID, questionIndex, false); + } + } + + } else { + Object[] results = storeSequentialAnswer(qaLearningForm, request, generalLearnerFlowDTO, true); + mapAnswers = (Map) results[0]; + errorMap = (MultiValueMap) results[1]; + + mapAnswersPresentable = (Map) sessionMap.get(QaAppConstants.MAP_ALL_RESULTS_KEY); + mapAnswersPresentable = LearningController.removeNewLinesMap(mapAnswersPresentable); + } + + //finalize response so user won't need to edit his answers again, if coming back to the activity after leaving activity at this point + if (errorMap.isEmpty()) { + qaQueUsr.setResponseFinalized(true); + qaService.updateUser(qaQueUsr); + + //in case of errors - prompt learner to enter answers again + } else { + request.setAttribute("errorMap", errorMap); + forwardName = QaAppConstants.LOAD_LEARNER; + } + + generalLearnerFlowDTO.setMapAnswers(mapAnswers); + generalLearnerFlowDTO.setMapAnswersPresentable(mapAnswersPresentable); + + /* mapAnswers will be used in the viewAllAnswers screen */ + if (sessionMap == null) { + sessionMap = new SessionMap<>(); + } + + sessionMap.put(QaAppConstants.MAP_ALL_RESULTS_KEY, mapAnswers); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + qaLearningForm.setSessionMapID(sessionMap.getSessionID()); + generalLearnerFlowDTO.setSessionMapID(sessionMap.getSessionID()); + + boolean lockWhenFinished = qaContent.isLockWhenFinished(); + generalLearnerFlowDTO.setLockWhenFinished(new Boolean(lockWhenFinished).toString()); + generalLearnerFlowDTO.setNoReeditAllowed(qaContent.isNoReeditAllowed()); + generalLearnerFlowDTO.setReflection(new Boolean(qaContent.isReflect()).toString()); + + request.setAttribute(QaAppConstants.GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + + // notify teachers on response submit + if (errorMap.isEmpty() && qaContent.isNotifyTeachersOnResponseSubmit()) { + qaService.notifyTeachersOnResponseSubmit(new Long(toolSessionID)); + } + return forwardName; + } + + @RequestMapping(value = "/checkLeaderProgress") + @ResponseBody + public String checkLeaderProgress(HttpServletRequest request, HttpServletResponse response) throws IOException { + Long toolSessionId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); + + QaSession session = qaService.getSessionById(toolSessionId); + QaQueUsr leader = session.getGroupLeader(); + + boolean isLeaderResponseFinalized = leader.isResponseFinalized(); + + ObjectNode ObjectNode = JsonNodeFactory.instance.objectNode(); + ObjectNode.put("isLeaderResponseFinalized", isLeaderResponseFinalized); + response.setContentType("application/json;charset=UTF-8"); + return ObjectNode.toString(); + } + + /** + * Auto saves responses. + */ + @RequestMapping("/autoSaveAnswers") + @ResponseStatus(HttpStatus.OK) + public void autoSaveAnswers(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request) throws IOException, ServletException { + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + + QaQueUsr qaQueUsr = getCurrentUser(toolSessionID); + //prohibit users from autosaving answers after response is finalized but Resubmit button is not pressed (e.g. using 2 browsers) + if (qaQueUsr.isResponseFinalized()) { + } + + LearningUtil.saveFormRequestData(request, qaLearningForm); + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + QaContent qaContent = qaSession.getQaContent(); + int intTotalQuestionCount = qaContent.getQaQueContents().size(); + + if (!qaContent.isQuestionsSequenced()) { + for (int questionIndex = QaAppConstants.INITIAL_QUESTION_COUNT + .intValue(); questionIndex <= intTotalQuestionCount; questionIndex++) { + String newAnswer = request.getParameter("answer" + questionIndex); + qaService.updateResponseWithNewAnswer(newAnswer, toolSessionID, questionIndex, true); + } + + } else { + String currentQuestionIndex = qaLearningForm.getCurrentQuestionIndex(); + String newAnswer = qaLearningForm.getAnswer(); + QaQueContent currentQuestion = qaService + .getQuestionByContentAndDisplayOrder(new Integer(currentQuestionIndex), qaContent.getUid()); + + boolean isRequiredQuestionMissed = currentQuestion.getQbQuestion().isAnswerRequired() && isEmpty(newAnswer); + if (!isRequiredQuestionMissed) { + qaService.updateResponseWithNewAnswer(newAnswer, toolSessionID, new Integer(currentQuestionIndex), + true); + } + } + } + + /** + * enables retaking the activity + */ + @RequestMapping("/redoQuestions") + public String redoQuestions(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request) throws IOException, ServletException { + LearningUtil.saveFormRequestData(request, qaLearningForm); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + QaContent qaContent = qaSession.getQaContent(); + + GeneralLearnerFlowDTO generalLearnerFlowDTO = LearningUtil.buildGeneralLearnerFlowDTO(qaService, qaContent); + + qaLearningForm.setCurrentQuestionIndex(new Integer(1).toString()); + + String sessionMapID = qaLearningForm.getSessionMapID(); + + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + qaLearningForm.setSessionMapID(sessionMap.getSessionID()); + generalLearnerFlowDTO.setSessionMapID(sessionMap.getSessionID()); + generalLearnerFlowDTO.setToolContentID(qaContent.getQaContentId().toString()); + + // create mapQuestions + Map mapQuestions = generalLearnerFlowDTO.getMapQuestionContentLearner(); + generalLearnerFlowDTO.setMapQuestions(mapQuestions); + generalLearnerFlowDTO.setTotalQuestionCount(new Integer(mapQuestions.size())); + generalLearnerFlowDTO.setRemainingQuestionCount(new Integer(mapQuestions.size()).toString()); + qaLearningForm.setTotalQuestionCount(new Integer(mapQuestions.size()).toString()); + + //in order to track whether redo button is pressed store this info + QaQueUsr qaQueUsr = getCurrentUser(toolSessionID); + qaQueUsr.setResponseFinalized(false); + qaService.updateUser(qaQueUsr); + + // populate answers + LearningUtil.populateAnswers(sessionMap, qaContent, qaQueUsr, mapQuestions, generalLearnerFlowDTO, qaService); + + request.setAttribute(QaAppConstants.GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + request.setAttribute("learningForm", qaLearningForm); + return "learning/AnswersContent"; + } + + /** + * Stores all results and moves onto the next step. If view other users answers = true, then goes to the view all + * answers screen, otherwise goes straight to the reflection screen (if any). + * + * @return Learner Report for a session + */ + @RequestMapping("/storeAllResults") + public String storeAllResults(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + LearningUtil.saveFormRequestData(request, qaLearningForm); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + String userID = request.getParameter("userID"); + QaQueUsr user = qaService.getUserByIdAndSession(new Long(userID), new Long(toolSessionID)); + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + QaContent qaContent = qaSession.getQaContent(); + + // LearningUtil.storeResponses(mapAnswers, qaService, toolContentID, new Long(toolSessionID)); + + if (qaContent.isShowOtherAnswers()) { + GeneralLearnerFlowDTO generalLearnerFlowDTO = LearningUtil.buildGeneralLearnerFlowDTO(qaService, qaContent); + String sessionMapID = qaLearningForm.getSessionMapID(); + generalLearnerFlowDTO.setSessionMapID(sessionMapID); + + /** Set up the data for the view all answers screen */ + LearningController.refreshSummaryData(request, qaContent, qaSession, qaService, sessionMapID, user, + generalLearnerFlowDTO); + + generalLearnerFlowDTO.setReflection(new Boolean(qaContent.isReflect()).toString()); + + boolean lockWhenFinished = qaContent.isLockWhenFinished(); + generalLearnerFlowDTO.setLockWhenFinished(new Boolean(lockWhenFinished).toString()); + generalLearnerFlowDTO.setNoReeditAllowed(qaContent.isNoReeditAllowed()); + + boolean useSelectLeaderToolOuput = qaContent.isUseSelectLeaderToolOuput(); + generalLearnerFlowDTO.setUseSelectLeaderToolOuput(new Boolean(useSelectLeaderToolOuput).toString()); + + boolean allowRichEditor = qaContent.isAllowRichEditor(); + generalLearnerFlowDTO.setAllowRichEditor(new Boolean(allowRichEditor).toString()); + generalLearnerFlowDTO.setUserUid(user.getQueUsrId().toString()); + + boolean usernameVisible = qaContent.isUsernameVisible(); + generalLearnerFlowDTO.setUserNameVisible(usernameVisible); + + NotebookEntry notebookEntry = qaService.getEntry(new Long(toolSessionID), + CoreNotebookConstants.NOTEBOOK_TOOL, QaAppConstants.MY_SIGNATURE, new Integer(userID)); + + if (notebookEntry != null) { + // String notebookEntryPresentable=QaUtils.replaceNewLines(notebookEntry.getEntry()); + String notebookEntryPresentable = notebookEntry.getEntry(); + qaLearningForm.setEntryText(notebookEntryPresentable); + } + + request.setAttribute(QaAppConstants.GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + return "learning/LearnerRep"; + + } else if (qaContent.isReflect()) { + return forwardtoReflection(request, qaContent, toolSessionID, userID, qaLearningForm); + + } else { + return endLearning(qaLearningForm, request, response); + } + } + + /** + * @param qaLearningForm + * @param request + * @return + * @throws IOException + * @throws ServletException + */ + @RequestMapping("/refreshAllResults") + public String refreshAllResults(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request) throws IOException, ServletException { + + LearningUtil.saveFormRequestData(request, qaLearningForm); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + qaLearningForm.setToolSessionID(toolSessionID); + + String userID = request.getParameter("userID"); + QaQueUsr user = qaService.getUserByIdAndSession(new Long(userID), new Long(toolSessionID)); + + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + + QaContent qaContent = qaSession.getQaContent(); + + GeneralLearnerFlowDTO generalLearnerFlowDTO = LearningUtil.buildGeneralLearnerFlowDTO(qaService, qaContent); + + String sessionMapID = qaLearningForm.getSessionMapID(); + qaLearningForm.setSessionMapID(sessionMapID); + generalLearnerFlowDTO.setSessionMapID(sessionMapID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + + /* recreate the users and responses */ + + LearningController.refreshSummaryData(request, qaContent, qaSession, qaService, sessionMapID, user, + generalLearnerFlowDTO); + + generalLearnerFlowDTO.setReflection(new Boolean(qaContent.isReflect()).toString()); + // generalLearnerFlowDTO.setNotebookEntriesVisible(Boolean.FALSE.toString()); + + boolean lockWhenFinished; + boolean noReeditAllowed; + if (sessionMap.get("noRefresh") != null && (boolean) sessionMap.get("noRefresh")) { + lockWhenFinished = true; + noReeditAllowed = true; + } else { + lockWhenFinished = qaContent.isLockWhenFinished(); + noReeditAllowed = qaContent.isNoReeditAllowed(); + } + generalLearnerFlowDTO.setLockWhenFinished(new Boolean(lockWhenFinished).toString()); + generalLearnerFlowDTO.setNoReeditAllowed(noReeditAllowed); + + boolean allowRichEditor = qaContent.isAllowRichEditor(); + generalLearnerFlowDTO.setAllowRichEditor(new Boolean(allowRichEditor).toString()); + + boolean useSelectLeaderToolOuput = qaContent.isUseSelectLeaderToolOuput(); + generalLearnerFlowDTO.setUseSelectLeaderToolOuput(new Boolean(useSelectLeaderToolOuput).toString()); + + QaQueUsr qaQueUsr = getCurrentUser(toolSessionID); + generalLearnerFlowDTO.setUserUid(qaQueUsr.getQueUsrId().toString()); + + boolean usernameVisible = qaContent.isUsernameVisible(); + generalLearnerFlowDTO.setUserNameVisible(usernameVisible); + + request.setAttribute(QaAppConstants.GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + + return "learning/LearnerRep"; + } + + /** + * moves to the next question and modifies the map ActionForward + * + * @param qaLearningForm + * @param request + * @return + * @throws IOException + * @throws ServletException + * @throws ToolException + */ + @RequestMapping("/getNextQuestion") + public String getNextQuestion(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request) throws IOException, ServletException, ToolException { + + LearningUtil.saveFormRequestData(request, qaLearningForm); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + qaLearningForm.setToolSessionID(toolSessionID); + String sessionMapID = qaLearningForm.getSessionMapID(); + qaLearningForm.setSessionMapID(sessionMapID); + + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + QaContent qaContent = qaSession.getQaContent(); + + QaQueUsr qaQueUsr = getCurrentUser(toolSessionID); + //prohibit users from submitting answers after response is finalized but Resubmit button is not pressed (e.g. using 2 browsers) + if (qaQueUsr.isResponseFinalized()) { + String redirectURL = "redirect:learning/LearnerRep.jsp"; + redirectURL = WebUtil.appendParameterToURL(redirectURL, AttributeNames.PARAM_TOOL_SESSION_ID, + toolSessionID.toString()); + redirectURL = WebUtil.appendParameterToURL(redirectURL, QaAppConstants.MODE, "learner"); + return redirectURL; + } + + GeneralLearnerFlowDTO generalLearnerFlowDTO = LearningUtil.buildGeneralLearnerFlowDTO(qaService, qaContent); + storeSequentialAnswer(qaLearningForm, request, generalLearnerFlowDTO, true); + + request.setAttribute("learningForm", qaLearningForm); + return "learning/AnswersContent"; + } + + /** + * Get the answer from the form and copy into DTO. Set up the next question. If the current question is required and + * the answer is blank, then just persist the error and don't change questions. + */ + private Object[] storeSequentialAnswer(QaLearningForm qaLearningForm, HttpServletRequest request, + GeneralLearnerFlowDTO generalLearnerFlowDTO, boolean getNextQuestion) { + String sessionMapID = qaLearningForm.getSessionMapID(); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + + String currentQuestionIndex = qaLearningForm.getCurrentQuestionIndex(); + + Map mapAnswers = (Map) sessionMap.get(QaAppConstants.MAP_ALL_RESULTS_KEY); + if (mapAnswers == null) { + mapAnswers = new TreeMap(new QaComparator()); + } + + String newAnswer = qaLearningForm.getAnswer(); + Map mapSequentialAnswers = (Map) sessionMap + .get(QaAppConstants.MAP_SEQUENTIAL_ANSWERS_KEY); + if (mapSequentialAnswers.size() >= new Integer(currentQuestionIndex).intValue()) { + mapSequentialAnswers.remove(new Long(currentQuestionIndex).toString()); + } + mapSequentialAnswers.put(new Long(currentQuestionIndex).toString(), newAnswer); + mapAnswers.put(currentQuestionIndex, newAnswer); + + int nextQuestionOffset = getNextQuestion ? 1 : -1; + + // validation only if trying to go to the next question + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + if (getNextQuestion) { + errorMap = validateQuestionAnswer(newAnswer, new Integer(currentQuestionIndex), generalLearnerFlowDTO); + } + + // store + if (errorMap.isEmpty()) { + qaService.updateResponseWithNewAnswer(newAnswer, qaLearningForm.getToolSessionID(), + new Integer(currentQuestionIndex), false); + } else { + request.setAttribute("errorMap", errorMap); + nextQuestionOffset = 0; + } + + int intCurrentQuestionIndex = new Integer(currentQuestionIndex).intValue() + nextQuestionOffset; + String currentAnswer = ""; + if (mapAnswers.size() >= intCurrentQuestionIndex) { + currentAnswer = mapAnswers.get(new Long(intCurrentQuestionIndex).toString()); + } + generalLearnerFlowDTO.setCurrentAnswer(currentAnswer); + + // currentQuestionIndex will be: + generalLearnerFlowDTO.setCurrentQuestionIndex(new Integer(intCurrentQuestionIndex)); + + String totalQuestionCount = qaLearningForm.getTotalQuestionCount(); + + int remainingQuestionCount = new Long(totalQuestionCount).intValue() + - new Integer(currentQuestionIndex).intValue(); + String userFeedback = ""; + if (remainingQuestionCount != 0) { + userFeedback = "Remaining question count: " + remainingQuestionCount; + } else { + userFeedback = "End of the questions."; + } + generalLearnerFlowDTO.setUserFeedback(userFeedback); + generalLearnerFlowDTO.setRemainingQuestionCount("" + remainingQuestionCount); + + sessionMap.put(QaAppConstants.MAP_ALL_RESULTS_KEY, mapAnswers); + sessionMap.put(QaAppConstants.MAP_SEQUENTIAL_ANSWERS_KEY, mapSequentialAnswers); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + qaLearningForm.setSessionMapID(sessionMap.getSessionID()); + generalLearnerFlowDTO.setSessionMapID(sessionMap.getSessionID()); + + request.setAttribute(QaAppConstants.GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + + return new Object[] { mapSequentialAnswers, errorMap }; + } + + private MultiValueMap validateQuestionAnswer(String newAnswer, Integer questionIndex, + GeneralLearnerFlowDTO generalLearnerFlowDTO) { + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + + Map questionMap = generalLearnerFlowDTO.getMapQuestionContentLearner(); + QaQuestionDTO dto = questionMap.get(questionIndex); + + // if so, check if the answer is blank and generate an error if it is blank. + boolean isRequiredQuestionMissed = dto.isRequired() && isEmpty(newAnswer); + if (isRequiredQuestionMissed) { + errorMap.add("GLOBAL", messageService.getMessage("error.required", new Object[] { questionIndex })); + } + + boolean isMinWordsLimitReached = ValidationUtil.isMinWordsLimitReached(newAnswer, dto.getMinWordsLimit(), + Boolean.parseBoolean(generalLearnerFlowDTO.getAllowRichEditor())); + if (!isMinWordsLimitReached) { + errorMap.add("GLOBAL", messageService.getMessage("label.minimum.number.words", + ": " + new Object[] { dto.getMinWordsLimit() })); + } + + return errorMap; + } + + /** + * Is this string empty? Need to strip out all HTML tags first otherwise an empty DIV might look like a valid answer + * Smileys and math functions only put in an img tag so explicitly look for that. + */ + private boolean isEmpty(String answer) { + if ((answer != null) && ((answer.indexOf(" -1) || (answer.indexOf(" -1))) { + return false; + } else { + return StringUtils.isBlank(WebUtil.removeHTMLtags(answer)); + } + } + + /** + * moves to the previous question and modifies the map ActionForward + * + * @param qaLearningForm + * @param request + * @return + * @throws IOException + * @throws ServletException + * @throws ToolException + */ + @RequestMapping("/getPreviousQuestion") + public String getPreviousQuestion(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request) throws IOException, ServletException, ToolException { + + LearningUtil.saveFormRequestData(request, qaLearningForm); + + String sessionMapID = qaLearningForm.getSessionMapID(); + qaLearningForm.setSessionMapID(sessionMapID); + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + qaLearningForm.setToolSessionID(toolSessionID); + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + QaContent qaContent = qaSession.getQaContent(); + + QaQueUsr qaQueUsr = getCurrentUser(toolSessionID); + //prohibit users from submitting answers after response is finalized but Resubmit button is not pressed (e.g. using 2 browsers) + if (qaQueUsr.isResponseFinalized()) { + String redirectURL = "redirect:learning/learningIndex.jsp"; + redirectURL = WebUtil.appendParameterToURL(redirectURL, AttributeNames.PARAM_TOOL_SESSION_ID, + toolSessionID.toString()); + redirectURL = WebUtil.appendParameterToURL(redirectURL, QaAppConstants.MODE, "learner"); + return redirectURL; + } + + GeneralLearnerFlowDTO generalLearnerFlowDTO = LearningUtil.buildGeneralLearnerFlowDTO(qaService, qaContent); + storeSequentialAnswer(qaLearningForm, request, generalLearnerFlowDTO, false); + + return "learning/AnswersContent"; + } + + /** + * finishes the user's tool activity + * + * @param request + * @param qaService + * @param response + * @throws IOException + * @throws ToolException + */ + @RequestMapping("/endLearning") + public String endLearning(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException, ToolException { + LearningUtil.saveFormRequestData(request, qaLearningForm); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + qaLearningForm.setToolSessionID(toolSessionID); + + String userID = request.getParameter("userID"); + qaLearningForm.setUserID(userID); + + /* + * The learner is done with the tool session. The tool needs to clean-up. + */ + HttpSession ss = SessionManager.getSession(); + /* get back login user DTO */ + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + + String sessionMapID = qaLearningForm.getSessionMapID(); + // request.getSession().removeAttribute(sessionMapID); + qaLearningForm.setSessionMapID(sessionMapID); + String nextActivityUrl = qaService.finishToolSession(Long.valueOf(toolSessionID), user.getUserID().longValue()); + response.sendRedirect(nextActivityUrl); + + return null; + } + + @RequestMapping("/submitReflection") + public String submitReflection(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException, ToolException { + + LearningUtil.saveFormRequestData(request, qaLearningForm); + + String sessionMapID = qaLearningForm.getSessionMapID(); + + qaLearningForm.setSessionMapID(sessionMapID); + + String toolSessionIDString = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + qaLearningForm.setToolSessionID(toolSessionIDString); + Long toolSessionID = new Long(toolSessionIDString); + + String userIDString = request.getParameter("userID"); + qaLearningForm.setUserID(userIDString); + Integer userID = new Integer(userIDString); + + String reflectionEntry = request.getParameter(QaAppConstants.ENTRY_TEXT); + + // check for existing notebook entry + NotebookEntry entry = qaService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, MY_SIGNATURE, + userID); + + if (entry == null) { + // create new entry + qaService.createNotebookEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + QaAppConstants.MY_SIGNATURE, userID, reflectionEntry); + + } else { + // update existing entry + entry.setEntry(reflectionEntry); + entry.setLastModified(new Date()); + qaService.updateEntry(entry); + } + + return endLearning(qaLearningForm, request, response); + } + + @RequestMapping("/forwardtoReflection") + public String forwardtoReflection(@ModelAttribute("qaLearningForm") QaLearningForm qaLearningForm, + HttpServletRequest request) throws IOException, ServletException, ToolException { + + String sessionMapID = qaLearningForm.getSessionMapID(); + + qaLearningForm.setSessionMapID(sessionMapID); + + String toolSessionID = request.getParameter(AttributeNames.PARAM_TOOL_SESSION_ID); + + QaSession qaSession = qaService.getSessionById(new Long(toolSessionID).longValue()); + + QaContent qaContent = qaSession.getQaContent(); + + String userID = request.getParameter("userID"); + qaLearningForm.setUserID(userID); + + return forwardtoReflection(request, qaContent, toolSessionID, userID, qaLearningForm); + } + + private String forwardtoReflection(HttpServletRequest request, QaContent qaContent, String toolSessionID, + String userID, QaLearningForm reflectionForm) { + + GeneralLearnerFlowDTO generalLearnerFlowDTO = new GeneralLearnerFlowDTO(); + generalLearnerFlowDTO.setActivityTitle(qaContent.getTitle()); + String reflectionSubject = qaContent.getReflectionSubject(); + // reflectionSubject = QaUtils.replaceNewLines(reflectionSubject); + generalLearnerFlowDTO.setReflectionSubject(reflectionSubject); + + // attempt getting notebookEntry + NotebookEntry notebookEntry = qaService.getEntry(new Long(toolSessionID), CoreNotebookConstants.NOTEBOOK_TOOL, + QaAppConstants.MY_SIGNATURE, new Integer(userID)); + + if (notebookEntry != null) { + // String notebookEntryPresentable=QaUtils.replaceNewLines(notebookEntry.getEntry()); + String notebookEntryPresentable = notebookEntry.getEntry(); + generalLearnerFlowDTO.setNotebookEntry(notebookEntryPresentable); + reflectionForm.setEntryText(notebookEntryPresentable); + } + + request.setAttribute(QaAppConstants.GENERAL_LEARNER_FLOW_DTO, generalLearnerFlowDTO); + return "learning/Notebook"; + } + + /** + * populates data for summary screen, view all results screen. + * + * User id is needed if isUserNamesVisible is false && learnerRequest is true, as it is required to work out if the + * data being analysed is the current user. + */ + public static void refreshSummaryData(HttpServletRequest request, QaContent qaContent, QaSession qaSession, + IQaService qaService, String sessionMapID, QaQueUsr user, GeneralLearnerFlowDTO generalLearnerFlowDTO) { + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Long userId = user.getQueUsrId(); + generalLearnerFlowDTO.setUserNameVisible(qaContent.isUsernameVisible()); + + // potentially empty list if the user starts the lesson after the time restriction has expired. + List userResponses = qaService.getResponsesByUserUid(user.getUid()); + + //handle rating criterias + int commentsMinWordsLimit = 0; + boolean isCommentsEnabled = false; + int countRatedQuestions = 0; + if (qaContent.isAllowRateAnswers()) { + + if (userResponses.isEmpty()) { + Set criterias = qaContent.getRatingCriterias(); + for (LearnerItemRatingCriteria criteria : criterias) { + if (criteria.isCommentRating()) { + isCommentsEnabled = true; + break; + } + } + + } else { + // create itemIds list + List itemIds = new LinkedList<>(); + for (QaUsrResp responseIter : userResponses) { + itemIds.add(responseIter.getUid()); + } + + List itemRatingDtos = qaService.getRatingCriteriaDtos(qaContent.getQaContentId(), + qaSession.getQaSessionId(), itemIds, true, userId); + sessionMap.put(AttributeNames.ATTR_ITEM_RATING_DTOS, itemRatingDtos); + + if (itemRatingDtos.size() > 0) { + commentsMinWordsLimit = itemRatingDtos.get(0).getCommentsMinWordsLimit(); + isCommentsEnabled = itemRatingDtos.get(0).isCommentsEnabled(); + } + + //map itemRatingDto to corresponding response + for (QaUsrResp response : userResponses) { + + //find corresponding itemRatingDto + ItemRatingDTO itemRatingDto = null; + for (ItemRatingDTO itemRatingDtoIter : itemRatingDtos) { + if (itemRatingDtoIter.getItemId().equals(response.getUid())) { + itemRatingDto = itemRatingDtoIter; + break; + } + } + + response.setItemRatingDto(itemRatingDto); + } + + // store how many items are rated + countRatedQuestions = qaService.getCountItemsRatedByUser(qaContent.getQaContentId(), userId.intValue()); + } + } + + Set questions = qaContent.getQaQueContents(); + generalLearnerFlowDTO.setQuestions(questions); + //find according QaQueContent, if any + for (QaQueContent question : questions) { + for (QaUsrResp userResponse : userResponses) { + if (question.getUid().equals(userResponse.getQaQuestion().getUid())) { + question.setUserResponse(userResponse); + break; + } + } + } + + request.setAttribute(TOOL_SESSION_ID, qaSession.getQaSessionId()); + sessionMap.put("commentsMinWordsLimit", commentsMinWordsLimit); + sessionMap.put("isCommentsEnabled", isCommentsEnabled); + sessionMap.put(AttributeNames.ATTR_COUNT_RATED_ITEMS, countRatedQuestions); + } + + /** + * Refreshes user list. + */ + @RequestMapping("/getResponses") + public String getResponses(HttpServletRequest request, HttpServletResponse res) + throws IOException, ServletException { + + // teacher timezone + HttpSession ss = SessionManager.getSession(); + UserDTO userDto = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone userTimeZone = userDto.getTimeZone(); + + boolean isAllowRateAnswers = WebUtil.readBooleanParam(request, "isAllowRateAnswers"); + boolean isAllowRichEditor = WebUtil.readBooleanParam(request, "isAllowRichEditor"); + boolean isOnlyLeadersIncluded = WebUtil.readBooleanParam(request, "isOnlyLeadersIncluded", false); + Long qaContentId = WebUtil.readLongParam(request, "qaContentId"); + + Long questionUid = WebUtil.readLongParam(request, "questionUid"); + Long qaSessionId = WebUtil.readLongParam(request, "qaSessionId"); + + //in case of monitoring we show all results. in case of learning - don't show results from the current user + boolean isMonitoring = WebUtil.readBooleanParam(request, "isMonitoring", false); + Long userId = isMonitoring ? -1 : WebUtil.readLongParam(request, "userId"); + + //paging parameters of tablesorter + int size = WebUtil.readIntParam(request, "size"); + int page = WebUtil.readIntParam(request, "page"); + Integer sortByCol1 = WebUtil.readIntParam(request, "column[0]", true); + Integer sortByCol2 = WebUtil.readIntParam(request, "column[1]", true); + String searchString = request.getParameter("fcol[0]"); + + int sorting = QaAppConstants.SORT_BY_NO; + if (sortByCol1 != null) { + if (isMonitoring) { + sorting = sortByCol1.equals(0) ? QaAppConstants.SORT_BY_USERNAME_ASC + : QaAppConstants.SORT_BY_USERNAME_DESC; + } else { + sorting = sortByCol1.equals(0) ? QaAppConstants.SORT_BY_ANSWER_ASC : QaAppConstants.SORT_BY_ANSWER_DESC; + } + + } else if (sortByCol2 != null) { + sorting = sortByCol2.equals(0) ? QaAppConstants.SORT_BY_RATING_ASC : QaAppConstants.SORT_BY_RATING_DESC; + + } else if (!isMonitoring) { + // Is it learner and comment only? If so sort by number of comments. + QaSession qaSession = qaService.getSessionById(qaSessionId); + Set criterias = qaSession.getQaContent().getRatingCriterias(); + boolean hasComment = false; + boolean hasRating = false; + for (LearnerItemRatingCriteria criteria : criterias) { + if (criteria.isCommentRating()) { + hasComment = true; + } else { + hasRating = true; + } + } + if (hasComment && !hasRating) { + sorting = QaAppConstants.SORT_BY_COMMENT_COUNT; + } + } + + List responses = qaService.getResponsesForTablesorter(qaContentId, qaSessionId, questionUid, userId, + isOnlyLeadersIncluded, page, size, sorting, searchString); + + ObjectNode responcedata = JsonNodeFactory.instance.objectNode(); + ArrayNode rows = JsonNodeFactory.instance.arrayNode(); + + responcedata.put("total_rows", 1); + + // handle rating criterias - even though we may have searched on ratings earlier we can't use the average ratings + // calculated as they may have been averages over more than one criteria. + List itemRatingDtos = null; + if (isAllowRateAnswers && !responses.isEmpty()) { + //create itemIds list + List itemIds = new LinkedList<>(); + for (QaUsrResp response : responses) { + itemIds.add(response.getUid()); + } + + //all comments required only for monitoring + boolean isCommentsByOtherUsersRequired = isMonitoring; + itemRatingDtos = qaService.getRatingCriteriaDtos(qaContentId, qaSessionId, itemIds, + isCommentsByOtherUsersRequired, userId); + + // store how many items are rated + int countRatedQuestions = qaService.getCountItemsRatedByUser(qaContentId, userId.intValue()); + responcedata.put(AttributeNames.ATTR_COUNT_RATED_ITEMS, countRatedQuestions); + } + + for (QaUsrResp response : responses) { + QaQueUsr user = response.getQaQueUser(); + + /* + * LDEV-3891: This code has been commented out, as the escapeCsv puts double quotes in the string, which + * goes through to the + * client and wrecks img src entries. It appears the browser cannot process the string with all the double + * quotes. + * This is the second time it is being fixed - the escapeCsv was removed in LDEV-3448 and then added back in + * when Peer Review was added (LDEV-3480). If escapeCsv needs to be added in again, make sure it does not + * break + * learner added images being seen in monitoring. + * //remove leading and trailing quotes + * String answer = StringEscapeUtils.escapeCsv(response.getAnswer()); + * if (isAllowRichEditor && answer.startsWith("\"") && answer.length() >= 3) { + * answer = answer.substring(1, answer.length() - 1); + * } + */ + + ObjectNode responseRow = JsonNodeFactory.instance.objectNode(); + responseRow.put("responseUid", response.getUid().toString()); + responseRow.put("answer", response.getAnswer()); + responseRow.put("userName", StringEscapeUtils.escapeCsv(user.getFullname())); + responseRow.put("visible", new Boolean(response.isVisible()).toString()); + responseRow.put("userID", user.getQueUsrId()); + responseRow.put("portraitId", response.getPortraitId()); + + // format attemptTime - got straight from server time to other timezones in formatter + // as trying to convert dates runs into tz issues - any Date object created is still + // in the server time zone. + Date attemptTime = response.getAttemptTime(); + responseRow.put("attemptTime", DateUtil.convertToStringForJSON(attemptTime, request.getLocale())); + responseRow.put("timeAgo", DateUtil.convertToStringForTimeagoJSON(attemptTime)); + + if (isAllowRateAnswers) { + + //find corresponding itemRatingDto + ItemRatingDTO itemRatingDto = null; + for (ItemRatingDTO itemRatingDtoIter : itemRatingDtos) { + if (response.getUid().equals(itemRatingDtoIter.getItemId())) { + itemRatingDto = itemRatingDtoIter; + break; + } + } + + boolean isItemAuthoredByUser = response.getQaQueUser().getQueUsrId().equals(userId); + responseRow.put("isItemAuthoredByUser", isItemAuthoredByUser); + + ArrayNode criteriasRows = JsonNodeFactory.instance.arrayNode(); + for (ItemRatingCriteriaDTO criteriaDto : itemRatingDto.getCriteriaDtos()) { + ObjectNode criteriasRow = JsonNodeFactory.instance.objectNode(); + criteriasRow.put("ratingCriteriaId", criteriaDto.getRatingCriteria().getRatingCriteriaId()); + criteriasRow.put("title", criteriaDto.getRatingCriteria().getTitle()); + criteriasRow.put("averageRating", criteriaDto.getAverageRating()); + criteriasRow.put("numberOfVotes", criteriaDto.getNumberOfVotes()); + criteriasRow.put("userRating", criteriaDto.getUserRating()); + + criteriasRows.add(criteriasRow); + } + responseRow.set("criteriaDtos", criteriasRows); + + //handle comments + responseRow.put("commentsCriteriaId", itemRatingDto.getCommentsCriteriaId()); + String commentPostedByUser = itemRatingDto.getCommentPostedByUser() == null ? "" + : itemRatingDto.getCommentPostedByUser().getComment(); + responseRow.put("commentPostedByUser", commentPostedByUser); + if (itemRatingDto.getCommentDtos() != null) { + + ArrayNode comments = JsonNodeFactory.instance.arrayNode(); + for (RatingCommentDTO commentDto : itemRatingDto.getCommentDtos()) { + ObjectNode comment = JsonNodeFactory.instance.objectNode(); + comment.put("comment", StringEscapeUtils.escapeCsv(commentDto.getComment())); + + if (isMonitoring) { + // format attemptTime + Date postedDate = commentDto.getPostedDate(); + postedDate = DateUtil.convertToTimeZoneFromDefault(userTimeZone, postedDate); + comment.put("postedDate", DateUtil.convertToStringForJSON(postedDate, request.getLocale())); + + comment.put("userFullName", StringEscapeUtils.escapeCsv(commentDto.getUserFullName())); + } + + comments.add(comment); + } + responseRow.set("comments", comments); + } + } + + rows.add(responseRow); + } + responcedata.set("rows", rows); + + res.setContentType("application/json;charset=utf-8"); + res.getWriter().print(new String(responcedata.toString())); + return null; + } + + private static Map removeNewLinesMap(Map map) { + Map newMap = new TreeMap(new QaStringComparator()); + + Iterator itMap = map.entrySet().iterator(); + while (itMap.hasNext()) { + Map.Entry pairs = (Map.Entry) itMap.next(); + String newText = ""; + if (pairs.getValue().toString() != null) { + newText = pairs.getValue().toString().replaceAll("\n", "
"); + } + newMap.put(pairs.getKey(), newText); + } + return newMap; + } + + private QaQueUsr getCurrentUser(String toolSessionId) { + // get back login user DTO + HttpSession ss = SessionManager.getSession(); + UserDTO toolUser = (UserDTO) ss.getAttribute(AttributeNames.USER); + Integer userId = toolUser.getUserID(); + + QaQueUsr qaUser = qaService.getUserByIdAndSession(userId.longValue(), new Long(toolSessionId)); + if (qaUser == null) { + qaUser = qaService.createUser(new Long(toolSessionId), userId); + } + + return qaUser; + } + +} Index: lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/MonitoringController.java =================================================================== diff -u --- lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/MonitoringController.java (revision 0) +++ lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/MonitoringController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,399 @@ +/*************************************************************************** +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 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.tool.qa.web.controller; + +import java.io.IOException; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.rating.dto.ItemRatingDTO; +import org.lamsfoundation.lams.rating.dto.RatingCommentDTO; +import org.lamsfoundation.lams.rating.model.LearnerItemRatingCriteria; +import org.lamsfoundation.lams.tool.exception.ToolException; +import org.lamsfoundation.lams.tool.qa.QaAppConstants; +import org.lamsfoundation.lams.tool.qa.dto.GroupDTO; +import org.lamsfoundation.lams.tool.qa.dto.QaQuestionDTO; +import org.lamsfoundation.lams.tool.qa.dto.QaStatsDTO; +import org.lamsfoundation.lams.tool.qa.model.QaContent; +import org.lamsfoundation.lams.tool.qa.model.QaQueContent; +import org.lamsfoundation.lams.tool.qa.model.QaQueUsr; +import org.lamsfoundation.lams.tool.qa.model.QaSession; +import org.lamsfoundation.lams.tool.qa.model.QaUsrResp; +import org.lamsfoundation.lams.tool.qa.service.IQaService; +import org.lamsfoundation.lams.tool.qa.util.QaSessionComparator; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.util.HtmlUtils; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Ozgur Demirtas + */ +@Controller +@RequestMapping("/monitoring") +public class MonitoringController implements QaAppConstants { + private static Logger logger = Logger.getLogger(MonitoringController.class.getName()); + + @Autowired + private IQaService qaService; + + @RequestMapping("/monitoring") + private String execute(HttpServletRequest request) throws ServletException { + String contentFolderID = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + + String strToolContentID = request.getParameter(AttributeNames.PARAM_TOOL_CONTENT_ID); + if ((strToolContentID == null) || (strToolContentID.length() == 0)) { + throw new ServletException("No Tool Content ID found"); + } + + QaContent qaContent = qaService.getQaContent(new Long(strToolContentID).longValue()); + if (qaContent == null) { + throw new ServletException("Data not initialised in Monitoring"); + } + + /* this section is related to summary tab. Starts here. */ +// SessionMap sessionMap = new SessionMap(); +// sessionMap.put(ACTIVITY_TITLE_KEY, qaContent.getTitle()); +// sessionMap.put(ACTIVITY_INSTRUCTIONS_KEY, qaContent.getInstructions()); +// +// qaMonitoringForm.setSessionMapID(sessionMap.getSessionID()); +// request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + + List questionDTOs = new LinkedList<>(); + for (QaQueContent question : qaContent.getQaQueContents()) { + QaQuestionDTO questionDTO = new QaQuestionDTO(question); + questionDTOs.add(questionDTO); + } + request.setAttribute(LIST_QUESTIONS, questionDTOs); +// sessionMap.put(LIST_QUESTIONS, questionDTOs); +// request.setAttribute(TOTAL_QUESTION_COUNT, new Integer(questionDTOs.size())); + + //session dto list + List groupDTOs = new LinkedList<>(); + Set sessions = new TreeSet<>(new QaSessionComparator()); + sessions.addAll(qaContent.getQaSessions()); + for (QaSession session : sessions) { + String sessionId = session.getQaSessionId().toString(); + String sessionName = session.getSession_name(); + + GroupDTO groupDTO = new GroupDTO(); + groupDTO.setSessionName(sessionName); + groupDTO.setSessionId(sessionId); + groupDTOs.add(groupDTO); + } + request.setAttribute(LIST_ALL_GROUPS_DTO, groupDTOs); + + // setting up the advanced summary + request.setAttribute(QaAppConstants.ATTR_CONTENT, qaContent); + + boolean isGroupedActivity = qaService.isGroupedActivity(qaContent.getQaContentId()); + request.setAttribute("isGroupedActivity", isGroupedActivity); + + //ratings stuff + boolean isRatingsEnabled = qaService.isRatingsEnabled(qaContent); + request.setAttribute("isRatingsEnabled", isRatingsEnabled); + + //comments stuff + boolean isCommentsEnabled = qaService.isCommentsEnabled(qaContent.getQaContentId()); + request.setAttribute("isCommentsEnabled", isCommentsEnabled); + + //buildQaStatsDTO + QaStatsDTO qaStatsDTO = new QaStatsDTO(); + int countSessionComplete = 0; + int countAllUsers = 0; + Iterator iteratorSession = qaContent.getQaSessions().iterator(); + while (iteratorSession.hasNext()) { + QaSession qaSession = (QaSession) iteratorSession.next(); + + if (qaSession != null) { + + if (qaSession.getSession_status().equals(COMPLETED)) { + ++countSessionComplete; + } + + Iterator iteratorUser = qaSession.getQaQueUsers().iterator(); + while (iteratorUser.hasNext()) { + QaQueUsr qaQueUsr = (QaQueUsr) iteratorUser.next(); + + if (qaQueUsr != null) { + ++countAllUsers; + } + } + } + } + qaStatsDTO.setCountAllUsers(new Integer(countAllUsers).toString()); + qaStatsDTO.setCountSessionComplete(new Integer(countSessionComplete).toString()); + request.setAttribute(QA_STATS_DTO, qaStatsDTO); + + // set SubmissionDeadline, if any + if (qaContent.getSubmissionDeadline() != null) { + Date submissionDeadline = qaContent.getSubmissionDeadline(); + HttpSession ss = SessionManager.getSession(); + UserDTO teacher = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone teacherTimeZone = teacher.getTimeZone(); + Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(teacherTimeZone, submissionDeadline); + request.setAttribute(QaAppConstants.ATTR_SUBMISSION_DEADLINE, tzSubmissionDeadline.getTime()); + // use the unconverted time, as convertToStringForJSON() does the timezone conversion if needed + request.setAttribute(QaAppConstants.ATTR_SUBMISSION_DEADLINE_DATESTRING, + DateUtil.convertToStringForJSON(submissionDeadline, request.getLocale())); + } + + return "monitoring/MonitoringMaincontent"; + } + + @RequestMapping("/updateResponse") + public String updateResponse(HttpServletRequest request) throws IOException, ServletException { + + Long responseUid = WebUtil.readLongParam(request, QaAppConstants.RESPONSE_UID); + String updatedResponse = request.getParameter("updatedResponse"); + QaUsrResp userResponse = qaService.getResponseById(responseUid); + + /* + * write out the audit log entry. If you move this after the update of the response, then make sure you update + * the audit call to use a copy of the original answer + */ + Long toolContentId = userResponse.getQaQueUser().getQaSession().getQaContent().getQaContentId(); + qaService.getLogEventService().logChangeLearnerContent(userResponse.getQaQueUser().getQueUsrId(), + userResponse.getQaQueUser().getUsername(), toolContentId, userResponse.getAnswer(), updatedResponse); + + userResponse.setAnswer(updatedResponse); + qaService.updateUserResponse(userResponse); + + return null; + } + + @RequestMapping("/updateResponseVisibility") + public String updateResponseVisibility(HttpServletRequest request) + throws IOException, ServletException, ToolException { + + Long responseUid = WebUtil.readLongParam(request, QaAppConstants.RESPONSE_UID); + boolean isHideItem = WebUtil.readBooleanParam(request, QaAppConstants.IS_HIDE_ITEM); + qaService.updateResponseVisibility(responseUid, isHideItem); + + return null; + } + + /** + * Set Submission Deadline + */ + @RequestMapping(path = "/setSubmissionDeadline", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String setSubmissionDeadline(HttpServletRequest request) { + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + QaContent content = qaService.getQaContent(contentID); + + Long dateParameter = WebUtil.readLongParam(request, QaAppConstants.ATTR_SUBMISSION_DEADLINE, true); + Date tzSubmissionDeadline = null; + String formattedDate = ""; + if (dateParameter != null) { + Date submissionDeadline = new Date(dateParameter); + HttpSession ss = SessionManager.getSession(); + UserDTO teacher = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone teacherTimeZone = teacher.getTimeZone(); + tzSubmissionDeadline = DateUtil.convertFromTimeZoneToDefault(teacherTimeZone, submissionDeadline); + formattedDate = DateUtil.convertToStringForJSON(tzSubmissionDeadline, request.getLocale()); + } else { + //set showOtherAnswersAfterDeadline to false + content.setShowOtherAnswersAfterDeadline(false); + } + content.setSubmissionDeadline(tzSubmissionDeadline); + qaService.saveOrUpdateQaContent(content); + + return formattedDate; + } + + /** + * Set Submission Deadline + * + * @param mapping + * @param form + * @param request + * @param response + * @return + */ + @RequestMapping("/setShowOtherAnswersAfterDeadline") + public String setShowOtherAnswersAfterDeadline(HttpServletRequest request) { + + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + QaContent content = qaService.getQaContent(contentID); + + boolean showOtherAnswersAfterDeadline = WebUtil.readBooleanParam(request, + QaAppConstants.PARAM_SHOW_OTHER_ANSWERS_AFTER_DEADLINE); + content.setShowOtherAnswersAfterDeadline(showOtherAnswersAfterDeadline); + qaService.saveOrUpdateQaContent(content); + + return null; + } + + /** + * Get Paged Reflections + * + * @param request + * @return + */ + @RequestMapping(path = "/getReflectionsJSON") + @ResponseBody + public String getReflectionsJSON(HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException, ToolException { + + Long toolSessionId = WebUtil.readLongParam(request, QaAppConstants.TOOL_SESSION_ID); + + // paging parameters of tablesorter + int size = WebUtil.readIntParam(request, "size"); + int page = WebUtil.readIntParam(request, "page"); + Integer sortByName = WebUtil.readIntParam(request, "column[0]", true); + String searchString = request.getParameter("fcol[0]"); + + int sorting = QaAppConstants.SORT_BY_NO; + if (sortByName != null) { + sorting = sortByName.equals(0) ? QaAppConstants.SORT_BY_USERNAME_ASC : QaAppConstants.SORT_BY_USERNAME_DESC; + } + + //return user list according to the given sessionID + List users = qaService.getUserReflectionsForTablesorter(toolSessionId, page, size, sorting, + searchString); + + ArrayNode rows = JsonNodeFactory.instance.arrayNode(); + ObjectNode responsedata = JsonNodeFactory.instance.objectNode(); + responsedata.put("total_rows", qaService.getCountUsersBySessionWithSearch(toolSessionId, searchString)); + + for (Object[] userAndReflection : users) { + ObjectNode responseRow = JsonNodeFactory.instance.objectNode(); + responseRow.put("username", HtmlUtils.htmlEscape((String) userAndReflection[1])); + if (userAndReflection.length > 2 && userAndReflection[2] != null) { + String reflection = HtmlUtils.htmlEscape((String) userAndReflection[2]); + responseRow.put(QaAppConstants.NOTEBOOK, reflection.replaceAll("\n", "
")); + } + rows.add(responseRow); + } + responsedata.set("rows", rows); + response.setContentType("application/json;charset=UTF-8"); + return responsedata.toString(); + } + + /** + * Start to download the page that has an HTML version of the answers. Calls answersDownload + * which forwards to the jsp to download the file. + * + * @throws ServletException + */ + @RequestMapping("/getPrintAnswers") + public String getPrintAnswers(HttpServletRequest request) throws ServletException { + + Long allUserIdValue = -1L; + + Long toolSessionID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); + QaSession qaSession = qaService.getSessionById(toolSessionID); + QaContent qaContent = qaSession.getQaContent(); + + Long questionUid = WebUtil.readLongParam(request, "questionUid"); + QaQueContent question = null; + for (QaQueContent check : qaContent.getQaQueContents()) { + if (check.getUid().equals(questionUid)) { + question = check; + break; + } + } + + if (question == null) { + logger.error("Cannot display printable answers as we cannot find question details for toolSessionId " + + toolSessionID + " questionUid " + questionUid); + throw new ServletException("Question details missing."); + } + + QaQuestionDTO questionDTO = new QaQuestionDTO(question); + request.setAttribute(QaAppConstants.QUESTION_DTO, questionDTO); + + List responses = qaService.getResponsesForTablesorter(qaContent.getQaContentId(), toolSessionID, + questionUid, allUserIdValue, qaContent.isUseSelectLeaderToolOuput(), 1, 0, + QaAppConstants.SORT_BY_USERNAME_ASC, null); + request.setAttribute(QaAppConstants.RESPONSES, responses); + request.setAttribute(QaAppConstants.ATTR_CONTENT, qaContent); + + boolean isAllowRateAnswers = qaContent.isAllowRateAnswers(); + boolean isCommentsEnabled = false; + if (isAllowRateAnswers) { + Set criterias = qaContent.getRatingCriterias(); + for (LearnerItemRatingCriteria criteria : criterias) { + if (criteria.isCommentRating()) { + isCommentsEnabled = true; + break; + } + } + } + request.setAttribute("isCommentsEnabled", isCommentsEnabled); + + // handle rating criterias - even though we may have searched on ratings earlier we can't use the average ratings + // calculated as they may have been averages over more than one criteria. + Map criteriaMap = null; + Map> commentMap = null; + if (isAllowRateAnswers && !responses.isEmpty()) { + //create itemIds list + List itemIds = new LinkedList<>(); + for (QaUsrResp usrResponse : responses) { + itemIds.add(usrResponse.getUid()); + } + List itemRatingDtos = qaService.getRatingCriteriaDtos(qaContent.getQaContentId(), + toolSessionID, itemIds, true, allUserIdValue); + if (itemRatingDtos.size() > 0) { + criteriaMap = new HashMap<>(); + commentMap = new HashMap<>(); + for (ItemRatingDTO itemRatingDto : itemRatingDtos) { + criteriaMap.put(itemRatingDto.getItemId(), itemRatingDto.getCriteriaDtos()); + commentMap.put(itemRatingDto.getItemId(), itemRatingDto.getCommentDtos()); + } + } + } + request.setAttribute("criteriaMap", criteriaMap); + request.setAttribute("commentMap", commentMap); + request.setAttribute(QaAppConstants.ATTR_CONTENT, qaContent); + + return "monitoring/PrintAnswers"; + } + +} \ No newline at end of file Index: lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/PedagogicalPlannerController.java =================================================================== diff -u --- lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/PedagogicalPlannerController.java (revision 0) +++ lams_tool_laqa/src/java/org/lamsfoundation/lams/tool/qa/web/controller/PedagogicalPlannerController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -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 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.tool.qa.web.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.lamsfoundation.lams.qb.model.QbQuestion; +import org.lamsfoundation.lams.tool.qa.model.QaContent; +import org.lamsfoundation.lams.tool.qa.model.QaQueContent; +import org.lamsfoundation.lams.tool.qa.service.IQaService; +import org.lamsfoundation.lams.tool.qa.web.form.QaPedagogicalPlannerForm; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@RequestMapping("/pedagogicalPlanner") +public class PedagogicalPlannerController { + + @Autowired + private IQaService qaService; + + @RequestMapping("") + protected String unspecified(QaPedagogicalPlannerForm pedagogicalPlannerForm, HttpServletRequest request, + HttpServletResponse response) { + return initPedagogicalPlannerForm(pedagogicalPlannerForm, request); + } + + @RequestMapping("/initPedagogicalPlannerForm") + public String initPedagogicalPlannerForm(QaPedagogicalPlannerForm pedagogicalPlannerForm, + HttpServletRequest request) { + Long toolContentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + QaContent qaContent = qaService.getQaContent(toolContentID); + pedagogicalPlannerForm.fillForm(qaContent); + String contentFolderId = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); + pedagogicalPlannerForm.setContentFolderID(contentFolderId); + return "authoring/pedagogicalPlannerForm"; + } + + @RequestMapping(value = "/saveOrUpdatePedagogicalPlannerForm", method = RequestMethod.POST) + public String saveOrUpdatePedagogicalPlannerForm(QaPedagogicalPlannerForm pedagogicalPlannerForm, + HttpServletRequest request) throws IOException { + + MultiValueMap errorMap = pedagogicalPlannerForm.validate(); + if (errorMap.isEmpty()) { + + QaContent qaContent = qaService.getQaContent(pedagogicalPlannerForm.getToolContentID()); + + int questionIndex = 0; + String question = null; + + do { + question = pedagogicalPlannerForm.getQuestion(questionIndex); + if (StringUtils.isEmpty(question)) { + pedagogicalPlannerForm.removeQuestion(questionIndex); + } else { + if (questionIndex < qaContent.getQaQueContents().size()) { + QaQueContent qaQuestion = qaService.getQuestionByContentAndDisplayOrder(questionIndex + 1, + qaContent.getUid()); + qaQuestion.getQbQuestion().setName(question); + qaService.saveOrUpdate(qaQuestion.getQbQuestion()); + + } else { + QaQueContent qaQuestion = new QaQueContent(); + qaQuestion.setDisplayOrder(questionIndex + 1); + qaQuestion.setToolContentId(qaContent.getQaContentId()); + + QbQuestion qbQuestion = new QbQuestion(); + qbQuestion.setAnswerRequired(false); + qbQuestion.setName(question); + qaService.saveOrUpdate(qbQuestion); + + qaQuestion.setQbQuestion(qbQuestion); + qaQuestion.setQaContent(qaContent); + qaService.saveOrUpdate(qaQuestion); + } + questionIndex++; + } + } while (question != null); + if (questionIndex < qaContent.getQaQueContents().size()) { + qaService.removeQuestionsFromCache(qaContent); + qaService.removeQaContentFromCache(qaContent); + for (; questionIndex < qaContent.getQaQueContents().size(); questionIndex++) { + QaQueContent qaQuestion = qaService.getQuestionByContentAndDisplayOrder(questionIndex + 1, + qaContent.getUid()); + qaService.removeQuestion(qaQuestion); + } + } + } else { + request.setAttribute("errorMap", errorMap); + } + return "authoring/pedagogicalPlannerForm"; + } + + @RequestMapping("/createPedagogicalPlannerQuestion") + public String createPedagogicalPlannerQuestion(QaPedagogicalPlannerForm pedagogicalPlannerForm) { + pedagogicalPlannerForm.setQuestion(pedagogicalPlannerForm.getQuestionCount().intValue(), ""); + return "authoring/pedagogicalPlannerForm"; + } + +} \ No newline at end of file Index: lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/AuthoringController.java =================================================================== diff -u --- lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/AuthoringController.java (revision 0) +++ lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/AuthoringController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,234 @@ +/**************************************************************** + * 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.tool.noticeboard.web.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.noticeboard.NoticeboardConstants; +import org.lamsfoundation.lams.tool.noticeboard.model.NoticeboardContent; +import org.lamsfoundation.lams.tool.noticeboard.service.INoticeboardService; +import org.lamsfoundation.lams.tool.noticeboard.service.NbApplicationException; +import org.lamsfoundation.lams.tool.noticeboard.web.form.NbAuthoringForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.CommonConstants; +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.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + *

+ * The unspecified method will get called on first entry into the authoring environment. + *

+ * + * @author mtruong + */ +@Controller +@RequestMapping("/authoring") +public class AuthoringController { + + private static Logger logger = Logger.getLogger(AuthoringController.class.getName()); + public final static String FORM = "NbAuthoringForm"; + + @Autowired + private INoticeboardService nbService; + + @Autowired + @Qualifier("nbMessageService") + private MessageService messageService; + + /** Get the user from the shared session */ + public UserDTO getUser(HttpServletRequest request) { + // set up the user details + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + if (user == null) { + String error = messageService.getMessage(NoticeboardConstants.ERR_MISSING_PARAM, "User"); + logger.error(error); + throw new NbApplicationException(error); + } + return user; + } + + @RequestMapping("/authoring") + public String unspecified(@ModelAttribute NbAuthoringForm nbAuthoringForm, HttpServletRequest request, + HttpServletResponse response) { + ToolAccessMode mode = WebUtil.readToolAccessModeAuthorDefaulted(request); + return readDatabaseData(nbAuthoringForm, request, mode); + } + + /** + * Set the defineLater flag so that learners cannot use content while we are editing. This flag is released when + * updateContent is called. + */ + @RequestMapping(path = "/definelater", method = RequestMethod.POST) + public String definelater(@ModelAttribute NbAuthoringForm nbAuthoringForm, HttpServletRequest request) { + Long contentId = WebUtil.readLongParam(request, NoticeboardConstants.TOOL_CONTENT_ID); + NoticeboardContent nb = nbService.retrieveNoticeboard(contentId); + nb.setDefineLater(true); + nbService.saveNoticeboard(nb); + + // audit log the teacher has started editing activity in monitor + nbService.auditLogStartEditingActivityInMonitor(contentId); + + return readDatabaseData(nbAuthoringForm, request, ToolAccessMode.TEACHER); + } + + /** + * Common method for "unspecified" and "defineLater" + */ + private String readDatabaseData(NbAuthoringForm nbAuthoringForm, HttpServletRequest request, ToolAccessMode mode) { + Long contentId = WebUtil.readLongParam(request, NoticeboardConstants.TOOL_CONTENT_ID); + String contentFolderId = WebUtil.readStrParam(request, NoticeboardConstants.CONTENT_FOLDER_ID); + + nbAuthoringForm.setToolContentID(contentId.toString()); + + /* + * DefineLater is used in the basic screen. If defineLater is set, then in the authoring page, + * the two tabs {Advanced, Instructions} are not visible. + */ + nbAuthoringForm.setDefineLater(request.getParameter(NoticeboardConstants.DEFINE_LATER)); + + if (!contentExists(nbService, contentId)) { + // Pre-fill the form with the default content + //NoticeboardContent nb = nbService.retrieveNoticeboard(NoticeboardConstants.DEFAULT_CONTENT_ID); + Long defaultToolContentId = nbService + .getToolDefaultContentIdBySignature(NoticeboardConstants.TOOL_SIGNATURE); + // logger.debug("Default tool content id is " + defaultToolContentId); + NoticeboardContent nb = nbService.retrieveNoticeboard(defaultToolContentId); + + if (nb == null) { + String error = "There is data missing in the database"; + logger.error(error); + throw new NbApplicationException(error); + } + + //initialise the values in the form, so the values will be shown in the jsp + nbAuthoringForm.setToolContentID(contentId.toString()); + nbAuthoringForm.setContentFolderID(contentFolderId); + nbAuthoringForm.setTitle(nb.getTitle()); + nbAuthoringForm.setBasicContent(nb.getContent()); + + //content already exists on the database + } else { + //get the values from the database + NoticeboardContent nb = nbService.retrieveNoticeboard(contentId); + + /* + * If retrieving existing content, check whether the contentInUse flag is set, if set, the + * author is not allowed to edit content + */ + + /* + * Define later set to true when the edit activity tab is brought up + * So that users cannot start using the content while the staff member is editing the content + */ + nbAuthoringForm.populateFormWithNbContentValues(nb); + nbAuthoringForm.setContentFolderID(contentFolderId); + boolean isDefineLater = Boolean.parseBoolean(nbAuthoringForm.getDefineLater()); + nb.setDefineLater(isDefineLater); + nbService.saveNoticeboard(nb); + } + request.setAttribute(AttributeNames.ATTR_MODE, mode); + + request.setAttribute(FORM, nbAuthoringForm); + + return "authoring/authoring"; + } + + /** + * Checks the session to see if the title and content session variables exist or not. + */ + private boolean contentExists(INoticeboardService service, Long id) { + NoticeboardContent nb = service.retrieveNoticeboard(id); + if (nb == null) { + return false; + } else { + return true; + } + + } + + @RequestMapping(value = "/save", method = RequestMethod.POST) + public String save(@ModelAttribute NbAuthoringForm nbAuthoringForm, HttpServletRequest request) { + //copyAuthoringFormValuesIntoFormBean(request, nbForm); + String idAsString = nbAuthoringForm.getToolContentID(); + + if (idAsString == null) { + String error = messageService.getMessage("error.missingParam"); + logger.error(error); + throw new NbApplicationException(error); + } + + Long content_id = Long.valueOf(nbAuthoringForm.getToolContentID()); + + //throws exception if the content id does not exist + checkContentId(content_id); + + NoticeboardContent nbContent = nbService.retrieveNoticeboard(content_id); + if (nbContent == null) { + //create a new noticeboard object + nbContent = new NoticeboardContent(); + nbContent.setNbContentId(content_id); + } + + nbAuthoringForm.copyValuesIntoNbContent(nbContent); + if (nbContent.getDateCreated() == null) { + nbContent.setDateCreated(nbContent.getDateUpdated()); + } + + UserDTO user = getUser(request); + nbContent.setCreatorUserId(new Long(user.getUserID().longValue())); + + // Author has finished editing the content and mark the defineLater flag to false + nbContent.setDefineLater(false); + nbService.saveNoticeboard(nbContent); + + request.setAttribute(CommonConstants.LAMS_AUTHORING_SUCCESS_FLAG, Boolean.TRUE); + return "authoring/authoring"; + } + + /** + * It is assumed that the contentId is passed as a http parameter + * if the contentId is null, an exception is thrown, otherwise proceed as normal + */ + private void checkContentId(Long contentId) { + if (contentId == null) { + String error = "Unable to continue. Tool content id missing."; + + throw new NbApplicationException(error); + } + } + +} Index: lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/LearningController.java =================================================================== diff -u --- lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/LearningController.java (revision 0) +++ lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/LearningController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,367 @@ +/**************************************************************** + * 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.tool.noticeboard.web.controller; + +import java.io.IOException; +import java.util.Date; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.ToolSessionManager; +import org.lamsfoundation.lams.tool.exception.DataMissingException; +import org.lamsfoundation.lams.tool.exception.ToolException; +import org.lamsfoundation.lams.tool.noticeboard.NoticeboardConstants; +import org.lamsfoundation.lams.tool.noticeboard.model.NoticeboardContent; +import org.lamsfoundation.lams.tool.noticeboard.model.NoticeboardSession; +import org.lamsfoundation.lams.tool.noticeboard.model.NoticeboardUser; +import org.lamsfoundation.lams.tool.noticeboard.service.INoticeboardService; +import org.lamsfoundation.lams.tool.noticeboard.service.NbApplicationException; +import org.lamsfoundation.lams.tool.noticeboard.web.form.NbLearnerForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +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.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Creation Date: 27-06-05 + * + * The learner url can be of three modes learner, teacher or author. Depending on + * what mode was set, it will trigger the corresponding action. If the mode parameter + * is missing or a key is not found in the keymap, it will call the unspecified method + * which defaults to the learner action. + * + *

+ * The difference between author mode (which is basically the preview) + * is that if the defineLater flag is set, it will not be able to see the noticeboard screen + *

+ */ +@Controller +@RequestMapping("/learning") +public class LearningController { + + static Logger logger = Logger.getLogger(LearningController.class.getName()); + + @Autowired + private INoticeboardService nbService; + + @Autowired + @Qualifier("nbMessageService") + private MessageService messageService; + + private UserDTO getUserDTO(HttpServletRequest request) { + // set up the user details + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + if (user == null) { + String error = messageService.getMessage(NoticeboardConstants.ERR_MISSING_PARAM, "User"); + logger.error(error); + throw new NbApplicationException(error); + } + return user; + } + + /** Get the user id from the shared session */ + public Long getUserID(HttpServletRequest request) { + UserDTO user = getUserDTO(request); + return new Long(user.getUserID().longValue()); + } + + /** Get the user id from the url - needed for the monitoring mode */ + public Long getUserIDFromURLCall(HttpServletRequest request) { + return WebUtil.readLongParam(request, AttributeNames.PARAM_USER_ID, false); + } + + public String unspecified(@ModelAttribute NbLearnerForm NbLearnerForm, HttpServletRequest request, + HttpServletResponse response) { + + return learner(NbLearnerForm, request, response); + } + + @RequestMapping("/learner") + public String learner(@ModelAttribute NbLearnerForm NbLearnerForm, HttpServletRequest request, + HttpServletResponse response) { + + NoticeboardContent nbContent = null; + NoticeboardUser nbUser = null; + + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + + Long toolSessionID = Long.valueOf(NbLearnerForm.getToolSessionID()); + + if (toolSessionID == null) { + String error = "Unable to continue. The parameters tool session id is missing"; + logger.error(error); + throw new NbApplicationException(error); + } + + nbContent = nbService.retrieveNoticeboardBySessionID(toolSessionID); + // nbSession = nbService.retrieveNoticeboardSession(toolSessionId); + + if (nbContent == null) { + String error = "An Internal error has occurred. Please exit and retry this sequence"; + logger.error(error); + throw new NbApplicationException(error); + } + + if (isFlagSet(nbContent, NoticeboardConstants.FLAG_DEFINE_LATER)) { + return "defineLater"; + } + + boolean readOnly = false; + if (NbLearnerForm.getMode() == null) { + NbLearnerForm.setMode(ToolAccessMode.LEARNER.toString()); + } + ToolAccessMode mode = WebUtil.getToolAccessMode(NbLearnerForm.getMode()); + Long userID = null; + if (mode == ToolAccessMode.LEARNER || mode == ToolAccessMode.AUTHOR) { + userID = getUserID(request); + nbUser = nbService.retrieveNbUserBySession(userID, toolSessionID); + + if (!nbContent.isContentInUse()) { + /* Set the ContentInUse flag to true, and defineLater flag to false */ + nbContent.setContentInUse(true); + nbService.saveNoticeboard(nbContent); + } + + if (nbUser == null) { + //create a new user with this session id + nbUser = new NoticeboardUser(userID); + UserDTO user = getUserDTO(request); + nbUser.setUsername(user.getLogin()); + nbUser.setFullname(user.getFirstName() + " " + user.getLastName()); + nbService.addUser(toolSessionID, nbUser); + } + } else { + // user will not exist if force completed. + userID = getUserIDFromURLCall(request); + nbUser = nbService.retrieveNbUserBySession(userID, toolSessionID); + readOnly = true; + } + + NbLearnerForm.copyValuesIntoForm(nbContent, readOnly, mode.toString()); + + NotebookEntry notebookEntry = nbService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + NoticeboardConstants.TOOL_SIGNATURE, userID.intValue()); + if (notebookEntry != null) { + request.setAttribute("reflectEntry", notebookEntry.getEntry()); + } + request.setAttribute("reflectInstructions", nbContent.getReflectInstructions()); + request.setAttribute("reflectOnActivity", nbContent.getReflectOnActivity()); + request.setAttribute("allowComments", nbContent.isAllowComments()); + request.setAttribute("likeAndDislike", nbContent.isCommentsLikeAndDislike()); + request.setAttribute("anonymous", nbContent.isAllowAnonymous()); + + Boolean userFinished = (nbUser != null && NoticeboardUser.COMPLETED.equals(nbUser.getUserStatus())); + request.setAttribute("userFinished", userFinished); + + request.setAttribute(AttributeNames.ATTR_IS_LAST_ACTIVITY, nbService.isLastActivity(toolSessionID)); + + /* + * Checks to see if the runOffline flag is set. + * If the particular flag is set, control is forwarded to jsp page + * displaying to the user the message according to what flag is set. + */ + if (displayMessageToUser(nbContent, errorMap)) { + request.setAttribute("errorMap", errorMap); + return "message"; + } + + return "learnerContent"; + + } + + @RequestMapping("/teacher") + public String teacher(@ModelAttribute NbLearnerForm NbLearnerForm, HttpServletRequest request, + HttpServletResponse response) throws NbApplicationException { + NbLearnerForm.setMode("teacher"); + return learner(NbLearnerForm, request, response); + } + + @RequestMapping("/author") + public String author(@ModelAttribute NbLearnerForm NbLearnerForm, HttpServletRequest request, + HttpServletResponse response) throws NbApplicationException { + NbLearnerForm.setMode("author"); + return learner(NbLearnerForm, request, response); + + } + + /** + *

+ * Performs a check on the flag indicated by flag + * In this noticeboard tool, there are 3 possible flags: + *

  • defineLater
  • + *
  • contentInUse
  • + *
    + * Returns true if the flag is set, false otherwise + */ + private boolean isFlagSet(NoticeboardContent content, int flag) throws NbApplicationException { + switch (flag) { + case NoticeboardConstants.FLAG_DEFINE_LATER: + return (content.isDefineLater()); + // break; + case NoticeboardConstants.FLAG_CONTENT_IN_USE: + return (content.isContentInUse()); + // break; + default: + throw new NbApplicationException("Invalid flag"); + } + + } + + /** + *

    + * This methods checks the defineLater and runOffline flag. + * If defineLater flag is set, then a message is added to an ActionMessages + * object saying that the contents have not been defined yet. If the runOffline + * flag is set, a message is added to ActionMessages saying that the contents + * are not being run online. + * This method will return true if any one of the defineLater or runOffline flag is set. + * Otherwise false will be returned. + *

    + */ + private boolean displayMessageToUser(NoticeboardContent content, MultiValueMap errorMap) { + boolean isDefineLaterSet = isFlagSet(content, NoticeboardConstants.FLAG_DEFINE_LATER); + errorMap = new LinkedMultiValueMap<>(); + if (isDefineLaterSet) { + if (isDefineLaterSet) { + errorMap.add("GLOBAL", messageService.getMessage("message.defineLaterSet")); + } + return true; + } else { + return false; + } + } + + /** + * Indicates that the user has finished viewing the noticeboard. + * The session is set to complete and leaveToolSession is called. + */ + @RequestMapping("/finish") + public String finish(@ModelAttribute NbLearnerForm nbLearnerForm, HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + + Long userID = getUserID(request); + + Long toolSessionID = Long.valueOf(nbLearnerForm.getToolSessionID()); + if (toolSessionID == null) { + String error = "Unable to continue. The parameters tool session id is missing"; + logger.error(error); + throw new NbApplicationException(error); + } + + ToolSessionManager sessionMgrService = (ToolSessionManager) nbService; + + ToolAccessMode mode = WebUtil.getToolAccessMode(nbLearnerForm.getMode()); + if (mode == ToolAccessMode.LEARNER || mode == ToolAccessMode.AUTHOR) { + NoticeboardSession nbSession = nbService.retrieveNoticeboardSession(toolSessionID); + NoticeboardUser nbUser = nbService.retrieveNbUserBySession(userID, toolSessionID); + + nbUser.setUserStatus(NoticeboardUser.COMPLETED); + nbService.updateNoticeboardSession(nbSession); + nbService.updateNoticeboardUser(nbUser); + + // Create the notebook entry if reflection is set. + NoticeboardContent nbContent = nbService.retrieveNoticeboardBySessionID(toolSessionID); + if (nbContent.getReflectOnActivity()) { + // check for existing notebook entry + NotebookEntry entry = nbService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + NoticeboardConstants.TOOL_SIGNATURE, userID.intValue()); + + if (entry == null) { + // create new entry + nbService.createNotebookEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + NoticeboardConstants.TOOL_SIGNATURE, userID.intValue(), nbLearnerForm.getReflectionText()); + } else { + // update existing entry + entry.setEntry(nbLearnerForm.getReflectionText()); + entry.setLastModified(new Date()); + nbService.updateEntry(entry); + } + } + + String nextActivityUrl; + try { + nextActivityUrl = sessionMgrService.leaveToolSession(toolSessionID, getUserID(request)); + } catch (DataMissingException e) { + logger.error(e); + throw new ServletException(e); + } catch (ToolException e) { + logger.error(e); + throw new ServletException(e); + } + + response.sendRedirect(nextActivityUrl); + + return null; + + } + request.setAttribute(NoticeboardConstants.READ_ONLY_MODE, "true"); + + return "learnerContent"; + + } + + /** + * Indicates that the user has finished viewing the noticeboard, and will be + * passed onto the Notebook reflection screen. + */ + @RequestMapping(path = "/reflect", method = RequestMethod.POST) + public String reflect(@ModelAttribute NbLearnerForm nbLearnerForm, HttpServletRequest request) { + + Long toolSessionID = Long.valueOf(nbLearnerForm.getToolSessionID()); + NoticeboardContent nbContent = nbService.retrieveNoticeboardBySessionID(toolSessionID); + request.setAttribute("reflectInstructions", nbContent.getReflectInstructions()); + request.setAttribute("title", nbContent.getTitle()); + request.setAttribute("allowComments", nbContent.isAllowComments()); + request.setAttribute("likeAndDislike", nbContent.isCommentsLikeAndDislike()); + request.setAttribute("anonymous", nbContent.isAllowAnonymous()); + request.setAttribute("toolSessionID", toolSessionID); + // get the existing reflection entry + NotebookEntry entry = nbService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + NoticeboardConstants.TOOL_SIGNATURE, getUserID(request).intValue()); + if (entry != null) { + request.setAttribute("reflectEntry", entry.getEntry()); + } + + request.setAttribute(AttributeNames.ATTR_IS_LAST_ACTIVITY, nbService.isLastActivity(toolSessionID)); + return "reflect"; + } + +} \ No newline at end of file Index: lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/MonitoringController.java =================================================================== diff -u --- lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/MonitoringController.java (revision 0) +++ lams_tool_nb/src/java/org/lamsfoundation/lams/tool/noticeboard/web/controller/MonitoringController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,165 @@ +/**************************************************************** + * 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.tool.noticeboard.web.controller; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; +import org.lamsfoundation.lams.tool.noticeboard.NoticeboardConstants; +import org.lamsfoundation.lams.tool.noticeboard.dto.ReflectionDTO; +import org.lamsfoundation.lams.tool.noticeboard.model.NoticeboardContent; +import org.lamsfoundation.lams.tool.noticeboard.model.NoticeboardSession; +import org.lamsfoundation.lams.tool.noticeboard.model.NoticeboardUser; +import org.lamsfoundation.lams.tool.noticeboard.service.INoticeboardService; +import org.lamsfoundation.lams.tool.noticeboard.service.NbApplicationException; +import org.lamsfoundation.lams.tool.noticeboard.web.form.MonitoringDTO; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * The buttons are a switch between tabs and will forward to a jsp and display + * the appropriate page. + * + * @author mtruong + */ +@Controller +@RequestMapping("/monitoring") +public class MonitoringController { + static Logger logger = Logger.getLogger(MonitoringController.class.getName()); + + @Autowired + private INoticeboardService nbService; + + public final static String FORM = "MonitoringDTO"; + + @RequestMapping("/monitoring") + public String unspecified(HttpServletRequest request) { + Long toolContentId = WebUtil.readLongParam(request, NoticeboardConstants.TOOL_CONTENT_ID); + String contentFolderID = WebUtil.readStrParam(request, NoticeboardConstants.CONTENT_FOLDER_ID); + + if (toolContentId == null) { + String error = "Unable to continue. Tool content id missing"; + logger.error(error); + throw new NbApplicationException(error); + } + + NoticeboardContent content = nbService.retrieveNoticeboard(toolContentId); + + MonitoringDTO monitoringDTO = new MonitoringDTO(); + request.setAttribute("monitoringDTO", monitoringDTO); + monitoringDTO.setTitle(content.getTitle()); + monitoringDTO.setBasicContent(content.getContent()); + + request.setAttribute(NoticeboardConstants.TOOL_CONTENT_ID, toolContentId); + request.setAttribute(NoticeboardConstants.CONTENT_FOLDER_ID, contentFolderID); + + //Get the total number of learners that have participated in this tool activity + monitoringDTO.setTotalLearners(nbService.calculateTotalNumberOfUsers(toolContentId)); + + Set sessions = content.getNbSessions(); + Iterator i = sessions.iterator(); + Map numUsersMap = new HashMap(); + Map sessionIdMap = new HashMap(); + List reflections = new ArrayList<>(); + while (i.hasNext()) { + NoticeboardSession session = (NoticeboardSession) i.next(); + int numUsersInSession = nbService.getNumberOfUsersInSession(session); + numUsersMap.put(session.getNbSessionName(), new Integer(numUsersInSession)); + sessionIdMap.put(session.getNbSessionName(), session.getNbSessionId()); + // Get list of users that have made a reflection entry + if (content.getReflectOnActivity()) { + List sessionUsers = nbService.getUsersBySession(session.getNbSessionId()); + for (int j = 0; j < sessionUsers.size(); j++) { + NoticeboardUser nbUser = (NoticeboardUser) sessionUsers.get(j); + NotebookEntry nbEntry = nbService.getEntry(session.getNbSessionId(), + CoreNotebookConstants.NOTEBOOK_TOOL, NoticeboardConstants.TOOL_SIGNATURE, + nbUser.getUserId().intValue()); + if (nbEntry != null) { + ReflectionDTO dto = new ReflectionDTO(nbEntry); + dto.setExternalId(session.getNbSessionId()); + dto.setUserId(nbUser.getUserId()); + dto.setUsername(nbUser.getUsername()); + reflections.add(dto); + } + } + } + } + monitoringDTO.setGroupStatsMap(numUsersMap); + monitoringDTO.setSessionIdMap(sessionIdMap); + + boolean isGroupedActivity = nbService.isGroupedActivity(toolContentId); + request.setAttribute("isGroupedActivity", isGroupedActivity); + + // Set reflection statistics, if reflection is set + request.setAttribute("reflectOnActivity", content.getReflectOnActivity()); + request.setAttribute("reflectInstructions", content.getReflectInstructions()); + request.setAttribute("reflections", reflections); + + request.setAttribute("allowComments", content.isAllowComments()); + + String currentTab = WebUtil.readStrParam(request, AttributeNames.PARAM_CURRENT_TAB, true); + monitoringDTO.setCurrentTab(currentTab != null ? currentTab : "1"); + + return "/monitoring/monitoring"; + } + + @RequestMapping("/viewReflection") + public String viewReflection(HttpServletRequest request) { + Long userId = WebUtil.readLongParam(request, NoticeboardConstants.USER_ID); + Long toolSessionId = WebUtil.readLongParam(request, NoticeboardConstants.TOOL_SESSION_ID); + NoticeboardUser nbUser = nbService.retrieveNoticeboardUser(userId, toolSessionId); + NotebookEntry nbEntry = nbService.getEntry(nbUser.getNbSession().getNbSessionId(), + CoreNotebookConstants.NOTEBOOK_TOOL, NoticeboardConstants.TOOL_SIGNATURE, userId.intValue()); + if (nbEntry != null) { + request.setAttribute("nbEntry", nbEntry.getEntry()); + request.setAttribute("name", nbUser.getFullname()); + } + + return "/monitoring/reflection"; + } + + @RequestMapping("/viewComments") + public String viewComments(HttpServletRequest request) { + Long toolSessionID = WebUtil.readLongParam(request, NoticeboardConstants.TOOL_SESSION_ID, false); + NoticeboardContent nbContent = nbService.retrieveNoticeboardBySessionID(toolSessionID); + + request.setAttribute(NoticeboardConstants.TOOL_SESSION_ID, toolSessionID); + request.setAttribute("anonymous", nbContent.isAllowAnonymous()); + return "monitoring/comments"; + } + +} Index: lams_tool_sbmt/src/java/org/lamsfoundation/lams/tool/sbmt/web/controller/LearningController.java =================================================================== diff -u --- lams_tool_sbmt/src/java/org/lamsfoundation/lams/tool/sbmt/web/controller/LearningController.java (revision 0) +++ lams_tool_sbmt/src/java/org/lamsfoundation/lams/tool/sbmt/web/controller/LearningController.java (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -0,0 +1,613 @@ +/**************************************************************** + * 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.tool.sbmt.web.controller; + +import java.io.IOException; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.SortedMap; +import java.util.TimeZone; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.events.IEventNotificationService; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.ToolSessionManager; +import org.lamsfoundation.lams.tool.exception.DataMissingException; +import org.lamsfoundation.lams.tool.exception.ToolException; +import org.lamsfoundation.lams.tool.sbmt.SbmtConstants; +import org.lamsfoundation.lams.tool.sbmt.dto.FileDetailsDTO; +import org.lamsfoundation.lams.tool.sbmt.dto.SubmitUserDTO; +import org.lamsfoundation.lams.tool.sbmt.model.SubmitFilesContent; +import org.lamsfoundation.lams.tool.sbmt.model.SubmitFilesSession; +import org.lamsfoundation.lams.tool.sbmt.model.SubmitUser; +import org.lamsfoundation.lams.tool.sbmt.service.ISubmitFilesService; +import org.lamsfoundation.lams.tool.sbmt.util.SubmitFilesException; +import org.lamsfoundation.lams.tool.sbmt.web.form.LearnerForm; +import org.lamsfoundation.lams.tool.sbmt.web.form.ReflectionForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.Configuration; +import org.lamsfoundation.lams.util.ConfigurationKeys; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.FileUtil; +import org.lamsfoundation.lams.util.FileValidatorUtil; +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.lamsfoundation.lams.web.util.SessionMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.multipart.MultipartFile; + +/** + * @author Manpreet Minhas + * @author Steve.Ni + */ +@Controller +@RequestMapping("/learning") +public class LearningController implements SbmtConstants { + public static Logger logger = Logger.getLogger(LearningController.class); + + @Autowired + private ISubmitFilesService submitFilesService; + + @Autowired + @Qualifier("sbmtMessageService") + private MessageService messageService; + + /** + * The initial page of learner in Submission tool. This page will list all uploaded files and learn + */ + @RequestMapping("/learner") + public String learner(@ModelAttribute LearnerForm learnerForm, HttpServletRequest request) { + // initial session Map + SessionMap sessionMap = new SessionMap<>(); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + request.setAttribute(SbmtConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + + learnerForm.setSessionMapID(sessionMap.getSessionID()); + + // get parameters from Request + ToolAccessMode mode = null; + try { + mode = WebUtil.getToolAccessMode((String) request.getAttribute(AttributeNames.PARAM_MODE)); + } catch (Exception e) { + } + if (mode == null) { + mode = ToolAccessMode.LEARNER; + } + request.setAttribute("mode", mode); + + Long toolSessionID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); + + // get session from shared session. + HttpSession ss = SessionManager.getSession(); + + Integer userID = null; + if (mode != null && mode.isTeacher()) { + // monitoring mode - user is specified in URL + userID = WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID, false); + } else { + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + userID = user.getUserID(); + } + + SubmitFilesSession session = submitFilesService.getSessionById(toolSessionID); + SubmitFilesContent content = session.getContent(); + + // this must before getFileUploadByUser() method becuase getCurrentLearner() + // will create session user if it does not exist. + SubmitUser learner = getCurrentLearner(toolSessionID, submitFilesService); + List filesUploaded = submitFilesService.getFilesUploadedByUser(userID, toolSessionID, + request.getLocale(), false); + + // check whehter finish lock is on/off + boolean lock = content.isLockOnFinished() && learner.isFinished(); + + sessionMap.put(AttributeNames.PARAM_MODE, mode); + sessionMap.put(AttributeNames.PARAM_TOOL_SESSION_ID, toolSessionID); + sessionMap.put(SbmtConstants.ATTR_FINISH_LOCK, lock); + sessionMap.put(SbmtConstants.ATTR_LOCK_ON_FINISH, content.isLockOnFinished()); + sessionMap.put(SbmtConstants.ATTR_USE_SEL_LEADER, content.isUseSelectLeaderToolOuput()); + sessionMap.put(SbmtConstants.ATTR_REFLECTION_ON, content.isReflectOnActivity()); + sessionMap.put(SbmtConstants.ATTR_REFLECTION_INSTRUCTION, content.getReflectInstructions()); + sessionMap.put(SbmtConstants.ATTR_TITLE, content.getTitle()); + sessionMap.put(SbmtConstants.ATTR_INSTRUCTION, content.getInstruction()); + sessionMap.put(SbmtConstants.ATTR_IS_MAX_LIMIT_UPLOAD_ENABLED, content.isLimitUpload()); + sessionMap.put(SbmtConstants.ATTR_MAX_LIMIT_UPLOAD_NUMBER, content.getLimitUploadNumber()); + sessionMap.put(SbmtConstants.ATTR_MIN_LIMIT_UPLOAD_NUMBER, content.getMinLimitUploadNumber()); + sessionMap.put(SbmtConstants.ATTR_USER_FINISHED, learner.isFinished()); + sessionMap.put(SbmtConstants.ATTR_IS_MARKS_RELEASED, session.isMarksReleased()); + + sessionMap.put(SbmtConstants.ATTR_UPLOAD_MAX_FILE_SIZE, + FileValidatorUtil.formatSize(Configuration.getAsInt(ConfigurationKeys.UPLOAD_FILE_MAX_SIZE))); + setLearnerDTO(request, sessionMap, learner, filesUploaded, mode); + + // if content in use, return special page. + if (content.isDefineLater()) { + return "learner/definelater"; + } + + // set contentInUse flag to true! + content.setContentInUse(true); + content.setDefineLater(false); + submitFilesService.saveOrUpdateContent(content); + + request.setAttribute(AttributeNames.ATTR_IS_LAST_ACTIVITY, submitFilesService.isLastActivity(toolSessionID)); + + // check if there is submission deadline + Date submissionDeadline = content.getSubmissionDeadline(); + if (submissionDeadline != null) { + // store submission deadline to sessionMap + sessionMap.put(SbmtConstants.ATTR_SUBMISSION_DEADLINE, submissionDeadline); + + UserDTO learnerDto = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone learnerTimeZone = learnerDto.getTimeZone(); + Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, submissionDeadline); + Date currentLearnerDate = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, new Date()); + + // calculate whether submission deadline has passed, and if so forward to "submissionDeadline" + if (currentLearnerDate.after(tzSubmissionDeadline)) { + return "learner/submissionDeadline"; + } + } + + if (content.isNotifyLearnersOnMarkRelease()) { + boolean isHtmlFormat = false; + submitFilesService.getEventNotificationService().createEvent(SbmtConstants.TOOL_SIGNATURE, + SbmtConstants.EVENT_NAME_NOTIFY_LEARNERS_ON_MARK_RELEASE, content.getContentID(), + submitFilesService.getLocalisedMessage("event.mark.release.subject", null), + submitFilesService.getLocalisedMessage("event.mark.release.body", null), isHtmlFormat); + + submitFilesService.getEventNotificationService().subscribe(SbmtConstants.TOOL_SIGNATURE, + SbmtConstants.EVENT_NAME_NOTIFY_LEARNERS_ON_MARK_RELEASE, content.getContentID(), userID, + IEventNotificationService.DELIVERY_METHOD_MAIL); + } + + SortedMap> submittedFilesMap = submitFilesService + .getFilesUploadedBySession(toolSessionID, request.getLocale()); + // support for leader select feature + SubmitUser groupLeader = content.isUseSelectLeaderToolOuput() + ? submitFilesService.checkLeaderSelectToolForSessionLeader(learner, toolSessionID) + : null; + + if (content.isUseSelectLeaderToolOuput() && !mode.isTeacher()) { + + // forwards to the leaderSelection page + if (groupLeader == null) { + List groupUsers = submitFilesService.getUsersBySession(toolSessionID); + request.setAttribute(SbmtConstants.ATTR_GROUP_USERS, groupUsers); + request.setAttribute(SbmtConstants.ATTR_SUBMIT_FILES, submittedFilesMap); + request.setAttribute(SbmtConstants.PARAM_WAITING_MESSAGE_KEY, "label.waiting.for.leader"); + return "learner/waitForLeaderTimeLimit"; + } + + // forwards to the waitForLeader pages + boolean isNonLeader = !userID.equals(groupLeader.getUserID()); + + if (isNonLeader && !learner.isFinished()) { + List filesUploadedByLeader = submitFilesService + .getFilesUploadedByUser(groupLeader.getUserID(), toolSessionID, request.getLocale(), false); + + if (filesUploadedByLeader == null) { + request.setAttribute(SbmtConstants.PARAM_WAITING_MESSAGE_KEY, + "label.waiting.for.leader.launch.time.limit"); + return "learner/waitForLeaderTimeLimit"; + } + + //if the time is up and leader hasn't submitted response - show waitForLeaderFinish page + if (!groupLeader.isFinished()) { + request.setAttribute(SbmtConstants.PARAM_WAITING_MESSAGE_KEY, "label.waiting.for.leader.finish"); + return "learner/waitForLeaderTimeLimit"; + } + } + + // check if leader has submitted all answers + if (groupLeader.isFinished()) { + submitFilesService.copyLearnerContent(groupLeader, learner); + filesUploaded = submitFilesService.getFilesUploadedByUser(learner.getUserID(), learner.getSessionID(), + request.getLocale(), false); + setLearnerDTO(request, sessionMap, learner, filesUploaded, mode); + } + } + + sessionMap.put(SbmtConstants.ATTR_GROUP_LEADER, groupLeader); + boolean isUserLeader = submitFilesService.isUserGroupLeader(learner.getUserID().longValue(), toolSessionID); + sessionMap.put(SbmtConstants.ATTR_IS_USER_LEADER, isUserLeader); + + boolean hasEditRight = !content.isUseSelectLeaderToolOuput() + || content.isUseSelectLeaderToolOuput() && isUserLeader; + sessionMap.put(SbmtConstants.ATTR_HAS_EDIT_RIGHT, hasEditRight); + + return "learner/sbmtlearner"; + } + + @RequestMapping("/teacher") + public String teacher(@ModelAttribute LearnerForm learnerForm, HttpServletRequest request, + HttpServletResponse response) { + request.setAttribute("mode", "teacher"); + return learner(learnerForm, request); + } + + @RequestMapping("/author") + public String author(@ModelAttribute LearnerForm learnerForm, HttpServletRequest request, + HttpServletResponse response) { + request.setAttribute("mode", "author"); + return learner(learnerForm, request); + } + + /** + * Loads the main learner page with the details currently in the session map + */ + @RequestMapping("/refresh") + public String refresh(@ModelAttribute LearnerForm learnerForm, HttpServletRequest request) { + String sessionMapID = WebUtil.readStrParam(request, SbmtConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + learnerForm.setSessionMapID(sessionMap.getSessionID()); + request.setAttribute(SbmtConstants.ATTR_SESSION_MAP_ID, sessionMapID); + + // get session from shared session. + HttpSession ss = SessionManager.getSession(); + // get back login user DTO + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + Integer userID = user.getUserID(); + Long sessionID = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + + List filesUploaded = submitFilesService.getFilesUploadedByUser(userID, sessionID, + request.getLocale(), false); + SubmitUser learner = getCurrentLearner(sessionID, submitFilesService); + ToolAccessMode mode = (ToolAccessMode) sessionMap.get(AttributeNames.ATTR_MODE); + setLearnerDTO(request, sessionMap, learner, filesUploaded, mode); + SubmitFilesSession session = submitFilesService.getSessionById(sessionID); + sessionMap.put(SbmtConstants.ATTR_IS_MARKS_RELEASED, session.isMarksReleased()); + + return "learner/sbmtlearner"; + } + + /** + * Implements learner upload submission function. This function also display the page again for learner uploading + * more submission use. + */ + @RequestMapping("/uploadFile") + public String uploadFile(@ModelAttribute LearnerForm learnerForm, HttpServletRequest request) { + String sessionMapID = learnerForm.getSessionMapID(); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute("sessionMapID", sessionMapID); + + Long sessionID = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + request.setAttribute(AttributeNames.ATTR_IS_LAST_ACTIVITY, submitFilesService.isLastActivity(sessionID)); + + if (validateUploadForm(learnerForm, request)) { + // get session from shared session. + HttpSession ss = SessionManager.getSession(); + // get back login user DTO + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + Integer userID = user.getUserID(); + + List filesUploaded = submitFilesService.getFilesUploadedByUser(userID, sessionID, + request.getLocale(), false); + + SubmitUser learner = getCurrentLearner(sessionID, submitFilesService); + ToolAccessMode mode = (ToolAccessMode) sessionMap.get(AttributeNames.ATTR_MODE); + setLearnerDTO(request, sessionMap, learner, filesUploaded, mode); + + return "learner/sbmtlearner"; + } + + // get session from shared session. + HttpSession ss = SessionManager.getSession(); + // get back login user DTO + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + Integer userID = user.getUserID(); + + MultipartFile file = learnerForm.getFile(); + String fileDescription = learnerForm.getDescription(); + // reset fields and display a new form for next new file upload + learnerForm.setDescription(""); + try { + submitFilesService.uploadFileToSession(sessionID, file, fileDescription, userID); + } catch (SubmitFilesException e) { + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + logger.error("Error while uploading file", e); + errorMap.add("GLOBAL", messageService.getMessage("error.upload", new Object[] { e.getMessage() })); + + request.setAttribute("errorMap", errorMap); + return "learner/sbmtlearner"; + } + + SubmitUser learner = getCurrentLearner(sessionID, submitFilesService); + + SubmitFilesContent content = submitFilesService.getSessionById(sessionID).getContent(); + if (content.isNotifyTeachersOnFileSubmit()) { + + String message = submitFilesService.getLocalisedMessage("event.file.submit.body", + new Object[] { learner.getFullName() }); + submitFilesService.getEventNotificationService().notifyLessonMonitors(sessionID, message, false); + } + + request.setAttribute(SbmtConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "learner/redirectAfterSubmit"; + } + + /** + * Learner choose finish upload button, will invoke this function. This function will mark the finished + * field by special toolSessionID and userID. + */ + @RequestMapping("/finish") + public String finish(HttpServletRequest request, HttpServletResponse response) { + String sessionMapID = WebUtil.readStrParam(request, SbmtConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute(SbmtConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + + ToolAccessMode mode = (ToolAccessMode) sessionMap.get(AttributeNames.ATTR_MODE); + Long sessionID = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + + if (mode == ToolAccessMode.LEARNER || mode.equals(ToolAccessMode.AUTHOR)) { + ToolSessionManager sessionMgrService = (ToolSessionManager) submitFilesService; + // get back login user DTO + // get session from shared session. + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + Integer userID = user.getUserID(); + submitFilesService.finishSubmission(sessionID, userID); + + String nextActivityUrl; + try { + nextActivityUrl = sessionMgrService.leaveToolSession(sessionID, new Long(userID.intValue())); + return "redirect:" + nextActivityUrl; + } catch (DataMissingException e) { + throw new SubmitFilesException(e); + } catch (ToolException e) { + throw new SubmitFilesException(e); + } + } + return null; + } + + // ********************************************************************************************** + // Private mehtods + // ********************************************************************************************** + + // validate uploaded form + private boolean validateUploadForm(LearnerForm learnerForm, HttpServletRequest request) { + + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + if (learnerForm.getFile() == null || StringUtils.isBlank(learnerForm.getFile().getName())) { + errorMap.add("GLOBAL", messageService.getMessage("learner.form.filepath.displayname")); + } + if (StringUtils.isBlank(learnerForm.getDescription())) { + errorMap.add("GLOBAL", messageService.getMessage("label.learner.fileDescription")); + } else if (learnerForm.getDescription().length() > LearnerForm.DESCRIPTION_LENGTH) { + errorMap.add("GLOBAL", messageService.getMessage("errors.maxdescsize")); + } + + boolean fileSizeValid = FileValidatorUtil.validateFileSize(learnerForm.getFile().getSize(), false); + if (!fileSizeValid) { + errorMap.add("GLOBAL", messageService.getMessage("errors.maxfilesize", + new Object[] { Configuration.getAsInt(ConfigurationKeys.UPLOAD_FILE_MAX_SIZE) })); + } + + if (learnerForm.getFile() != null) { + logger.debug("Learner submit file : " + learnerForm.getFile().getName()); + } + + if (learnerForm.getFile() != null && FileUtil.isExecutableFile(learnerForm.getFile().getName())) { + logger.debug("File is executatable : " + learnerForm.getFile().getName()); + errorMap.add("GLOBAL", messageService.getMessage("error.attachment.executable")); + } + + if (!errorMap.isEmpty()) { + request.setAttribute("errorMap", errorMap); + return true; + } + return false; + } + + /** + * + * Set information into learner DTO object for page display. Fill file list uploaded by the special user into web + * form. Remove the unauthorized mark and comments. + */ + private void setLearnerDTO(HttpServletRequest request, SessionMap sessionMap, SubmitUser currUser, + List filesUploaded, ToolAccessMode mode) { + + SubmitUserDTO dto = new SubmitUserDTO(currUser); + if (currUser != null) { + // if Monitoring does not release marks, then skip this mark and comment content. + if (filesUploaded != null) { + Iterator iter = filesUploaded.iterator(); + while (iter.hasNext()) { + FileDetailsDTO filedto = iter.next(); + if (mode.isTeacher() || currUser.getUid().equals(filedto.getOwner().getUserUid())) { + filedto.setCurrentLearner(true); + } else { + filedto.setCurrentLearner(false); + } + } + } + dto.setFilesUploaded(filesUploaded); + } + + // preset + // Monitor can edit the activity and set a limit / decreased the limit with + // the learner having already uploaded more files so ensure code handles that case. + boolean limitUpload = (Boolean) sessionMap.get(SbmtConstants.ATTR_IS_MAX_LIMIT_UPLOAD_ENABLED); + if (limitUpload && filesUploaded != null) { + int maxLimit = (Integer) sessionMap.get(SbmtConstants.ATTR_MAX_LIMIT_UPLOAD_NUMBER); + if (maxLimit <= filesUploaded.size()) { + sessionMap.put(SbmtConstants.ATTR_MAX_LIMIT_REACHED, Boolean.TRUE); + } + } + + // retrieve notebook reflection entry. + NotebookEntry notebookEntry = submitFilesService.getEntry( + (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID), CoreNotebookConstants.NOTEBOOK_TOOL, + SbmtConstants.TOOL_SIGNATURE, currUser.getUserID()); + + if (notebookEntry != null) { + dto.setReflect(notebookEntry.getEntry()); + } + + request.setAttribute("learner", dto); + } + + private SubmitUser getCurrentLearner(Long sessionID, ISubmitFilesService submitFilesService) { + // get session from shared session. + HttpSession ss = SessionManager.getSession(); + // get back login user DTO + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + Integer userID = user.getUserID(); + + SubmitUser learner = submitFilesService.getSessionUser(sessionID, userID); + if (learner == null) { + learner = submitFilesService.createSessionUser(user, sessionID); + } + + return learner; + } + + @RequestMapping("/deleteLearnerFile") + public void deleteLearnerFile(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + UserDTO currentUser = (UserDTO) SessionManager.getSession().getAttribute(AttributeNames.USER); + Long detailID = WebUtil.readLongParam(request, "detailId"); + + FileDetailsDTO fileDetail = submitFilesService.getFileDetails(detailID, request.getLocale()); + + if (fileDetail.getOwner().getUserID().equals(currentUser.getUserID()) + && (StringUtils.isBlank(fileDetail.getMarks()))) { + + submitFilesService.removeLearnerFile(detailID, null); + + } else { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not allowed to delete this item"); + } + } + + /** + * Display empty reflection form. + */ + @RequestMapping("/newReflection") + public String newReflection(@ModelAttribute("refForm") ReflectionForm refForm, HttpServletRequest request, + HttpServletResponse response) { + +// ISubmitFilesService submitFilesService = getService(); +// ActionErrors errors = validateBeforeFinish(request,submitFilesService); +// if(!errors.isEmpty()){ +// this.addErrors(request,errors); +// return mapping.getInputForward(); +// } + + //get session value + String sessionMapID = WebUtil.readStrParam(request, SbmtConstants.ATTR_SESSION_MAP_ID); + request.setAttribute(SbmtConstants.ATTR_SESSION_MAP_ID, sessionMapID); + + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + + refForm.setUserID(user.getUserID()); + refForm.setSessionMapID(sessionMapID); + + // get the existing reflection entry + + SessionMap map = (SessionMap) request.getSession().getAttribute(sessionMapID); + Long toolSessionID = (Long) map.get(AttributeNames.PARAM_TOOL_SESSION_ID); + NotebookEntry entry = submitFilesService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + SbmtConstants.TOOL_SIGNATURE, user.getUserID()); + + if (entry != null) { + refForm.setEntryText(entry.getEntry()); + } + + return "learner/notebook"; + } + + /** + * Submit reflection form input database. + */ + @RequestMapping("/submitReflection") + public String submitReflection(@ModelAttribute("refForm") ReflectionForm refForm, HttpServletRequest request, + HttpServletResponse response) { + Integer userId = refForm.getUserID(); + + String sessionMapID = WebUtil.readStrParam(request, SbmtConstants.ATTR_SESSION_MAP_ID); + request.setAttribute(SbmtConstants.ATTR_SESSION_MAP_ID, sessionMapID); + + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Long sessionId = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + + // check for existing notebook entry + NotebookEntry entry = submitFilesService.getEntry(sessionId, CoreNotebookConstants.NOTEBOOK_TOOL, + SbmtConstants.TOOL_SIGNATURE, userId); + + if (entry == null) { + // create new entry + submitFilesService.createNotebookEntry(sessionId, CoreNotebookConstants.NOTEBOOK_TOOL, + SbmtConstants.TOOL_SIGNATURE, userId, refForm.getEntryText()); + } else { + // update existing entry + entry.setEntry(refForm.getEntryText()); + entry.setLastModified(new Date()); + submitFilesService.updateEntry(entry); + } + + return finish(request, response); + } + + public void validateBeforeFinish(HttpServletRequest request, ISubmitFilesService submitFilesService) { + String sessionMapID = WebUtil.readStrParam(request, SbmtConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Long sessionId = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + Integer userID = user.getUserID(); + MultiValueMap errorMap = new LinkedMultiValueMap<>(); + List list = submitFilesService.getFilesUploadedByUser(userID, sessionId, request.getLocale(), + false); + int minUpload = (Integer) sessionMap.get(SbmtConstants.PARAM_MIN_UPLOAD); + if (minUpload > 0) { + errorMap.add("GLOBAL", messageService.getMessage("error.learning.minimum.upload.number.less")); + } + } +} \ No newline at end of file Index: lams_tool_scratchie/.classpath =================================================================== diff -u -r4148b35337096058f50c22fa950f64aa77294a4f -rf7937adfea85b6a6976eaa98df8c68db93f9f060 --- lams_tool_scratchie/.classpath (.../.classpath) (revision 4148b35337096058f50c22fa950f64aa77294a4f) +++ lams_tool_scratchie/.classpath (.../.classpath) (revision f7937adfea85b6a6976eaa98df8c68db93f9f060) @@ -1,7 +1,11 @@ - + + + + + @@ -12,7 +16,6 @@ -