Index: lams_tool_assessment/.classpath =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/.classpath (.../.classpath) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/.classpath (.../.classpath) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -2,7 +2,7 @@ - + Index: lams_tool_assessment/.externalToolBuilders/SASS_lams_tool_assessment.launch =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/.externalToolBuilders/SASS_lams_tool_assessment.launch (.../SASS_lams_tool_assessment.launch) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/.externalToolBuilders/SASS_lams_tool_assessment.launch (.../SASS_lams_tool_assessment.launch) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -4,6 +4,12 @@ + + + + + + Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/AssessmentConstants.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/AssessmentConstants.java (.../AssessmentConstants.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/AssessmentConstants.java (.../AssessmentConstants.java) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -57,21 +57,6 @@ public static final short QUESTION_TYPE_MARK_HEDGING = 8; - // for action forward name - public static final String SUCCESS = "success"; - - public static final String LEARNING = "learning"; - - public static final String SHOW_RESULTS = "showResults"; - - public static final String ERROR = "error"; - - public static final String DEFINE_LATER = "definelater"; - - public static final String WAIT_FOR_LEADER = "waitForLeader"; - - public static final String WAIT_FOR_LEADER_TIME_LIMIT = "waitForLeaderTimeLimit"; - // for parameters' name public static final String PARAM_WAITING_MESSAGE_KEY = "waitingMessageKey"; Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/action/AuthoringAction.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/action/ClearSessionAction.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/action/LearningAction.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/action/MonitoringAction.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/action/TblMonitoringAction.java'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java =================================================================== diff -u --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (revision 0) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -0,0 +1,1965 @@ +/**************************************************************** + * 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.assessment.web.controller; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +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.collections.CollectionUtils; +import org.apache.commons.fileupload.DiskFileUpload; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.authoring.web.AuthoringConstants; +import org.lamsfoundation.lams.learningdesign.service.ExportToolContentException; +import org.lamsfoundation.lams.questions.Answer; +import org.lamsfoundation.lams.questions.Question; +import org.lamsfoundation.lams.questions.QuestionExporter; +import org.lamsfoundation.lams.questions.QuestionParser; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; +import org.lamsfoundation.lams.tool.assessment.model.Assessment; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentOverallFeedback; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionOption; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUnit; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; +import org.lamsfoundation.lams.tool.assessment.service.IAssessmentService; +import org.lamsfoundation.lams.tool.assessment.util.SequencableComparator; +import org.lamsfoundation.lams.tool.assessment.web.form.AssessmentForm; +import org.lamsfoundation.lams.tool.assessment.web.form.AssessmentQuestionForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.FileUtil; +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 com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; +import com.thoughtworks.xstream.security.AnyTypePermission; + +/** + * @author Andrey Balan + */ +@Controller +@RequestMapping("/authoring") +public class AuthoringController { + + private static Logger log = Logger.getLogger(AuthoringController.class); + + @Autowired + @Qualifier("laasseAssessmentService") + private IAssessmentService service; + + /** + * Read assessment data from database and put them into HttpSession. It will redirect to init.do directly after this + * method run successfully. + * + * This method will avoid read database again and lost un-saved resouce question lost when user "refresh page", + */ + @RequestMapping("/start") + public String start(@ModelAttribute("assessmentForm") AssessmentForm assessmentForm, HttpServletRequest request) + throws ServletException { + ToolAccessMode mode = WebUtil.readToolAccessModeAuthorDefaulted(request); + request.setAttribute(AttributeNames.ATTR_MODE, mode.toString()); + return showStartPage(assessmentForm, request); + } + + @RequestMapping("/definelater") + public String definelater(@ModelAttribute("assessmentForm") AssessmentForm assessmentForm, + HttpServletRequest request) throws ServletException { + // update define later flag to true + Long contentId = new Long(WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID)); + Assessment assessment = service.getAssessmentByContentId(contentId); + + assessment.setDefineLater(true); + service.saveOrUpdateAssessment(assessment); + + //audit log the teacher has started editing activity in monitor + service.auditLogStartEditingActivityInMonitor(contentId); + + request.setAttribute(AttributeNames.ATTR_MODE, ToolAccessMode.TEACHER.toString()); + return showStartPage(assessmentForm, request); + } + + /** + * Common method for "start" and "defineLater" + */ + private String showStartPage(AssessmentForm assessmentForm, HttpServletRequest request) throws ServletException { + // save toolContentID into HTTPSession + Long contentId = new Long(WebUtil.readLongParam(request, AssessmentConstants.PARAM_TOOL_CONTENT_ID)); + + List questions = null; + Assessment assessment = null; + + // initial Session Map + SessionMap sessionMap = new SessionMap<>(); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + assessmentForm.setSessionMapID(sessionMap.getSessionID()); + + // Get contentFolderID and save to form. + String contentFolderID = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); + sessionMap.put(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + assessmentForm.setContentFolderID(contentFolderID); + + try { + assessment = service.getAssessmentByContentId(contentId); + // if assessment does not exist, try to use default content instead. + if (assessment == null) { + assessment = service.getDefaultContent(contentId); + } + + assessmentForm.setAssessment(assessment); + } catch (Exception e) { + AuthoringController.log.error(e); + throw new ServletException(e); + } + + if (assessment.getQuestions() != null) { + questions = new ArrayList(assessment.getQuestions()); + } else { + questions = new ArrayList<>(); + } + + // init assessment question list + SortedSet questionList = getQuestionList(sessionMap); + questionList.clear(); + questionList.addAll(questions); + + // init question references + SortedSet references = getQuestionReferences(sessionMap); + references.clear(); + if (assessment.getQuestionReferences() != null) { + references.addAll(assessment.getQuestionReferences()); + } + + // init available questions + reinitializeAvailableQuestions(sessionMap); + + sessionMap.put(AssessmentConstants.ATTR_ASSESSMENT_FORM, assessmentForm); + return "pages/authoring/start"; + } + + /** + * Display same entire authoring page content from HttpSession variable. + */ + @RequestMapping("/init") + public String init(@ModelAttribute("assessmentForm") AssessmentForm assessmentForm, HttpServletRequest request) throws ServletException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + AssessmentForm existForm = (AssessmentForm) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT_FORM); + + try { + PropertyUtils.copyProperties(assessmentForm, existForm); + } catch (Exception e) { + throw new ServletException(e); + } + + ToolAccessMode mode = WebUtil.readToolAccessModeAuthorDefaulted(request); + request.setAttribute(AttributeNames.ATTR_MODE, mode.toString()); + + return "pages/authoring/authoring"; + } + + /** + * This method will persist all inforamtion in this authoring page, include all assessment question, information + * etc. + */ + @RequestMapping("/updateContent") + public String updateContent(@ModelAttribute("assessmentForm") AssessmentForm assessmentForm, + HttpServletRequest request) throws Exception { + // get back sessionMAP + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(assessmentForm.getSessionMapID()); + ToolAccessMode mode = WebUtil.readToolAccessModeAuthorDefaulted(request); + Assessment assessment = assessmentForm.getAssessment(); + + // **********************************Get Assessment PO********************* + Assessment assessmentPO = service.getAssessmentByContentId(assessmentForm.getAssessment().getContentId()); + + //allow using old and modified questions and references altogether + if (mode.isTeacher()) { + for (AssessmentQuestion question : (Set) assessment.getQuestions()) { + service.releaseFromCache(question); + } + for (QuestionReference reference : (Set) assessment.getQuestionReferences()) { + service.releaseFromCache(reference); + } + } + + Set oldQuestions = (assessmentPO == null) ? new HashSet<>() : assessmentPO.getQuestions(); + Set oldReferences = (assessmentPO == null) ? new HashSet<>() + : assessmentPO.getQuestionReferences(); + AssessmentUser assessmentUser = null; + + if (assessmentPO == null) { + // new Assessment, create it. + assessmentPO = assessment; + assessmentPO.setCreated(new Timestamp(new Date().getTime())); + + } else { + Long uid = assessmentPO.getUid(); + assessmentUser = assessmentPO.getCreatedBy(); + PropertyUtils.copyProperties(assessmentPO, assessment); + + // copyProperties() above may result in "collection assigned to two objects in a session" exception + service.releaseFromCache(assessment); + assessmentForm.setAssessment(null); + assessment = null; + // set back UID + assessmentPO.setUid(uid); + + // if it is Teacher (from monitor) - change define later status + if (mode.isTeacher()) { + assessmentPO.setDefineLater(false); + assessmentPO.setUpdated(new Timestamp(new Date().getTime())); + } + + } + + // *******************************Handle user******************* + if (assessmentUser == null) { + // try to get form system session + HttpSession ss = SessionManager.getSession(); + // get back login user DTO + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + assessmentUser = service.getUserCreatedAssessment(new Long(user.getUserID().intValue()), + assessmentPO.getContentId()); + if (assessmentUser == null) { + assessmentUser = new AssessmentUser(user, assessmentPO); + } + } + assessmentPO.setCreatedBy(assessmentUser); + + // ************************* Handle assessment questions ******************* + // Handle assessment questions + Set questions = new LinkedHashSet<>(); + Set newQuestions = getQuestionList(sessionMap); + for (AssessmentQuestion question : newQuestions) { + removeNewLineCharacters(question); + questions.add(question); + } + assessmentPO.setQuestions(questions); + + List deletedQuestions = getDeletedQuestionList(sessionMap); + Set newReferences = updateQuestionReferencesGrades(request, sessionMap, true); + List deletedReferences = getDeletedQuestionReferences(sessionMap); + //recalculate results in case content is edited from monitoring + if (mode.isTeacher()) { + service.recalculateUserAnswers(assessmentPO, oldQuestions, newQuestions, deletedQuestions, oldReferences, + newReferences, deletedReferences); + } + + // delete References from database. + Iterator iterRef = deletedReferences.iterator(); + while (iterRef.hasNext()) { + QuestionReference reference = iterRef.next(); + iterRef.remove(); + if (reference.getUid() != null) { + service.deleteQuestionReference(reference.getUid()); + } + } + + // delete Questions from database. + Iterator iter = deletedQuestions.iterator(); + while (iter.hasNext()) { + AssessmentQuestion question = iter.next(); + iter.remove(); + if (question.getUid() != null) { + service.deleteAssessmentQuestion(question.getUid()); + } + } + + // Handle question references + assessmentPO.setQuestionReferences(newReferences); + + // ************************* Handle assessment overall feedbacks ******************* + TreeSet overallFeedbackList = getOverallFeedbacksFromForm(request, true); + assessmentPO.setOverallFeedbacks(overallFeedbackList); + + // ********************************************** + // finally persist assessmentPO again + service.saveOrUpdateAssessment(assessmentPO); + + assessmentForm.setAssessment(assessmentPO); + + request.setAttribute(AuthoringConstants.LAMS_AUTHORING_SUCCESS_FLAG, Boolean.TRUE); + request.setAttribute(AttributeNames.ATTR_MODE, mode.toString()); + return "pages/authoring/authoring"; + } + + /** + * Display empty page for new assessment question. + */ + @RequestMapping("/newQuestionInit") + public String newQuestionInit(@ModelAttribute("assessmentQuestionForm") AssessmentQuestionForm questionForm, + HttpServletRequest request) { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + updateQuestionReferencesGrades(request, sessionMap, false); + String contentFolderID = (String) sessionMap.get(AttributeNames.PARAM_CONTENT_FOLDER_ID); + questionForm.setSessionMapID(sessionMapID); + questionForm.setContentFolderID(contentFolderID); + questionForm.setDefaultGrade("1"); + questionForm.setPenaltyFactor("0"); + questionForm.setAnswerRequired(true); + + List optionList = new ArrayList<>(); + for (int i = 0; i < AssessmentConstants.INITIAL_OPTIONS_NUMBER; i++) { + AssessmentQuestionOption option = new AssessmentQuestionOption(); + option.setSequenceId(i + 1); + option.setGrade(0); + optionList.add(option); + } + request.setAttribute(AssessmentConstants.ATTR_OPTION_LIST, optionList); + + List unitList = new ArrayList<>(); + AssessmentUnit unit = new AssessmentUnit(); + unit.setSequenceId(1); + unit.setMultiplier(1); + unitList.add(unit); + for (int i = 1; i < AssessmentConstants.INITIAL_UNITS_NUMBER; i++) { + unit = new AssessmentUnit(); + unit.setSequenceId(i + 1); + unit.setMultiplier(0); + unitList.add(unit); + } + request.setAttribute(AssessmentConstants.ATTR_UNIT_LIST, unitList); + + short type = (short) NumberUtils.toInt(request.getParameter(AssessmentConstants.ATTR_QUESTION_TYPE)); + sessionMap.put(AssessmentConstants.ATTR_QUESTION_TYPE, type); + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + return findForward(type); + } + + /** + * Display edit page for existed assessment question. + */ + @RequestMapping("/editQuestion") + public String editQuestion(@ModelAttribute("assessmentQuestionForm") AssessmentQuestionForm questionForm, + HttpServletRequest request) { + + // get back sessionMAP + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + updateQuestionReferencesGrades(request, sessionMap, false); + String contentFolderID = (String) sessionMap.get(AttributeNames.PARAM_CONTENT_FOLDER_ID); + + int questionIdx = NumberUtils.toInt(request.getParameter(AssessmentConstants.PARAM_QUESTION_INDEX), -1); + AssessmentQuestion question = null; + if (questionIdx != -1) { + SortedSet assessmentList = getQuestionList(sessionMap); + List rList = new ArrayList<>(assessmentList); + question = rList.get(questionIdx); + if (question != null) { + populateQuestionToForm(questionIdx, question, questionForm, request); + questionForm.setContentFolderID(contentFolderID); + } + } + sessionMap.put(AssessmentConstants.ATTR_QUESTION_TYPE, question.getType()); + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, contentFolderID); + return findForward(question == null ? -1 : question.getType()); + } + + /** + * This method will get necessary information from assessment question form and save or update into + * HttpSession AssessmentQuestionList. 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. + */ + @RequestMapping("/saveOrUpdateQuestion") + public String saveOrUpdateQuestion(@ModelAttribute("assessmentQuestionForm") AssessmentQuestionForm questionForm, + HttpServletRequest request) { + extractFormToAssessmentQuestion(request, questionForm); + + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(questionForm.getSessionMapID()); + reinitializeAvailableQuestions(sessionMap); + + // set session map ID so that questionlist.jsp can get sessionMAP + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, questionForm.getSessionMapID()); + return "pages/authoring/parts/questionlist"; + } + + /** + * Parses questions extracted from IMS QTI file and adds them as new items. + */ + @SuppressWarnings("rawtypes") + @RequestMapping("/saveQTI") + public String saveQTI(HttpServletRequest request) throws UnsupportedEncodingException { + String sessionMapId = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapId); + String contentFolderID = (String) sessionMap.get(AttributeNames.PARAM_CONTENT_FOLDER_ID); + SortedSet questionList = getQuestionList(sessionMap); + + Question[] questions = QuestionParser.parseQuestionChoiceForm(request); + for (Question question : questions) { + AssessmentQuestion assessmentQuestion = new AssessmentQuestion(); + int maxSeq = 0; + if ((questionList != null) && (questionList.size() > 0)) { + AssessmentQuestion last = questionList.last(); + maxSeq = last.getSequenceId() + 1; + } + assessmentQuestion.setSequenceId(maxSeq); + assessmentQuestion.setTitle(question.getTitle()); + assessmentQuestion.setQuestion(QuestionParser.processHTMLField(question.getText(), false, contentFolderID, + question.getResourcesFolderPath())); + assessmentQuestion.setGeneralFeedback(QuestionParser.processHTMLField(question.getFeedback(), false, + contentFolderID, question.getResourcesFolderPath())); + assessmentQuestion.setPenaltyFactor(0); + + int questionGrade = 1; + + // options are different depending on the type + if (Question.QUESTION_TYPE_MULTIPLE_CHOICE.equals(question.getType()) + || Question.QUESTION_TYPE_FILL_IN_BLANK.equals(question.getType()) + || Question.QUESTION_TYPE_MARK_HEDGING.equals(question.getType())) { + boolean isMultipleChoice = Question.QUESTION_TYPE_MULTIPLE_CHOICE.equals(question.getType()); + boolean isMarkHedgingType = Question.QUESTION_TYPE_MARK_HEDGING.equals(question.getType()); + + // setting answers is very similar in both types, so they were put together here + if (isMarkHedgingType) { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_MARK_HEDGING); + + } else if (isMultipleChoice) { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE); + assessmentQuestion.setMultipleAnswersAllowed(false); + assessmentQuestion.setShuffle(false); + assessmentQuestion.setPrefixAnswersWithLetters(false); + + } else { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER); + assessmentQuestion.setCaseSensitive(false); + } + + String correctAnswer = null; + if (question.getAnswers() != null) { + TreeSet optionList = new TreeSet<>(new SequencableComparator()); + int orderId = 0; + for (Answer answer : question.getAnswers()) { + String answerText = QuestionParser.processHTMLField(answer.getText(), false, contentFolderID, + question.getResourcesFolderPath()); + if ((correctAnswer != null) && correctAnswer.equals(answerText)) { + AuthoringController.log + .warn("Skipping an answer with same text as the correct answer: " + answerText); + continue; + } + AssessmentQuestionOption assessmentAnswer = new AssessmentQuestionOption(); + assessmentAnswer.setOptionString(answerText); + assessmentAnswer.setSequenceId(orderId++); + assessmentAnswer.setFeedback(answer.getFeedback()); + + if ((answer.getScore() != null) && (answer.getScore() > 0)) { + // for fill in blanks question all answers are correct and get full grade + if (!isMultipleChoice && !isMarkHedgingType || correctAnswer == null) { + // whatever the correct answer holds, it becomes the question score + questionGrade = new Double(Math.ceil(answer.getScore())).intValue(); + // 100% goes to the correct answer + assessmentAnswer.setGrade(1); + correctAnswer = answerText; + } else { + AuthoringController.log + .warn("Choosing only first correct answer, despite another one was found: " + + answerText); + assessmentAnswer.setGrade(0); + } + } else { + assessmentAnswer.setGrade(0); + } + + optionList.add(assessmentAnswer); + } + + assessmentQuestion.setOptions(optionList); + } + + if (correctAnswer == null) { + AuthoringController.log.warn("No correct answer found for question: " + question.getText()); + continue; + } + + } else if (Question.QUESTION_TYPE_MULTIPLE_RESPONSE.equals(question.getType())) { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE); + assessmentQuestion.setMultipleAnswersAllowed(true); + assessmentQuestion.setShuffle(false); + assessmentQuestion.setPrefixAnswersWithLetters(false); + + if (question.getAnswers() != null) { + float totalScore = 0; + for (Answer answer : question.getAnswers()) { + if ((answer.getScore() != null) && (answer.getScore() > 0)) { + // the question score information is stored as sum of answer scores + totalScore += answer.getScore(); + } + } + questionGrade = new Double(Math.round(totalScore)).intValue(); + + TreeSet optionList = new TreeSet<>(new SequencableComparator()); + int orderId = 1; + for (Answer answer : question.getAnswers()) { + String answerText = answer.getText(); + AssessmentQuestionOption assessmentAnswer = new AssessmentQuestionOption(); + assessmentAnswer.setOptionString(answerText); + assessmentAnswer.setSequenceId(orderId++); + assessmentAnswer.setFeedback(answer.getFeedback()); + + if ((answer.getScore() != null) && (answer.getScore() > 0)) { + // set the factor of score for correct answers + assessmentAnswer.setGrade(answer.getScore() / totalScore); + } else { + assessmentAnswer.setGrade(0); + } + + optionList.add(assessmentAnswer); + } + + assessmentQuestion.setOptions(optionList); + } + + } else if (Question.QUESTION_TYPE_TRUE_FALSE.equals(question.getType())) { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_TRUE_FALSE); + + if (question.getAnswers() == null) { + AuthoringController.log.warn("Answers missing from true-false question: " + question.getText()); + continue; + } else { + for (Answer answer : question.getAnswers()) { + if ((answer.getScore() != null) && (answer.getScore() > 0)) { + assessmentQuestion.setCorrectAnswer(Boolean.parseBoolean(answer.getText())); + questionGrade = new Double(Math.ceil(answer.getScore())).intValue(); + } + if (!StringUtils.isBlank(answer.getFeedback())) { + // set feedback for true/false answers + if (Boolean.parseBoolean(answer.getText())) { + assessmentQuestion.setFeedbackOnCorrect(answer.getFeedback()); + } else { + assessmentQuestion.setFeedbackOnIncorrect(answer.getFeedback()); + } + } + } + } + } else if (Question.QUESTION_TYPE_MATCHING.equals(question.getType())) { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS); + assessmentQuestion.setShuffle(true); + + if (question.getAnswers() != null) { + // the question score information is stored as sum of answer scores + float totalScore = 0; + for (Answer answer : question.getAnswers()) { + if ((answer.getScore() != null) && (answer.getScore() > 0)) { + totalScore += answer.getScore(); + } + } + questionGrade = new Double(Math.round(totalScore)).intValue(); + + TreeSet optionList = new TreeSet<>(new SequencableComparator()); + int orderId = 1; + for (int answerIndex = 0; answerIndex < question.getAnswers().size(); answerIndex++) { + // QTI allows answers without a match, but LAMS assessment tool does not + Integer matchAnswerIndex = question.getMatchMap() == null ? null + : question.getMatchMap().get(answerIndex); + Answer matchAnswer = (matchAnswerIndex == null) || (question.getMatchAnswers() == null) ? null + : question.getMatchAnswers().get(matchAnswerIndex); + if (matchAnswer != null) { + Answer answer = question.getAnswers().get(answerIndex); + String answerText = answer.getText(); + AssessmentQuestionOption assessmentAnswer = new AssessmentQuestionOption(); + assessmentAnswer.setQuestion(answerText); + assessmentAnswer.setOptionString(matchAnswer.getText()); + assessmentAnswer.setSequenceId(orderId++); + assessmentAnswer.setFeedback(answer.getFeedback()); + + optionList.add(assessmentAnswer); + } + } + + assessmentQuestion.setOptions(optionList); + } + } else if (Question.QUESTION_TYPE_ESSAY.equals(question.getType())) { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_ESSAY); + assessmentQuestion.setAllowRichEditor(false); + + } else if (Question.QUESTION_TYPE_ESSAY.equals(question.getType())) { + assessmentQuestion.setType(AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE); + assessmentQuestion.setShuffle(false); + assessmentQuestion.setPrefixAnswersWithLetters(false); + + String correctAnswer = null; + if (question.getAnswers() != null) { + TreeSet optionList = new TreeSet<>(new SequencableComparator()); + int orderId = 1; + for (Answer answer : question.getAnswers()) { + String answerText = QuestionParser.processHTMLField(answer.getText(), false, contentFolderID, + question.getResourcesFolderPath()); + if ((correctAnswer != null) && correctAnswer.equals(answerText)) { + AuthoringController.log + .warn("Skipping an answer with same text as the correct answer: " + answerText); + continue; + } + AssessmentQuestionOption assessmentAnswer = new AssessmentQuestionOption(); + assessmentAnswer.setOptionString(answerText); + assessmentAnswer.setSequenceId(orderId++); + assessmentAnswer.setFeedback(answer.getFeedback()); + + if ((answer.getScore() != null) && (answer.getScore() > 0)) { + // for fill in blanks question all answers are correct and get full grade + if (correctAnswer == null) { + // whatever the correct answer holds, it becomes the question score + questionGrade = new Double(Math.ceil(answer.getScore())).intValue(); + // 100% goes to the correct answer + assessmentAnswer.setGrade(1); + correctAnswer = answerText; + } else { + AuthoringController.log + .warn("Choosing only first correct answer, despite another one was found: " + + answerText); + assessmentAnswer.setGrade(0); + } + } else { + assessmentAnswer.setGrade(0); + } + + optionList.add(assessmentAnswer); + } + + assessmentQuestion.setOptions(optionList); + } + + if (correctAnswer == null) { + AuthoringController.log.warn("No correct answer found for question: " + question.getText()); + continue; + } + + } else { + AuthoringController.log.warn("Unknow QTI question type: " + question.getType()); + continue; + } + + assessmentQuestion.setDefaultGrade(questionGrade); + + questionList.add(assessmentQuestion); + if (AuthoringController.log.isDebugEnabled()) { + AuthoringController.log.debug("Added question: " + assessmentQuestion.getTitle()); + } + } + + reinitializeAvailableQuestions(sessionMap); + + // set session map ID so that questionlist.jsp can get sessionMAP + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapId); + return "pages/authoring/parts/questionlist"; + } + + /** + * Prepares Assessment content for QTI packing + */ + @SuppressWarnings("rawtypes") + @RequestMapping("/exportQTI") + public String exportQTI(HttpServletRequest request, HttpServletResponse response) + throws UnsupportedEncodingException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + + SortedSet questionList = getQuestionList(sessionMap); + List questions = new LinkedList<>(); + for (AssessmentQuestion assessmentQuestion : questionList) { + Question question = new Question(); + List answers = new ArrayList<>(); + + switch (assessmentQuestion.getType()) { + + case AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE: + + if (assessmentQuestion.isMultipleAnswersAllowed()) { + question.setType(Question.QUESTION_TYPE_MULTIPLE_RESPONSE); + int correctAnswerCount = 0; + + for (AssessmentQuestionOption assessmentAnswer : assessmentQuestion.getOptions()) { + if (assessmentAnswer.getGrade() > 0) { + correctAnswerCount++; + } + } + + Float correctAnswerScore = correctAnswerCount > 0 + ? new Integer(100 / correctAnswerCount).floatValue() + : null; + int incorrectAnswerCount = assessmentQuestion.getOptions().size() - correctAnswerCount; + Float incorrectAnswerScore = incorrectAnswerCount > 0 + ? new Integer(-100 / incorrectAnswerCount).floatValue() + : null; + + for (AssessmentQuestionOption assessmentAnswer : assessmentQuestion.getOptions()) { + Answer answer = new Answer(); + boolean isCorrectAnswer = assessmentAnswer.getGrade() > 0; + + answer.setText(assessmentAnswer.getOptionString()); + answer.setScore(isCorrectAnswer ? correctAnswerScore : incorrectAnswerScore); + answer.setFeedback(isCorrectAnswer ? assessmentQuestion.getFeedbackOnCorrect() + : assessmentQuestion.getFeedbackOnIncorrect()); + + answers.add(assessmentAnswer.getSequenceId(), answer); + } + + } else { + question.setType(Question.QUESTION_TYPE_MULTIPLE_CHOICE); + + for (AssessmentQuestionOption assessmentAnswer : assessmentQuestion.getOptions()) { + Answer answer = new Answer(); + boolean isCorrectAnswer = assessmentAnswer.getGrade() == 1F; + + answer.setText(assessmentAnswer.getOptionString()); + answer.setScore( + isCorrectAnswer ? new Integer(assessmentQuestion.getDefaultGrade()).floatValue() + : 0); + answer.setFeedback(isCorrectAnswer ? assessmentQuestion.getFeedbackOnCorrect() + : assessmentQuestion.getFeedbackOnIncorrect()); + + answers.add(assessmentAnswer.getSequenceId(), answer); + } + } + break; + + case AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER: + question.setType(Question.QUESTION_TYPE_FILL_IN_BLANK); + + for (AssessmentQuestionOption assessmentAnswer : assessmentQuestion.getOptions()) { + // only answer which has more than 0% is considered a correct one + if (assessmentAnswer.getGrade() > 0) { + Answer answer = new Answer(); + answer.setText(assessmentAnswer.getOptionString()); + answer.setScore(new Integer(assessmentQuestion.getDefaultGrade()).floatValue()); + + answers.add(answer); + } + } + break; + + case AssessmentConstants.QUESTION_TYPE_TRUE_FALSE: + question.setType(Question.QUESTION_TYPE_TRUE_FALSE); + boolean isTrueCorrect = assessmentQuestion.getCorrectAnswer(); + + // true/false question is basically the same for QTI, just with special answers + Answer trueAnswer = new Answer(); + trueAnswer.setText("True"); + trueAnswer.setScore( + isTrueCorrect ? new Integer(assessmentQuestion.getDefaultGrade()).floatValue() : 0); + trueAnswer.setFeedback(isTrueCorrect ? assessmentQuestion.getFeedbackOnCorrect() + : assessmentQuestion.getFeedbackOnIncorrect()); + answers.add(trueAnswer); + + Answer falseAnswer = new Answer(); + falseAnswer.setText("False"); + falseAnswer.setScore( + !isTrueCorrect ? new Integer(assessmentQuestion.getDefaultGrade()).floatValue() : 0); + falseAnswer.setFeedback(!isTrueCorrect ? assessmentQuestion.getFeedbackOnCorrect() + : assessmentQuestion.getFeedbackOnIncorrect()); + answers.add(falseAnswer); + break; + + case AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS: + question.setType(Question.QUESTION_TYPE_MATCHING); + + int answerIndex = 0; + float score = assessmentQuestion.getDefaultGrade() / assessmentQuestion.getOptions().size(); + question.setMatchAnswers(new ArrayList(assessmentQuestion.getOptions().size())); + question.setMatchMap(new TreeMap()); + for (AssessmentQuestionOption assessmentAnswer : assessmentQuestion.getOptions()) { + Answer answer = new Answer(); + + answer.setText(assessmentAnswer.getQuestion()); + answer.setScore(score); + answer.setFeedback(assessmentAnswer.getFeedback()); + answers.add(answer); + + Answer matchingAnswer = new Answer(); + matchingAnswer.setText(assessmentAnswer.getOptionString()); + question.getMatchAnswers().add(matchingAnswer); + question.getMatchMap().put(answerIndex, answerIndex); + answerIndex++; + } + + break; + + case AssessmentConstants.QUESTION_TYPE_ESSAY: + // not much to do with essay + question.setType(Question.QUESTION_TYPE_ESSAY); + answers = null; + break; + + case AssessmentConstants.QUESTION_TYPE_MARK_HEDGING: + + question.setType(Question.QUESTION_TYPE_MARK_HEDGING); + + for (AssessmentQuestionOption assessmentAnswer : assessmentQuestion.getOptions()) { + Answer answer = new Answer(); + boolean isCorrectAnswer = assessmentAnswer.isCorrect(); + + answer.setText(assessmentAnswer.getOptionString()); + answer.setScore( + isCorrectAnswer ? new Integer(assessmentQuestion.getDefaultGrade()).floatValue() : 0); + answer.setFeedback(isCorrectAnswer ? assessmentQuestion.getFeedbackOnCorrect() + : assessmentQuestion.getFeedbackOnIncorrect()); + + answers.add(assessmentAnswer.getSequenceId(), answer); + } + break; + + default: + continue; + } + + question.setTitle(assessmentQuestion.getTitle()); + question.setText(assessmentQuestion.getQuestion()); + question.setFeedback(assessmentQuestion.getGeneralFeedback()); + question.setAnswers(answers); + + questions.add(question); + } + + String title = request.getParameter("title"); + QuestionExporter exporter = new QuestionExporter(title, questions.toArray(Question.QUESTION_ARRAY_TYPE)); + exporter.exportQTIPackage(request, response); + + return null; + } + + /** + * Remove assessment question 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. + */ + @RequestMapping("/removeQuestion") + public String removeQuestion(HttpServletRequest request) { + + // get back sessionMAP + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + updateQuestionReferencesGrades(request, sessionMap, false); + + int questionIdx = NumberUtils.toInt(request.getParameter(AssessmentConstants.PARAM_QUESTION_INDEX), -1); + if (questionIdx != -1) { + SortedSet questionList = getQuestionList(sessionMap); + List rList = new ArrayList<>(questionList); + AssessmentQuestion question = rList.remove(questionIdx); + questionList.clear(); + questionList.addAll(rList); + // add to delList + List delList = getDeletedQuestionList(sessionMap); + delList.add(question); + + // remove according questionReference, if exists + SortedSet questionReferences = getQuestionReferences(sessionMap); + QuestionReference questionReferenceToDelete = null; + for (QuestionReference questionReference : questionReferences) { + if ((questionReference.getQuestion() != null) + && (questionReference.getQuestion().getSequenceId() == question.getSequenceId())) { + questionReferenceToDelete = questionReference; + } + } + // check if we need to delete random question reference + if ((questionReferenceToDelete == null) && (questionReferences.size() > questionList.size())) { + // find the first random question + for (QuestionReference questionReference : questionReferences) { + if (questionReference.isRandomQuestion()) { + questionReferenceToDelete = questionReference; + break; + } + } + } + if (questionReferenceToDelete != null) { + questionReferences.remove(questionReferenceToDelete); + // add to delList + List delReferencesList = getDeletedQuestionReferences(sessionMap); + delReferencesList.add(questionReferenceToDelete); + } + + } + + reinitializeAvailableQuestions(sessionMap); + + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "pages/authoring/parts/questionlist"; + } + + /** + * Remove assessment question 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. + */ + @RequestMapping("/addQuestionReference") + public String addQuestionReference(HttpServletRequest request) { + // get back sessionMAP + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + updateQuestionReferencesGrades(request, sessionMap, false); + + SortedSet references = getQuestionReferences(sessionMap); + int questionIdx = NumberUtils.toInt(request.getParameter(AssessmentConstants.PARAM_QUESTION_INDEX), -1); + + // set SequenceId + QuestionReference reference = new QuestionReference(); + int maxSeq = 1; + if ((references != null) && (references.size() > 0)) { + QuestionReference last = references.last(); + maxSeq = last.getSequenceId() + 1; + } + reference.setSequenceId(maxSeq); + + // set isRandomQuestion + boolean isRandomQuestion = (questionIdx == -1); + reference.setRandomQuestion(isRandomQuestion); + + if (isRandomQuestion) { + reference.setDefaultGrade(1); + } else { + SortedSet questionList = getQuestionList(sessionMap); + AssessmentQuestion question = null; + for (AssessmentQuestion questionFromList : questionList) { + if (questionFromList.getSequenceId() == questionIdx) { + question = questionFromList; + break; + } + } + reference.setQuestion(question); + + reference.setDefaultGrade(question.getDefaultGrade()); + } + references.add(reference); + + reinitializeAvailableQuestions(sessionMap); + + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "pages/authoring/parts/questionlist"; + } + + /** + * Remove assessment question 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. + */ + @RequestMapping("/removeQuestionReference") + public String removeQuestionReference(HttpServletRequest request) { + + // get back sessionMAP + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + updateQuestionReferencesGrades(request, sessionMap, false); + + int questionReferenceIdx = NumberUtils + .toInt(request.getParameter(AssessmentConstants.PARAM_QUESTION_REFERENCE_INDEX), -1); + if (questionReferenceIdx != -1) { + SortedSet questionReferences = getQuestionReferences(sessionMap); + List rList = new ArrayList<>(questionReferences); + QuestionReference questionReference = rList.remove(questionReferenceIdx); + questionReferences.clear(); + questionReferences.addAll(rList); + // add to delList + List delList = getDeletedQuestionReferences(sessionMap); + delList.add(questionReference); + } + + reinitializeAvailableQuestions(sessionMap); + + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "pages/authoring/parts/questionlist"; + } + + /** + * Move up current question reference. + */ + @RequestMapping("/upQuestionReference") + public String upQuestionReference(HttpServletRequest request) { + return switchQuestionReferences(request, true); + } + + /** + * Move down current question reference. + */ + @RequestMapping("/downQuestionReference") + public String downQuestionReference(HttpServletRequest request) { + return switchQuestionReferences(request, false); + } + + private String switchQuestionReferences(HttpServletRequest request, boolean up) { + // get back sessionMAP + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + updateQuestionReferencesGrades(request, sessionMap, false); + + int questionReferenceIdx = NumberUtils + .toInt(request.getParameter(AssessmentConstants.PARAM_QUESTION_REFERENCE_INDEX), -1); + if (questionReferenceIdx != -1) { + SortedSet references = getQuestionReferences(sessionMap); + List rList = new ArrayList<>(references); + // get current and the target item, and switch their sequnece + QuestionReference reference = rList.get(questionReferenceIdx); + QuestionReference repReference; + if (up) { + repReference = rList.get(--questionReferenceIdx); + } else { + repReference = rList.get(++questionReferenceIdx); + } + int upSeqId = repReference.getSequenceId(); + repReference.setSequenceId(reference.getSequenceId()); + reference.setSequenceId(upSeqId); + + // put back list, it will be sorted again + references.clear(); + references.addAll(rList); + } + + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "pages/authoring/parts/questionlist"; + } + + /** + * Initializes import questions page. + */ + @RequestMapping("/importInit") + public String importInit(HttpServletRequest request) throws ServletException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + + return "pages/authoring/importQuestions"; + } + + /** + * Imports questions into question bank from uploaded xml file. + */ + @RequestMapping("/importQuestions") + public String importQuestions(HttpServletRequest request) throws ServletException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + SortedSet oldQuestions = getQuestionList(sessionMap); + + List toolsErrorMsgs = new ArrayList<>(); + try { + File designFile = null; + Map params = new HashMap<>(); + String filename = null; + + String uploadPath = FileUtil.createTempDirectory("_uploaded_2questions_xml"); + + DiskFileUpload fu = new DiskFileUpload(); + // maximum size that will be stored in memory + fu.setSizeThreshold(4096); + // the location for saving data that is larger than getSizeThreshold() + // fu.setRepositoryPath(uploadPath); + + List fileItems = fu.parseRequest(request); + Iterator iter = fileItems.iterator(); + while (iter.hasNext()) { + FileItem fi = (FileItem) iter.next(); + // UPLOAD_FILE is input field from HTML page + if (!fi.getFieldName().equalsIgnoreCase("UPLOAD_FILE")) { + params.put(fi.getFieldName(), fi.getString()); + } else { + // filename on the client + filename = FileUtil.getFileName(fi.getName()); + designFile = new File(uploadPath + filename); + fi.write(designFile); + } + } + + String filename2 = designFile.getName(); + String fileExtension = (filename2 != null) && (filename2.length() >= 4) + ? filename2.substring(filename2.length() - 4) + : ""; + if (!fileExtension.equalsIgnoreCase(".xml")) { + throw new RuntimeException("Wrong file extension. Xml is expected"); + } + // String learningDesignPath = ZipFileUtil.expandZip(new FileInputStream(designFile), filename2); + + // import learning design + String fullFilePath = designFile.getAbsolutePath();// FileUtil.getFullPath(learningDesignPath, + // ExportToolContentService.LEARNING_DESIGN_FILE_NAME); + List questions = (List) FileUtil.getObjectFromXML(null, + fullFilePath); + if (questions != null) { + for (AssessmentQuestion question : questions) { + int maxSeq = 1; + if ((oldQuestions != null) && (oldQuestions.size() > 0)) { + AssessmentQuestion last = oldQuestions.last(); + maxSeq = last.getSequenceId() + 1; + } + question.setSequenceId(maxSeq); + oldQuestions.add(question); + } + } + + } catch (Exception e) { + AuthoringController.log.error("Error occured during import", e); + toolsErrorMsgs.add(e.getClass().getName() + " " + e.getMessage()); + } + + if (toolsErrorMsgs.size() > 0) { + request.setAttribute("toolsErrorMessages", toolsErrorMsgs); + } + + reinitializeAvailableQuestions(sessionMap); + return "pages/authoring/parts/questionlist"; + } + + /** + * Exports xml format questions from question bank. + */ + @RequestMapping("/exportQuestions") + public String exportQuestions(HttpServletRequest request, HttpServletResponse response) { + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + + AssessmentForm assessmentForm = (AssessmentForm) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT_FORM); + Assessment assessment = assessmentForm.getAssessment(); + + String errors = null; + if (assessment != null) { + try { + ArrayList questionsToExport = new ArrayList<>(); + + for (AssessmentQuestion question : getQuestionList(sessionMap)) { + AssessmentQuestion clonedQuestion = (AssessmentQuestion) question.clone(); + questionsToExport.add(clonedQuestion); + } + // exporting XML + XStream designXml = new XStream(new StaxDriver()); + designXml.addPermission(AnyTypePermission.ANY); + String resultedXml = designXml.toXML(questionsToExport); + + response.setContentType("application/x-download"); + response.setHeader("Content-Disposition", + "attachment;filename=" + AssessmentConstants.EXPORT_QUESTIONS_FILENAME); + AuthoringController.log.debug("Exporting assessment questions to an xml: " + assessment.getContentId()); + + OutputStream out = null; + try { + out = response.getOutputStream(); + out.write(resultedXml.getBytes()); + int count = resultedXml.getBytes().length; + AuthoringController.log.debug("Wrote out " + count + " bytes"); + response.setContentLength(count); + out.flush(); + } catch (Exception e) { + AuthoringController.log.error("Exception occured writing out file:" + e.getMessage()); + throw new ExportToolContentException(e); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (Exception e) { + AuthoringController.log + .error("Error Closing file. File already written out - no exception being thrown.", e); + } + } + + } catch (Exception e) { + errors = "Unable to export tool content: " + e.toString(); + AuthoringController.log.error(errors); + } + + } + if (errors != null) { + try { + PrintWriter out = response.getWriter(); + out.write(errors); + out.flush(); + } catch (IOException e) { + } + } + return null; + } + + /** + * Ajax call, will add one more input line for new resource item instruction. + */ + @RequestMapping("/addOption") + public String addOption(HttpServletRequest request) { + TreeSet optionList = getOptionsFromRequest(request, false); + AssessmentQuestionOption option = new AssessmentQuestionOption(); + int maxSeq = 1; + if ((optionList != null) && (optionList.size() > 0)) { + AssessmentQuestionOption last = optionList.last(); + maxSeq = last.getSequenceId() + 1; + } + option.setSequenceId(maxSeq); + option.setGrade(0); + optionList.add(option); + + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, + WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID)); + request.setAttribute(AssessmentConstants.ATTR_QUESTION_TYPE, + WebUtil.readIntParam(request, AssessmentConstants.ATTR_QUESTION_TYPE)); + request.setAttribute(AssessmentConstants.ATTR_OPTION_LIST, optionList); + return "pages/authoring/parts/optionlist"; + } + + /** + * Ajax call, remove the given line of instruction of resource item. + */ + @RequestMapping("/removeOption") + public String removeOption(HttpServletRequest request) { + Set optionList = getOptionsFromRequest(request, false); + int optionIndex = NumberUtils.toInt(request.getParameter(AssessmentConstants.PARAM_OPTION_INDEX), -1); + if (optionIndex != -1) { + List rList = new ArrayList<>(optionList); + AssessmentQuestionOption question = rList.remove(optionIndex); + optionList.clear(); + optionList.addAll(rList); + } + + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, + WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID)); + request.setAttribute(AssessmentConstants.ATTR_QUESTION_TYPE, + WebUtil.readIntParam(request, AssessmentConstants.ATTR_QUESTION_TYPE)); + request.setAttribute(AssessmentConstants.ATTR_OPTION_LIST, optionList); + return "pages/authoring/parts/optionlist"; + } + + /** + * Move up current option. + */ + @RequestMapping("/upOption") + public String upOption(HttpServletRequest request) { + return switchOption(request, true); + } + + /** + * Move down current option. + */ + @RequestMapping("/downOption") + public String downOption(HttpServletRequest request) { + return switchOption(request, false); + } + + private String switchOption(HttpServletRequest request, boolean up) { + Set optionList = getOptionsFromRequest(request, false); + + int optionIndex = NumberUtils.toInt(request.getParameter(AssessmentConstants.PARAM_OPTION_INDEX), -1); + if (optionIndex != -1) { + List rList = new ArrayList<>(optionList); + + // get current and the target item, and switch their sequnece + AssessmentQuestionOption option = rList.get(optionIndex); + AssessmentQuestionOption repOption; + if (up) { + repOption = rList.get(--optionIndex); + } else { + repOption = rList.get(++optionIndex); + } + + int upSeqId = repOption.getSequenceId(); + repOption.setSequenceId(option.getSequenceId()); + option.setSequenceId(upSeqId); + + // put back list, it will be sorted again + optionList.clear(); + optionList.addAll(rList); + } + + request.setAttribute(AttributeNames.PARAM_CONTENT_FOLDER_ID, + WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID)); + request.setAttribute(AssessmentConstants.ATTR_QUESTION_TYPE, + WebUtil.readIntParam(request, AssessmentConstants.ATTR_QUESTION_TYPE)); + request.setAttribute(AssessmentConstants.ATTR_OPTION_LIST, optionList); + return "pages/authoring/parts/optionlist"; + } + + /** + * Ajax call, will add one more input line for new Unit. + */ + @RequestMapping("/newUnit") + public String newUnit(HttpServletRequest request) { + TreeSet unitList = getUnitsFromRequest(request, false); + AssessmentUnit unit = new AssessmentUnit(); + int maxSeq = 1; + if ((unitList != null) && (unitList.size() > 0)) { + AssessmentUnit last = unitList.last(); + maxSeq = last.getSequenceId() + 1; + } + unit.setSequenceId(maxSeq); + unitList.add(unit); + + request.setAttribute(AssessmentConstants.ATTR_UNIT_LIST, unitList); + return "pages/authoring/parts/unitlist"; + } + + /** + * Ajax call, will add one more input line for new resource item instruction. + */ + @RequestMapping("/initOverallFeedback") + public String initOverallFeedback(HttpServletRequest request) { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + AssessmentForm assessmentForm = (AssessmentForm) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT_FORM); + Assessment assessment = assessmentForm.getAssessment(); + + // initial Overall feedbacks list + SortedSet overallFeedbackList = new TreeSet<>(new SequencableComparator()); + if (!assessment.getOverallFeedbacks().isEmpty()) { + overallFeedbackList.addAll(assessment.getOverallFeedbacks()); + } else { + for (int i = 1; i <= AssessmentConstants.INITIAL_OVERALL_FEEDBACK_NUMBER; i++) { + AssessmentOverallFeedback overallFeedback = new AssessmentOverallFeedback(); + if (i == 1) { + overallFeedback.setGradeBoundary(100); + } + overallFeedback.setSequenceId(i); + overallFeedbackList.add(overallFeedback); + } + } + + request.setAttribute(AssessmentConstants.ATTR_OVERALL_FEEDBACK_LIST, overallFeedbackList); + return "pages/authoring/parts/overallfeedbacklist"; + } + + /** + * Ajax call, will add one more input line for new OverallFeedback. + */ + @RequestMapping("/newOverallFeedback") + public String newOverallFeedback(HttpServletRequest request) { + TreeSet overallFeedbackList = getOverallFeedbacksFromRequest(request, false); + AssessmentOverallFeedback overallFeedback = new AssessmentOverallFeedback(); + int maxSeq = 1; + if ((overallFeedbackList != null) && (overallFeedbackList.size() > 0)) { + AssessmentOverallFeedback last = overallFeedbackList.last(); + maxSeq = last.getSequenceId() + 1; + } + overallFeedback.setSequenceId(maxSeq); + overallFeedbackList.add(overallFeedback); + + request.setAttribute(AssessmentConstants.ATTR_OVERALL_FEEDBACK_LIST, overallFeedbackList); + return "pages/authoring/parts/overallfeedbacklist"; + } + + // ************************************************************************************* + // Private methods + // ************************************************************************************* + + /** + * refreshes set of all available questions for adding to question list + * + * @param sessionMap + */ + private void reinitializeAvailableQuestions(SessionMap sessionMap) { + SortedSet bankQuestions = getQuestionList(sessionMap); + SortedSet references = getQuestionReferences(sessionMap); + Set questionsFromList = new LinkedHashSet<>(); + for (QuestionReference reference : references) { + questionsFromList.add(reference.getQuestion()); + } + + Set availableQuestions = new TreeSet<>(new SequencableComparator()); + availableQuestions.addAll(CollectionUtils.subtract(bankQuestions, questionsFromList)); + + sessionMap.put(AssessmentConstants.ATTR_AVAILABLE_QUESTIONS, availableQuestions); + } + + /** + * List save current assessment questions. + * + * @param request + * @return + */ + private SortedSet getQuestionList(SessionMap sessionMap) { + SortedSet list = (SortedSet) sessionMap + .get(AssessmentConstants.ATTR_QUESTION_LIST); + if (list == null) { + list = new TreeSet<>(new SequencableComparator()); + sessionMap.put(AssessmentConstants.ATTR_QUESTION_LIST, list); + } + return list; + } + + /** + * List save current question references. + * + * @param request + * @return + */ + private SortedSet getQuestionReferences(SessionMap sessionMap) { + SortedSet list = (SortedSet) sessionMap + .get(AssessmentConstants.ATTR_QUESTION_REFERENCES); + if (list == null) { + list = new TreeSet<>(new SequencableComparator()); + sessionMap.put(AssessmentConstants.ATTR_QUESTION_REFERENCES, list); + } + return list; + } + + /** + * List save deleted assessment questions, which could be persisted or non-persisted questions. + * + * @param request + * @return + */ + private List getDeletedQuestionList(SessionMap sessionMap) { + return getListFromSession(sessionMap, AssessmentConstants.ATTR_DELETED_QUESTION_LIST); + } + + /** + * List save deleted assessment questions, which could be persisted or non-persisted questions. + * + * @param request + * @return + */ + private List getDeletedQuestionReferences(SessionMap sessionMap) { + return getListFromSession(sessionMap, AssessmentConstants.ATTR_DELETED_QUESTION_REFERENCES); + } + + /** + * Get java.util.List from HttpSession by given name. + * + * @param request + * @param name + * @return + */ + private List getListFromSession(SessionMap sessionMap, String name) { + List list = (List) sessionMap.get(name); + if (list == null) { + list = new ArrayList(); + sessionMap.put(name, list); + } + return list; + } + + /** + * Get back jsp name. + * + * @param type + * @param mapping + * @return + */ + private String findForward(short type) { + String forward; + switch (type) { + case AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE: + forward = "pages/authoring/parts/addmultiplechoice"; + break; + case AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS: + forward = "pages/authoring/parts/addmatchingpairs"; + break; + case AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER: + forward = "pages/authoring/parts/addshortanswer"; + break; + case AssessmentConstants.QUESTION_TYPE_NUMERICAL: + forward = "pages/authoring/parts/addnumerical"; + break; + case AssessmentConstants.QUESTION_TYPE_TRUE_FALSE: + forward = "pages/authoring/parts/addtruefalse"; + break; + case AssessmentConstants.QUESTION_TYPE_ESSAY: + forward = "pages/authoring/parts/addessay"; + break; + case AssessmentConstants.QUESTION_TYPE_ORDERING: + forward = "pages/authoring/parts/addordering"; + break; + case AssessmentConstants.QUESTION_TYPE_MARK_HEDGING: + forward = "pages/authoring/parts/addmarkhedging"; + break; + default: + forward = null; + break; + } + return forward; + } + + /** + * This method will populate assessment question information to its form for edit use. + * + * @param questionIdx + * @param question + * @param form + * @param request + */ + private void populateQuestionToForm(int questionIdx, AssessmentQuestion question, AssessmentQuestionForm form, + HttpServletRequest request) { + form.setTitle(question.getTitle()); + form.setQuestion(question.getQuestion()); + form.setDefaultGrade(String.valueOf(question.getDefaultGrade())); + form.setPenaltyFactor(String.valueOf(question.getPenaltyFactor())); + form.setAnswerRequired(question.isAnswerRequired()); + form.setGeneralFeedback(question.getGeneralFeedback()); + form.setFeedback(question.getFeedback()); + form.setMultipleAnswersAllowed(question.isMultipleAnswersAllowed()); + form.setIncorrectAnswerNullifiesMark(question.isIncorrectAnswerNullifiesMark()); + form.setFeedbackOnCorrect(question.getFeedbackOnCorrect()); + form.setFeedbackOnPartiallyCorrect(question.getFeedbackOnPartiallyCorrect()); + form.setFeedbackOnIncorrect(question.getFeedbackOnIncorrect()); + form.setShuffle(question.isShuffle()); + form.setPrefixAnswersWithLetters(question.isPrefixAnswersWithLetters()); + form.setCaseSensitive(question.isCaseSensitive()); + form.setCorrectAnswer(question.getCorrectAnswer()); + form.setAllowRichEditor(question.isAllowRichEditor()); + form.setMaxWordsLimit(question.getMaxWordsLimit()); + form.setMinWordsLimit(question.getMinWordsLimit()); + form.setHedgingJustificationEnabled(question.isHedgingJustificationEnabled()); + if (questionIdx >= 0) { + form.setQuestionIndex(new Integer(questionIdx).toString()); + } + + short questionType = question.getType(); + if ((questionType == AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE) + || (questionType == AssessmentConstants.QUESTION_TYPE_ORDERING) + || (questionType == AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS) + || (questionType == AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER) + || (questionType == AssessmentConstants.QUESTION_TYPE_NUMERICAL) + || (questionType == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING)) { + Set optionList = question.getOptions(); + request.setAttribute(AssessmentConstants.ATTR_OPTION_LIST, optionList); + } + if (questionType == AssessmentConstants.QUESTION_TYPE_NUMERICAL) { + Set unitList = question.getUnits(); + request.setAttribute(AssessmentConstants.ATTR_UNIT_LIST, unitList); + } + } + + /** + * Extract web form content to assessment question. + */ + private void extractFormToAssessmentQuestion(HttpServletRequest request, AssessmentQuestionForm questionForm) { + /* + * BE CAREFUL: This method will copy nessary info from request form to an old or new AssessmentQuestion + * instance. It + * gets all info EXCEPT AssessmentQuestion.createDate, which need be set when + * persisting + * this assessment Question. + */ + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(questionForm.getSessionMapID()); + // check whether it is "edit(old Question)" or "add(new Question)" + SortedSet questionList = getQuestionList(sessionMap); + int questionIdx = NumberUtils.toInt(questionForm.getQuestionIndex(), -1); + AssessmentQuestion question = null; + + if (questionIdx == -1) { // add + question = new AssessmentQuestion(); + int maxSeq = 1; + if ((questionList != null) && (questionList.size() > 0)) { + AssessmentQuestion last = questionList.last(); + maxSeq = last.getSequenceId() + 1; + } + question.setSequenceId(maxSeq); + questionList.add(question); + } else { // edit + List rList = new ArrayList<>(questionList); + question = rList.get(questionIdx); + } + short type = questionForm.getQuestionType(); + question.setType(questionForm.getQuestionType()); + + question.setTitle(questionForm.getTitle()); + question.setQuestion(questionForm.getQuestion()); + + question.setDefaultGrade(Integer.parseInt(questionForm.getDefaultGrade())); + question.setGeneralFeedback(questionForm.getGeneralFeedback()); + question.setAnswerRequired(questionForm.isAnswerRequired()); + + if (type == AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE) { + question.setMultipleAnswersAllowed(questionForm.isMultipleAnswersAllowed()); + boolean incorrectAnswerNullifiesMark = questionForm.isMultipleAnswersAllowed() + ? questionForm.isIncorrectAnswerNullifiesMark() + : false; + question.setIncorrectAnswerNullifiesMark(incorrectAnswerNullifiesMark); + question.setPenaltyFactor(Float.parseFloat(questionForm.getPenaltyFactor())); + question.setShuffle(questionForm.isShuffle()); + question.setPrefixAnswersWithLetters(questionForm.isPrefixAnswersWithLetters()); + question.setFeedbackOnCorrect(questionForm.getFeedbackOnCorrect()); + question.setFeedbackOnPartiallyCorrect(questionForm.getFeedbackOnPartiallyCorrect()); + question.setFeedbackOnIncorrect(questionForm.getFeedbackOnIncorrect()); + } else if ((type == AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS)) { + question.setPenaltyFactor(Float.parseFloat(questionForm.getPenaltyFactor())); + question.setShuffle(questionForm.isShuffle()); + } else if ((type == AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER)) { + question.setPenaltyFactor(Float.parseFloat(questionForm.getPenaltyFactor())); + question.setCaseSensitive(questionForm.isCaseSensitive()); + } else if ((type == AssessmentConstants.QUESTION_TYPE_NUMERICAL)) { + question.setPenaltyFactor(Float.parseFloat(questionForm.getPenaltyFactor())); + } else if ((type == AssessmentConstants.QUESTION_TYPE_TRUE_FALSE)) { + question.setPenaltyFactor(Float.parseFloat(questionForm.getPenaltyFactor())); + question.setCorrectAnswer(questionForm.isCorrectAnswer()); + question.setFeedbackOnCorrect(questionForm.getFeedbackOnCorrect()); + question.setFeedbackOnIncorrect(questionForm.getFeedbackOnIncorrect()); + } else if ((type == AssessmentConstants.QUESTION_TYPE_ESSAY)) { + question.setAllowRichEditor(questionForm.isAllowRichEditor()); + question.setMaxWordsLimit(questionForm.getMaxWordsLimit()); + question.setMinWordsLimit(questionForm.getMinWordsLimit()); + } else if (type == AssessmentConstants.QUESTION_TYPE_ORDERING) { + question.setPenaltyFactor(Float.parseFloat(questionForm.getPenaltyFactor())); + question.setFeedbackOnCorrect(questionForm.getFeedbackOnCorrect()); + question.setFeedbackOnIncorrect(questionForm.getFeedbackOnIncorrect()); + } else if (type == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING) { + question.setShuffle(questionForm.isShuffle()); + question.setFeedbackOnCorrect(questionForm.getFeedbackOnCorrect()); + question.setFeedbackOnPartiallyCorrect(questionForm.getFeedbackOnPartiallyCorrect()); + question.setFeedbackOnIncorrect(questionForm.getFeedbackOnIncorrect()); + question.setHedgingJustificationEnabled(questionForm.isHedgingJustificationEnabled()); + } + + // set options + if ((type == AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE) + || (type == AssessmentConstants.QUESTION_TYPE_ORDERING) + || (type == AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS) + || (type == AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER) + || (type == AssessmentConstants.QUESTION_TYPE_NUMERICAL) + || (type == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING)) { + Set optionList = getOptionsFromRequest(request, true); + Set options = new LinkedHashSet<>(); + int seqId = 0; + for (AssessmentQuestionOption option : optionList) { + option.setSequenceId(seqId++); + options.add(option); + } + question.setOptions(options); + } + // set units + if (type == AssessmentConstants.QUESTION_TYPE_NUMERICAL) { + Set unitList = getUnitsFromRequest(request, true); + Set units = new LinkedHashSet<>(); + int seqId = 0; + for (AssessmentUnit unit : unitList) { + unit.setSequenceId(seqId++); + units.add(unit); + } + question.setUnits(units); + } + + } + + private Set updateQuestionReferencesGrades(HttpServletRequest request, + SessionMap sessionMap, boolean isFormSubmit) { + Map paramMap = splitRequestParameter(request, + AssessmentConstants.ATTR_QUESTION_REFERENCES_GRADES); + + SortedSet questionReferences = getQuestionReferences(sessionMap); + for (QuestionReference questionReference : questionReferences) { + try { + int grade; + if (isFormSubmit) { + grade = WebUtil.readIntParam(request, + AssessmentConstants.PARAM_GRADE + questionReference.getSequenceId()); + } else { + String gradeStr = paramMap.get(AssessmentConstants.PARAM_GRADE + questionReference.getSequenceId()); + grade = new Integer(gradeStr); + } + + questionReference.setDefaultGrade(grade); + } catch (Exception e) { + AuthoringController.log.debug(e.getMessage()); + } + } + + return questionReferences; + } + + /** + * Get answer options from HttpRequest + * + * @param request + * @param isForSaving + * whether the blank options will be preserved or not + */ + private TreeSet getOptionsFromRequest(HttpServletRequest request, boolean isForSaving) { + Map paramMap = splitRequestParameter(request, AssessmentConstants.ATTR_OPTION_LIST); + + int count = NumberUtils.toInt(paramMap.get(AssessmentConstants.ATTR_OPTION_COUNT)); + int questionType = WebUtil.readIntParam(request, AssessmentConstants.ATTR_QUESTION_TYPE); + Integer correctOptionIndex = (paramMap.get(AssessmentConstants.ATTR_OPTION_CORRECT) == null) ? null + : NumberUtils.toInt(paramMap.get(AssessmentConstants.ATTR_OPTION_CORRECT)); + TreeSet optionList = new TreeSet<>(new SequencableComparator()); + for (int i = 0; i < count; i++) { + if ((questionType == AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE) + || (questionType == AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER)) { + String optionString = paramMap.get(AssessmentConstants.ATTR_OPTION_STRING_PREFIX + i); + if ((optionString == null) && isForSaving) { + continue; + } + + AssessmentQuestionOption option = new AssessmentQuestionOption(); + String sequenceId = paramMap.get(AssessmentConstants.ATTR_OPTION_SEQUENCE_ID_PREFIX + i); + option.setSequenceId(NumberUtils.toInt(sequenceId)); + option.setOptionString(optionString); + float grade = Float.valueOf(paramMap.get(AssessmentConstants.ATTR_OPTION_GRADE_PREFIX + i)); + option.setGrade(grade); + option.setFeedback(paramMap.get(AssessmentConstants.ATTR_OPTION_FEEDBACK_PREFIX + i)); + optionList.add(option); + } else if (questionType == AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS) { + String question = paramMap.get(AssessmentConstants.ATTR_OPTION_QUESTION_PREFIX + i); + if ((question == null) && isForSaving) { + continue; + } + + AssessmentQuestionOption option = new AssessmentQuestionOption(); + String sequenceId = paramMap.get(AssessmentConstants.ATTR_OPTION_SEQUENCE_ID_PREFIX + i); + option.setSequenceId(NumberUtils.toInt(sequenceId)); + option.setOptionString(paramMap.get(AssessmentConstants.ATTR_OPTION_STRING_PREFIX + i)); + option.setQuestion(question); + optionList.add(option); + } else if (questionType == AssessmentConstants.QUESTION_TYPE_NUMERICAL) { + String optionFloatStr = paramMap.get(AssessmentConstants.ATTR_OPTION_FLOAT_PREFIX + i); + String acceptedErrorStr = paramMap.get(AssessmentConstants.ATTR_OPTION_ACCEPTED_ERROR_PREFIX + i); + String gradeStr = paramMap.get(AssessmentConstants.ATTR_OPTION_GRADE_PREFIX + i); + if (optionFloatStr.equals("0.0") && optionFloatStr.equals("0.0") && gradeStr.equals("0.0") + && isForSaving) { + continue; + } + + AssessmentQuestionOption option = new AssessmentQuestionOption(); + String sequenceId = paramMap.get(AssessmentConstants.ATTR_OPTION_SEQUENCE_ID_PREFIX + i); + option.setSequenceId(NumberUtils.toInt(sequenceId)); + try { + float optionFloat = Float.valueOf(optionFloatStr); + option.setOptionFloat(optionFloat); + } catch (Exception e) { + option.setOptionFloat(0); + } + try { + float acceptedError = Float.valueOf(acceptedErrorStr); + option.setAcceptedError(acceptedError); + } catch (Exception e) { + option.setAcceptedError(0); + } + float grade = Float.valueOf(paramMap.get(AssessmentConstants.ATTR_OPTION_GRADE_PREFIX + i)); + option.setGrade(grade); + option.setFeedback(paramMap.get(AssessmentConstants.ATTR_OPTION_FEEDBACK_PREFIX + i)); + optionList.add(option); + } else if (questionType == AssessmentConstants.QUESTION_TYPE_ORDERING) { + String optionString = paramMap.get(AssessmentConstants.ATTR_OPTION_STRING_PREFIX + i); + if ((optionString == null) && isForSaving) { + continue; + } + + AssessmentQuestionOption option = new AssessmentQuestionOption(); + String sequenceId = paramMap.get(AssessmentConstants.ATTR_OPTION_SEQUENCE_ID_PREFIX + i); + option.setSequenceId(NumberUtils.toInt(sequenceId)); + option.setOptionString(optionString); + //TODO check the following line is not required +// option.setAnswerInt(i); + optionList.add(option); + } else if (questionType == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING) { + String optionString = paramMap.get(AssessmentConstants.ATTR_OPTION_STRING_PREFIX + i); + if ((optionString == null) && isForSaving) { + continue; + } + + AssessmentQuestionOption option = new AssessmentQuestionOption(); + String sequenceId = paramMap.get(AssessmentConstants.ATTR_OPTION_SEQUENCE_ID_PREFIX + i); + option.setSequenceId(NumberUtils.toInt(sequenceId)); + option.setOptionString(optionString); + if ((correctOptionIndex != null) && correctOptionIndex.equals(new Integer(sequenceId))) { + option.setCorrect(true); + } + option.setFeedback(paramMap.get(AssessmentConstants.ATTR_OPTION_FEEDBACK_PREFIX + i)); + optionList.add(option); + } + } + return optionList; + } + + /** + * Get units from HttpRequest + * + * @param request + */ + private TreeSet getUnitsFromRequest(HttpServletRequest request, boolean isForSaving) { + Map paramMap = splitRequestParameter(request, AssessmentConstants.ATTR_UNIT_LIST); + + int count = NumberUtils.toInt(paramMap.get(AssessmentConstants.ATTR_UNIT_COUNT)); + TreeSet unitList = new TreeSet<>(new SequencableComparator()); + for (int i = 0; i < count; i++) { + String unitStr = paramMap.get(AssessmentConstants.ATTR_UNIT_UNIT_PREFIX + i); + if (StringUtils.isBlank(unitStr) && isForSaving) { + continue; + } + + AssessmentUnit unit = new AssessmentUnit(); + String sequenceId = paramMap.get(AssessmentConstants.ATTR_UNIT_SEQUENCE_ID_PREFIX + i); + unit.setSequenceId(NumberUtils.toInt(sequenceId)); + unit.setUnit(unitStr); + float multiplier = Float.valueOf(paramMap.get(AssessmentConstants.ATTR_UNIT_MULTIPLIER_PREFIX + i)); + unit.setMultiplier(multiplier); + unitList.add(unit); + } + + return unitList; + } + + /** + * Get overall feedbacks from HttpRequest + * + * @param request + */ + private TreeSet getOverallFeedbacksFromRequest(HttpServletRequest request, + boolean skipBlankOverallFeedbacks) { + int count = NumberUtils.toInt(request.getParameter(AssessmentConstants.ATTR_OVERALL_FEEDBACK_COUNT)); + TreeSet overallFeedbackList = new TreeSet<>(new SequencableComparator()); + for (int i = 0; i < count; i++) { + String gradeBoundaryStr = request + .getParameter(AssessmentConstants.ATTR_OVERALL_FEEDBACK_GRADE_BOUNDARY_PREFIX + i); + String feedback = request.getParameter(AssessmentConstants.ATTR_OVERALL_FEEDBACK_FEEDBACK_PREFIX + i); + String sequenceId = request.getParameter(AssessmentConstants.ATTR_OVERALL_FEEDBACK_SEQUENCE_ID_PREFIX + i); + + if ((StringUtils.isBlank(feedback) || StringUtils.isBlank(gradeBoundaryStr)) && skipBlankOverallFeedbacks) { + continue; + } + AssessmentOverallFeedback overallFeedback = new AssessmentOverallFeedback(); + overallFeedback.setSequenceId(NumberUtils.toInt(sequenceId)); + if (!StringUtils.isBlank(gradeBoundaryStr)) { + int gradeBoundary = NumberUtils.toInt( + request.getParameter(AssessmentConstants.ATTR_OVERALL_FEEDBACK_GRADE_BOUNDARY_PREFIX + i)); + overallFeedback.setGradeBoundary(gradeBoundary); + } + overallFeedback.setFeedback(feedback); + overallFeedbackList.add(overallFeedback); + } + return overallFeedbackList; + } + + /** + * Get overall feedbacks from HttpRequest + * + * @param request + */ + private TreeSet getOverallFeedbacksFromForm(HttpServletRequest request, + boolean skipBlankOverallFeedbacks) { + Map paramMap = splitRequestParameter(request, AssessmentConstants.ATTR_OVERALL_FEEDBACK_LIST); + + int count = NumberUtils.toInt(paramMap.get(AssessmentConstants.ATTR_OVERALL_FEEDBACK_COUNT)); + TreeSet overallFeedbackList = new TreeSet<>(new SequencableComparator()); + for (int i = 0; i < count; i++) { + String gradeBoundaryStr = paramMap.get(AssessmentConstants.ATTR_OVERALL_FEEDBACK_GRADE_BOUNDARY_PREFIX + i); + String feedback = paramMap.get(AssessmentConstants.ATTR_OVERALL_FEEDBACK_FEEDBACK_PREFIX + i); + String sequenceId = paramMap.get(AssessmentConstants.ATTR_OVERALL_FEEDBACK_SEQUENCE_ID_PREFIX + i); + + if ((StringUtils.isBlank(feedback) || StringUtils.isBlank(gradeBoundaryStr)) && skipBlankOverallFeedbacks) { + continue; + } + AssessmentOverallFeedback overallFeedback = new AssessmentOverallFeedback(); + overallFeedback.setSequenceId(NumberUtils.toInt(sequenceId)); + if (!StringUtils.isBlank(gradeBoundaryStr)) { + int gradeBoundary = NumberUtils + .toInt(paramMap.get(AssessmentConstants.ATTR_OVERALL_FEEDBACK_GRADE_BOUNDARY_PREFIX + i)); + overallFeedback.setGradeBoundary(gradeBoundary); + } + overallFeedback.setFeedback(feedback); + overallFeedbackList.add(overallFeedback); + } + return overallFeedbackList; + } + + /** + * Split Request Parameter from HttpRequest + * + * @param request + * @param parameterName + * parameterName + */ + private Map splitRequestParameter(HttpServletRequest request, String parameterName) { + String list = request.getParameter(parameterName); + if (list == null) { + return null; + } + + String[] params = list.split("&"); + Map paramMap = new HashMap<>(); + String[] pair; + for (String item : params) { + pair = item.split("="); + if ((pair == null) || (pair.length != 2)) { + continue; + } + try { + paramMap.put(pair[0], URLDecoder.decode(pair[1], "UTF-8")); + } catch (UnsupportedEncodingException e) { + AuthoringController.log.error("Error occurs when decode instruction string:" + e.toString()); + } + } + return paramMap; + } + + /** + * Removes redundant new line characters from options left by CKEditor (otherwise it will break Javascript in + * monitor) + * + * @param question + */ + private void removeNewLineCharacters(AssessmentQuestion question) { + Set options = question.getOptions(); + if (options != null) { + for (AssessmentQuestionOption option : options) { + String optionString = option.getOptionString(); + if (optionString != null) { + option.setOptionString(optionString.replaceAll("[\n\r\f]", "")); + } + + String questionStr = option.getQuestion(); + if (questionStr != null) { + option.setQuestion(questionStr.replaceAll("[\n\r\f]", "")); + } + } + + } + } +} Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/ClearSessionController.java =================================================================== diff -u --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/ClearSessionController.java (revision 0) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/ClearSessionController.java (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -0,0 +1,62 @@ +/**************************************************************** + * 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.assessment.web.controller; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.lamsfoundation.lams.authoring.web.LamsAuthoringFinishController; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.WebApplicationContext; + +/** + * This class give a chance to clear HttpSession when user save/close authoring page. + * + * @author Steve.Ni + */ +@Controller +public class ClearSessionController extends LamsAuthoringFinishController { + + @Autowired + private WebApplicationContext applicationContext; + + @RequestMapping("/clearsession") + public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException { + super.execute(request, response, applicationContext); + } + + @Override + public void clearSession(String customiseSessionID, HttpSession session, ToolAccessMode mode) { + if (mode.isAuthor()) { + session.removeAttribute(customiseSessionID); + } + } + +} Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningController.java =================================================================== diff -u --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningController.java (revision 0) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningController.java (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -0,0 +1,1277 @@ +/**************************************************************** + * 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.assessment.web.controller; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.SortedSet; +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.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.learning.web.bean.ActivityPositionDTO; +import org.lamsfoundation.lams.learning.web.util.LearningWebUtil; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.tool.ToolAccessMode; +import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; +import org.lamsfoundation.lams.tool.assessment.dto.OptionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionSummary; +import org.lamsfoundation.lams.tool.assessment.model.Assessment; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentOptionAnswer; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentOverallFeedback; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentSession; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; +import org.lamsfoundation.lams.tool.assessment.service.AssessmentApplicationException; +import org.lamsfoundation.lams.tool.assessment.service.IAssessmentService; +import org.lamsfoundation.lams.tool.assessment.util.AnswerIntComparator; +import org.lamsfoundation.lams.tool.assessment.util.AssessmentSessionComparator; +import org.lamsfoundation.lams.tool.assessment.util.SequencableComparator; +import org.lamsfoundation.lams.tool.assessment.web.form.ReflectionForm; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.AlphanumComparator; +import org.lamsfoundation.lams.util.DateUtil; +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.stereotype.Controller; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.WebApplicationContext; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Andrey Balan + */ +@Controller +@RequestMapping("/learning") +public class LearningController { + + private static Logger log = Logger.getLogger(LearningController.class); + + @Autowired + @Qualifier("laasseAssessmentService") + private IAssessmentService service; + + @Autowired + private WebApplicationContext applicationContext; + + /** + * Read assessment data from database and put them into HttpSession. It will redirect to init.do directly after this + * method run successfully. + * + * This method will avoid read database again and lost un-saved resouce question lost when user "refresh page". + */ + @SuppressWarnings("unchecked") + @RequestMapping("/start") + public String start(HttpServletRequest request) throws ServletException, IllegalAccessException, + InstantiationException, InvocationTargetException, NoSuchMethodException { + + // initialize Session Map + SessionMap sessionMap = new SessionMap<>(); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + + // save toolContentID into HTTPSession + ToolAccessMode mode = WebUtil.readToolAccessModeParam(request, AttributeNames.PARAM_MODE, true); + + Long toolSessionId = new Long(request.getParameter(AssessmentConstants.PARAM_TOOL_SESSION_ID)); + + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + request.setAttribute(AttributeNames.ATTR_MODE, mode); + request.setAttribute(AttributeNames.PARAM_TOOL_SESSION_ID, toolSessionId); + + // get back the assessment and question list and display them on page + AssessmentUser user = null; + if ((mode != null) && mode.isTeacher()) { + // monitoring mode - user is specified in URL + // assessmentUser may be null if the user was force completed. + user = getSpecifiedUser(service, toolSessionId, + WebUtil.readIntParam(request, AttributeNames.PARAM_USER_ID, false)); + } else { + user = getCurrentUser(toolSessionId); + } + + Assessment assessment = service.getAssessmentBySessionId(toolSessionId); + + // support for leader select feature + AssessmentUser groupLeader = assessment.isUseSelectLeaderToolOuput() + ? service.checkLeaderSelectToolForSessionLeader(user, new Long(toolSessionId).longValue()) + : null; + if (assessment.isUseSelectLeaderToolOuput() && !mode.isTeacher()) { + + // forwards to the leaderSelection page + if (groupLeader == null) { + List groupUsers = service.getUsersBySession(new Long(toolSessionId).longValue()); + request.setAttribute(AssessmentConstants.ATTR_GROUP_USERS, groupUsers); + request.setAttribute(AssessmentConstants.ATTR_ASSESSMENT, assessment); + + return "pages/learning/waitforleader"; + } + + AssessmentResult lastLeaderResult = service.getLastAssessmentResult(assessment.getUid(), + groupLeader.getUserId()); + boolean isLastAttemptFinishedByLeader = lastLeaderResult != null + && lastLeaderResult.getFinishDate() != null; + + // forwards to the waitForLeader pages + boolean isNonLeader = !user.getUserId().equals(groupLeader.getUserId()); + if (assessment.getTimeLimit() != 0 && isNonLeader && !isLastAttemptFinishedByLeader) { + + //show waitForLeaderLaunchTimeLimit page if the leader hasn't started activity or hasn't pressed OK button to launch time limit + if (lastLeaderResult == null || lastLeaderResult.getTimeLimitLaunchedDate() == null) { + request.setAttribute(AssessmentConstants.PARAM_WAITING_MESSAGE_KEY, + "label.waiting.for.leader.launch.time.limit"); + return "pages/learning/waitForLeaderTimeLimit"; + } + + //if the time is up and leader hasn't submitted response - show waitForLeaderFinish page + boolean isTimeLimitExceeded = service.checkTimeLimitExceeded(assessment, groupLeader); + if (isTimeLimitExceeded) { + request.setAttribute(AssessmentConstants.PARAM_WAITING_MESSAGE_KEY, + "label.waiting.for.leader.finish"); + return "pages/learning/waitForLeaderTimeLimit"; + } + } + + // check if leader has submitted all answers + if (isLastAttemptFinishedByLeader) { + + // 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 + service.copyAnswersFromLeader(user, groupLeader); + } + } + + sessionMap.put(AssessmentConstants.ATTR_GROUP_LEADER, groupLeader); + boolean isUserLeader = service.isUserGroupLeader(user, new Long(toolSessionId)); + sessionMap.put(AssessmentConstants.ATTR_IS_USER_LEADER, isUserLeader); + + Set questionReferences = new TreeSet<>(new SequencableComparator()); + questionReferences.addAll(assessment.getQuestionReferences()); + HashMap questionToReferenceMap = new HashMap<>(); + ArrayList takenQuestion = new ArrayList<>(); + + //add non-random questions + for (QuestionReference questionReference : questionReferences) { + if (!questionReference.isRandomQuestion()) { + AssessmentQuestion question = questionReference.getQuestion(); + takenQuestion.add(question); + questionToReferenceMap.put(questionReference.getUid(), question); + } + } + + Set allQuestions = assessment.getQuestions(); + Collection availableQuestions = CollectionUtils.subtract(allQuestions, takenQuestion); + //add random questions (actually replacing them with real ones) + for (QuestionReference questionReference : questionReferences) { + if (questionReference.isRandomQuestion()) { + //pick a random element + Random rand = new Random(System.currentTimeMillis()); + AssessmentQuestion question = (AssessmentQuestion) availableQuestions.toArray()[rand + .nextInt(availableQuestions.size())]; + takenQuestion.add(question); + questionToReferenceMap.put(questionReference.getUid(), question); + availableQuestions.remove(question); + } + } + + //user is allowed to answer questions if assessment activity doesn't have leaders or he is the leader + boolean hasEditRight = !assessment.isUseSelectLeaderToolOuput() + || assessment.isUseSelectLeaderToolOuput() && isUserLeader; + + //showResults if user has finished the last result + AssessmentResult lastResult = service.getLastAssessmentResult(assessment.getUid(), user.getUserId()); + boolean showResults = (lastResult != null) && (lastResult.getFinishDate() != null); + + // get notebook entry + String entryText = new String(); + AssessmentUser notebookCreator = (groupLeader == null) ? user : groupLeader; + NotebookEntry notebookEntry = service.getEntry(toolSessionId, notebookCreator.getUserId().intValue()); + if (notebookEntry != null) { + entryText = notebookEntry.getEntry(); + } + + // basic information + sessionMap.put(AssessmentConstants.ATTR_TITLE, assessment.getTitle()); + sessionMap.put(AssessmentConstants.ATTR_INSTRUCTIONS, assessment.getInstructions()); + sessionMap.put(AssessmentConstants.ATTR_SHOW_RESULTS, showResults); + sessionMap.put(AssessmentConstants.ATTR_HAS_EDIT_RIGHT, hasEditRight); + sessionMap.put(AssessmentConstants.ATTR_USER_FINISHED, user.isSessionFinished()); + sessionMap.put(AttributeNames.ATTR_LEARNER_CONTENT_FOLDER, + service.getLearnerContentFolder(toolSessionId, user.getUserId())); + sessionMap.put(AttributeNames.PARAM_TOOL_SESSION_ID, toolSessionId); + sessionMap.put(AssessmentConstants.ATTR_USER, user); + sessionMap.put(AttributeNames.ATTR_MODE, mode); + // reflection information + sessionMap.put(AssessmentConstants.ATTR_REFLECTION_ON, assessment.isReflectOnActivity()); + sessionMap.put(AssessmentConstants.ATTR_REFLECTION_INSTRUCTION, assessment.getReflectInstructions()); + sessionMap.put(AssessmentConstants.ATTR_REFLECTION_ENTRY, entryText); + + //time limit + boolean isTimeLimitEnabled = hasEditRight && !showResults && assessment.getTimeLimit() != 0; + long secondsLeft = isTimeLimitEnabled ? service.getSecondsLeft(assessment, user) : 0; + sessionMap.put(AssessmentConstants.ATTR_SECONDS_LEFT, secondsLeft); + boolean isTimeLimitNotLaunched = (lastResult == null) || (lastResult.getTimeLimitLaunchedDate() == null); + sessionMap.put(AssessmentConstants.ATTR_IS_TIME_LIMIT_NOT_LAUNCHED, isTimeLimitNotLaunched); + + ActivityPositionDTO activityPosition = LearningWebUtil.putActivityPositionInRequestByToolSessionId( + toolSessionId, request, applicationContext.getServletContext()); + sessionMap.put(AttributeNames.ATTR_ACTIVITY_POSITION, activityPosition); + + // add define later support + if (assessment.isDefineLater()) { + return "pages/learning/definelater"; + } + + //check if there is submission deadline + Date submissionDeadline = assessment.getSubmissionDeadline(); + if (submissionDeadline != null) { + //store submission deadline to sessionMap + sessionMap.put(AssessmentConstants.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)) { + return "pages/learning/submissionDeadline"; + } + } + + //sort questions + LinkedList questionDtos = new LinkedList<>(); + for (QuestionReference questionReference : questionReferences) { + AssessmentQuestion question = questionToReferenceMap.get(questionReference.getUid()); + + QuestionDTO questionDto = question.getQuestionDTO(); + questionDto.setGrade(questionReference.getDefaultGrade()); + + questionDtos.add(questionDto); + } + + // shuffling + if (assessment.isShuffled()) { + ArrayList shuffledList = new ArrayList<>(questionDtos); + Collections.shuffle(shuffledList); + questionDtos = new LinkedList<>(shuffledList); + } + for (QuestionDTO questionDto : questionDtos) { + if (questionDto.isShuffle() || (questionDto.getType() == AssessmentConstants.QUESTION_TYPE_ORDERING)) { + ArrayList shuffledList = new ArrayList<>(questionDto.getOptionDtos()); + Collections.shuffle(shuffledList); + questionDto.setOptionDtos(new LinkedHashSet<>(shuffledList)); + } + if (questionDto.getType() == AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS) { + //sort answer options alphanumerically (as per LDEV-4326) + ArrayList optionsSortedByOptionString = new ArrayList<>(questionDto.getOptionDtos()); + optionsSortedByOptionString.sort(new Comparator() { + @Override + public int compare(OptionDTO o1, OptionDTO o2) { + String optionString1 = o1.getOptionString() != null ? o1.getOptionString() : ""; + String optionString2 = o2.getOptionString() != null ? o2.getOptionString() : ""; + + return AlphanumComparator.compareAlphnumerically(optionString1, optionString2); + } + }); + questionDto.setMatchingPairOptions(new LinkedHashSet<>(optionsSortedByOptionString)); + } + } + + //paging + List> pagedQuestionDtos = new ArrayList>(); + int maxQuestionsPerPage = ((assessment.getQuestionsPerPage() != 0) && hasEditRight) + ? assessment.getQuestionsPerPage() + : questionDtos.size(); + LinkedHashSet questionsForOnePage = new LinkedHashSet(); + pagedQuestionDtos.add(questionsForOnePage); + int count = 0; + for (QuestionDTO questionDto : questionDtos) { + questionsForOnePage.add(questionDto); + count++; + if ((questionsForOnePage.size() == maxQuestionsPerPage) && (count != questionDtos.size())) { + questionsForOnePage = new LinkedHashSet<>(); + pagedQuestionDtos.add(questionsForOnePage); + } + } + + sessionMap.put(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS, pagedQuestionDtos); + sessionMap.put(AssessmentConstants.ATTR_QUESTION_NUMBERING_OFFSET, 1); + sessionMap.put(AssessmentConstants.ATTR_PAGE_NUMBER, 1); + sessionMap.put(AssessmentConstants.ATTR_ASSESSMENT, assessment); + + // loadupLastAttempt for display purpose + loadupLastAttempt(sessionMap); + + if (showResults) { + + // display results page + showResults(request, sessionMap); + return "pages/learning/results"; + + } else { + // set attempt started + if (hasEditRight) { + service.setAttemptStarted(assessment, user, toolSessionId); + } + + return "pages/learning/learning"; + } + } + + /** + * Checks Leader Progress + */ + @RequestMapping("/checkLeaderProgress") + public String checkLeaderProgress(HttpServletRequest request, HttpServletResponse response) throws IOException { + Long toolSessionId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); + + AssessmentSession session = service.getSessionBySessionId(toolSessionId); + AssessmentUser leader = session.getGroupLeader(); + + //in case of time limit - prevent user from seeing questions page longer than time limit allows + boolean isTimeLimitExceeded = service.checkTimeLimitExceeded(session.getAssessment(), leader); + boolean isLeaderResponseFinalized = service.isLastAttemptFinishedByUser(leader); + + ObjectNode ObjectNode = JsonNodeFactory.instance.objectNode(); + ObjectNode.put("isPageRefreshRequested", isLeaderResponseFinalized || isTimeLimitExceeded); + response.setContentType("application/x-json;charset=utf-8"); + response.getWriter().print(ObjectNode); + return null; + } + + /** + * Shows next page. It's available only to leaders as non-leaders see all questions on one page. + */ + @RequestMapping("/nextPage") + public String nextPage(HttpServletRequest request) + throws ServletException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + return nextPage(request, false, -1); + } + + /** + * Auxiliary method to be called by nextPage(HttpServletRequest request, + * HttpServletResponse response) or submitAll. + * + * @param mapping + * @param request + * @param isAnswersValidationFailed + * submitAll() method may set it as true in case some of the pages miss required answers + * @param pageNumberWithUnasweredQuestions + * page number with questions required to be answered + * @return + */ + private String nextPage(HttpServletRequest request, boolean isAnswersValidationFailed, + int pageNumberWithUnasweredQuestions) + throws ServletException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Assessment assessment = (Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT); + AssessmentUser user = (AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER); + int oldPageNumber = (Integer) sessionMap.get(AssessmentConstants.ATTR_PAGE_NUMBER); + + //if AnswersValidationFailed - get pageNumber as request parameter and as method parameter otherwise + int pageNumberToOpen; + if (isAnswersValidationFailed) { + pageNumberToOpen = pageNumberWithUnasweredQuestions; + } else { + pageNumberToOpen = WebUtil.readIntParam(request, AssessmentConstants.ATTR_PAGE_NUMBER); + } + + int questionNumberingOffset = 0; + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + for (int i = 0; i < pageNumberToOpen - 1; i++) { + Set questionsForOnePage = pagedQuestionDtos.get(i); + questionNumberingOffset += questionsForOnePage.size(); + } + sessionMap.put(AssessmentConstants.ATTR_QUESTION_NUMBERING_OFFSET, ++questionNumberingOffset); + sessionMap.put(AssessmentConstants.ATTR_PAGE_NUMBER, pageNumberToOpen); + + boolean showResults = (Boolean) sessionMap.get(AssessmentConstants.ATTR_SHOW_RESULTS); + if (showResults) { + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "pages/learning/results"; + + } else { + //get user answers from request and store them into sessionMap + storeUserAnswersIntoSessionMap(request, oldPageNumber); + // store results from sessionMap into DB + storeUserAnswersIntoDatabase(sessionMap, true); + + long secondsLeft = service.getSecondsLeft(assessment, user); + sessionMap.put(AssessmentConstants.ATTR_SECONDS_LEFT, secondsLeft); + + // use redirect to prevent form resubmission + String redirectURL = "redirect:/pages/learning/learning.jsp"; + redirectURL = WebUtil.appendParameterToURL(redirectURL, AssessmentConstants.ATTR_SESSION_MAP_ID, + sessionMapID); + redirectURL = WebUtil.appendParameterToURL(redirectURL, + AssessmentConstants.ATTR_IS_ANSWERS_VALIDATION_FAILED, "" + isAnswersValidationFailed); + return redirectURL; + } + } + + /** + * Ajax call to get the remaining seconds. Needed when the page is reloaded in the browser to check with the server + * what the current values should be! Otherwise the learner can keep hitting reload after a page change or submit + * all (when questions are spread across pages) and increase their time! + * + * @return + * @throws JSONException + * @throws IOException + */ + @RequestMapping("/getSecondsLeft") + public String getSecondsLeft(HttpServletRequest request, HttpServletResponse response) throws ServletException, + IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException { + + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Assessment assessment = (Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT); + AssessmentUser user = (AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER); + long secondsLeft = service.getSecondsLeft(assessment, user); + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put(AssessmentConstants.ATTR_SECONDS_LEFT, secondsLeft); + response.setContentType("application/x-json;charset=utf-8"); + response.getWriter().print(responseJSON); + return null; + } + + /** + * Handling submittion of MarkHedging type of Questions (in case of leader aware tool). + * + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + @RequestMapping("/submitSingleMarkHedgingQuestion") + public String submitSingleMarkHedgingQuestion(HttpServletRequest request) + throws ServletException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + int pageNumber = (Integer) sessionMap.get(AssessmentConstants.ATTR_PAGE_NUMBER); + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + Long userId = ((AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER)).getUserId(); + Long toolSessionId = (Long) sessionMap.get(AssessmentConstants.ATTR_TOOL_SESSION_ID); + Assessment assessment = service.getAssessmentBySessionId(toolSessionId); + + //get user answers from request and store them into sessionMap + storeUserAnswersIntoSessionMap(request, pageNumber); + + // store results from sessionMap into DB + Long singleMarkHedgingQuestionUid = WebUtil.readLongParam(request, "singleMarkHedgingQuestionUid"); + boolean isResultsStored = service.storeUserAnswers(assessment, userId, pagedQuestionDtos, + singleMarkHedgingQuestionUid, false); + // result was not stored in case user was prohibited from submitting (or autosubmitting) answers (e.g. when + // using 2 browsers). Then show last stored results + if (!isResultsStored) { + //loadupLastAttempt(sessionMap); + } + + //find according question in order to get its mark + QuestionDTO questionDto = null; + for (Set questionsForOnePage : pagedQuestionDtos) { + for (QuestionDTO questionDtoIter : questionsForOnePage) { + if (questionDtoIter.getUid().equals(singleMarkHedgingQuestionUid)) { + questionDto = questionDtoIter; + questionDto.setResponseSubmitted(true); + } + } + } + + // populate info for displaying results page + //prepareResultsPageData(sessionMap); + + request.setAttribute("assessment", assessment); + request.setAttribute("question", questionDto); + long questionIndex = WebUtil.readLongParam(request, "questionIndex"); + request.setAttribute("questionIndex", questionIndex); + request.setAttribute("isLeadershipEnabled", assessment.isUseSelectLeaderToolOuput()); + request.setAttribute("isUserLeader", sessionMap.get(AssessmentConstants.ATTR_IS_USER_LEADER)); + + return "pages/learning/results/markhedging"; + } + + /** + * Display same entire authoring page content from HttpSession variable. + * + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + @RequestMapping("/submitAll") + public String submitAll(HttpServletRequest request) + throws ServletException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + int pageNumber = (Integer) sessionMap.get(AssessmentConstants.ATTR_PAGE_NUMBER); + + //get user answers from request and store them into sessionMap + storeUserAnswersIntoSessionMap(request, pageNumber); + + boolean isTimelimitExpired = WebUtil.readBooleanParam(request, "isTimelimitExpired", false); + if (!isTimelimitExpired) { + + // check all required questions got answered + int pageNumberWithUnasweredQuestions = validateAnswers(sessionMap); + // if some were not then forward to nextPage() + if (pageNumberWithUnasweredQuestions != 0) { + return nextPage(request, true, pageNumberWithUnasweredQuestions); + } + } + + //store results from sessionMap into DB + boolean isResultsStored = storeUserAnswersIntoDatabase(sessionMap, false); + // result was not stored in case user was prohibited from submitting (or autosubmitting) answers (e.g. when + // using 2 browsers). Then show last stored results + if (!isResultsStored) { + loadupLastAttempt(sessionMap); + } + + String redirectURL = "redirect:/learning/start.do"; + ToolAccessMode mode = (ToolAccessMode) sessionMap.get(AttributeNames.ATTR_MODE); + if (mode != null) { + redirectURL = WebUtil.appendParameterToURL(redirectURL, AttributeNames.ATTR_MODE, mode.toString()); + } + redirectURL = WebUtil.appendParameterToURL(redirectURL, AssessmentConstants.ATTR_TOOL_SESSION_ID, + ((Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID)).toString()); + return redirectURL; + } + + /** + * User pressed Resubmit button. + */ + @RequestMapping("/resubmit") + public String resubmit(HttpServletRequest request) throws ServletException { + + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Long toolSessionId = (Long) sessionMap.get(AssessmentConstants.ATTR_TOOL_SESSION_ID); + ToolAccessMode mode = (ToolAccessMode) sessionMap.get(AttributeNames.ATTR_MODE); + Assessment assessment = service.getAssessmentBySessionId(toolSessionId); + AssessmentUser assessmentUser = (AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER); + Long userId = assessmentUser.getUserId(); + service.unsetSessionFinished(toolSessionId, userId); + + Date lastAttemptStartingDate = service.getLastAssessmentResult(assessment.getUid(), userId).getStartDate(); + + // set attempt started: create a new one + mark previous as not being the latest any longer + service.setAttemptStarted(assessment, assessmentUser, toolSessionId); + + // in case of content was modified in monitor - redirect to start.do in order to refresh info from the DB + if (assessment.isContentModifiedInMonitor(lastAttemptStartingDate)) { + String redirectURL = "redirect:/learning/start.do"; + redirectURL = WebUtil.appendParameterToURL(redirectURL, AttributeNames.PARAM_MODE, mode.toString()); + redirectURL = WebUtil.appendParameterToURL(redirectURL, AssessmentConstants.PARAM_TOOL_SESSION_ID, + toolSessionId.toString()); + return redirectURL; + + //otherwise use data from SessionMap + } else { + + sessionMap.put(AssessmentConstants.ATTR_SHOW_RESULTS, false); + sessionMap.put(AssessmentConstants.ATTR_PAGE_NUMBER, 1); + sessionMap.put(AssessmentConstants.ATTR_QUESTION_NUMBERING_OFFSET, 1); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + // clear isUserFailed indicator + sessionMap.put(AssessmentConstants.ATTR_IS_USER_FAILED, false); + + // time limit feature + sessionMap.put(AssessmentConstants.ATTR_IS_TIME_LIMIT_NOT_LAUNCHED, true); + sessionMap.put(AssessmentConstants.ATTR_SECONDS_LEFT, assessment.getTimeLimit() * 60); + + return "pages/learning/learning"; + } + + } + + /** + * Finish learning session. + */ + @RequestMapping("/finish") + public String finish(HttpServletRequest request) { + // get back SessionMap + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + String nextActivityUrl = null; + try { + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + Long userID = new Long(user.getUserID().longValue()); + Long sessionId = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + + nextActivityUrl = service.finishToolSession(sessionId, userID); + request.setAttribute(AssessmentConstants.ATTR_NEXT_ACTIVITY_URL, nextActivityUrl); + } catch (AssessmentApplicationException e) { + LearningController.log.error("Failed get next activity url:" + e.getMessage()); + } + + return "pages/learning/finish"; + } + + /** + * Move up current option. + */ + @RequestMapping("/upOption") + public String upOption(HttpServletRequest request) { + return switchOption(request, true); + } + + /** + * Move down current option. + */ + @RequestMapping("/downOption") + public String downOption(HttpServletRequest request) { + return switchOption(request, false); + } + + private String switchOption(HttpServletRequest request, boolean up) { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + int pageNumber = (Integer) sessionMap.get(AssessmentConstants.ATTR_PAGE_NUMBER); + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + Set questionsForOnePage = pagedQuestionDtos.get(pageNumber - 1); + Long questionUid = new Long(request.getParameter(AssessmentConstants.PARAM_QUESTION_UID)); + + QuestionDTO questionDto = null; + for (QuestionDTO questionDtoIter : questionsForOnePage) { + if (questionDtoIter.getUid().equals(questionUid)) { + questionDto = questionDtoIter; + break; + } + } + + Set optionDtoList = questionDto.getOptionDtos(); + + int optionIndex = NumberUtils.stringToInt(request.getParameter(AssessmentConstants.PARAM_OPTION_INDEX), -1); + if (optionIndex != -1) { + List rList = new ArrayList<>(optionDtoList); + + // get current and the target item, and switch their sequnece + OptionDTO option = rList.remove(optionIndex); + if (up) { + rList.add(--optionIndex, option); + } else { + rList.add(++optionIndex, option); + } + + // put back list + optionDtoList = new LinkedHashSet<>(rList); + questionDto.setOptionDtos(optionDtoList); + } + + request.setAttribute(AssessmentConstants.ATTR_QUESTION_FOR_ORDERING, questionDto); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return "pages/learning/parts/ordering"; + } + + /** + * auto saves responses + */ + @RequestMapping("/autoSaveAnswers") + public String autoSaveAnswers(HttpServletRequest request) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + int pageNumber = (Integer) sessionMap.get(AssessmentConstants.ATTR_PAGE_NUMBER); + + //get user answers from request and store them into sessionMap + storeUserAnswersIntoSessionMap(request, pageNumber); + //store results from sessionMap into DB + storeUserAnswersIntoDatabase(sessionMap, true); + + return null; + } + + /** + * Stores date when user has started activity with time limit + */ + @RequestMapping("/launchTimeLimit") + public String launchTimeLimit(HttpServletRequest request) { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + + Long assessmentUid = ((Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT)).getUid(); + Long userId = ((AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER)).getUserId(); + sessionMap.put(AssessmentConstants.ATTR_IS_TIME_LIMIT_NOT_LAUNCHED, false); + + service.launchTimeLimit(assessmentUid, userId); + + return null; + } + + /** + * Display empty reflection form. + */ + @RequestMapping("/newReflection") + public String newReflection(@ModelAttribute("reflectionForm") ReflectionForm refForm, HttpServletRequest request) { + + // get session value + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + 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 = service.getEntry(toolSessionID, user.getUserID()); + + if (entry != null) { + refForm.setEntryText(entry.getEntry()); + } + + return "pages/learning/notebook"; + } + + /** + * Submit reflection form input database. + */ + @RequestMapping("/submitReflection") + public String submitReflection(@ModelAttribute("reflectionForm") ReflectionForm refForm, + HttpServletRequest request) { + Integer userId = refForm.getUserID(); + + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Long sessionId = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + + // check for existing notebook entry + NotebookEntry entry = service.getEntry(sessionId, userId); + + if (entry == null) { + // create new entry + service.createNotebookEntry(sessionId, userId, refForm.getEntryText()); + } else { + // update existing entry + entry.setEntry(refForm.getEntryText()); + entry.setLastModified(new Date()); + service.updateEntry(entry); + } + + return finish(request); + } + + // ************************************************************************************* + // Private method + // ************************************************************************************* + + /** + * Get back user answers from request and store it into sessionMap. + * + * @param request + * @param pageNumber + * number of the page to process + */ + private void storeUserAnswersIntoSessionMap(HttpServletRequest request, int pageNumber) { + String sessionMapID = WebUtil.readStrParam(request, AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Assessment assessment = (Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT); + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + Set questionsForOnePage = pagedQuestionDtos.get(pageNumber - 1); + + for (int i = 0; i < questionsForOnePage.size(); i++) { + Long questionUid = WebUtil.readLongParam(request, AssessmentConstants.PARAM_QUESTION_UID + i); + QuestionDTO questionDto = null; + for (QuestionDTO sessionQuestion : questionsForOnePage) { + if (sessionQuestion.getUid().equals(questionUid)) { + questionDto = sessionQuestion; + break; + } + } + + // in case learner goes to the next page and refreshes it right after this. And thus it's not possible to know + // previous page number in this case. but anyway no need to save answers + if (questionDto == null) { + break; + } + + int questionType = questionDto.getType(); + if (questionType == AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE) { + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + boolean answerBoolean = false; + if (questionDto.isMultipleAnswersAllowed()) { + String answerString = request.getParameter( + AssessmentConstants.ATTR_QUESTION_PREFIX + i + "_" + optionDto.getSequenceId()); + answerBoolean = !StringUtils.isBlank(answerString); + } else { + String answerString = request.getParameter(AssessmentConstants.ATTR_QUESTION_PREFIX + i); + if (answerString != null) { + int optionSequenceId = Integer.parseInt(answerString); + answerBoolean = (optionDto.getSequenceId() == optionSequenceId); + } + } + optionDto.setAnswerBoolean(answerBoolean); + } + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS) { + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + int answerInt = WebUtil.readIntParam(request, + AssessmentConstants.ATTR_QUESTION_PREFIX + i + "_" + optionDto.getSequenceId()); + optionDto.setAnswerInt(answerInt); + } + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER) { + String answerString = request.getParameter(AssessmentConstants.ATTR_QUESTION_PREFIX + i); + questionDto.setAnswerString(answerString); + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_NUMERICAL) { + String answerString = request.getParameter(AssessmentConstants.ATTR_QUESTION_PREFIX + i); + questionDto.setAnswerString(answerString); + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_TRUE_FALSE) { + String answerString = request.getParameter(AssessmentConstants.ATTR_QUESTION_PREFIX + i); + if (answerString != null) { + questionDto.setAnswerBoolean(Boolean.parseBoolean(answerString)); + questionDto.setAnswerString("answered"); + } + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_ESSAY) { + String answerString = request.getParameter(AssessmentConstants.ATTR_QUESTION_PREFIX + i); + answerString = answerString.replaceAll("[\n\r\f]", ""); + questionDto.setAnswerString(answerString); + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_ORDERING) { + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING) { + + //store hedging marks + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + Integer markHedging = WebUtil.readIntParam(request, + AssessmentConstants.ATTR_QUESTION_PREFIX + i + "_" + optionDto.getSequenceId(), true); + if (markHedging != null) { + optionDto.setAnswerInt(markHedging); + } + } + + //store justification of hedging if enabled + if (questionDto.isHedgingJustificationEnabled()) { + String answerString = request.getParameter(AssessmentConstants.ATTR_QUESTION_PREFIX + i); +// answerString = answerString.replaceAll("[\n\r\f]", ""); + questionDto.setAnswerString(answerString); + } + } + + // store confidence level entered by the learner + if (assessment.isEnableConfidenceLevels()) { + int confidenceLevel = WebUtil.readIntParam(request, + AssessmentConstants.ATTR_CONFIDENCE_LEVEL_PREFIX + i); + questionDto.setConfidenceLevel(confidenceLevel); + } + } + } + + /** + * Checks whether all required questions were answered and all essay question with min words limit have fullfilled + * that. + * + * @param sessionMap + * @return 0 if all questions were answered OK, or a number of the first page that contains questions with issues + */ + private int validateAnswers(SessionMap sessionMap) { + + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + + //array of missing required questions + boolean isAllQuestionsAnswered = true; + boolean isAllQuestionsReachedMinWordsLimit = true; + + //iterate through all pages to find first that contains missing required questions + int pageCount; + for (pageCount = 0; pageCount < pagedQuestionDtos.size(); pageCount++) { + Set questionsForOnePage = pagedQuestionDtos.get(pageCount); + + for (QuestionDTO questionDto : questionsForOnePage) { + int questionType = questionDto.getType(); + + //enforce all hedging marks question type to be answered as well + if (questionDto.isAnswerRequired() + || (questionType == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING)) { + + boolean isAnswered = false; + + if (questionType == AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE) { + + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + isAnswered |= optionDto.getAnswerBoolean(); + } + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS) { + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + isAnswered |= optionDto.getAnswerInt() != 0; + } + + } else if ((questionType == AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER) + || (questionType == AssessmentConstants.QUESTION_TYPE_NUMERICAL) + || (questionType == AssessmentConstants.QUESTION_TYPE_TRUE_FALSE) + || (questionType == AssessmentConstants.QUESTION_TYPE_ESSAY)) { + isAnswered |= StringUtils.isNotBlank(questionDto.getAnswerString()); + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_ORDERING) { + isAnswered = true; + + } else if (questionType == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING) { + + //verify sum of all hedging marks is equal to question's grade + int sumMarkHedging = 0; + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + sumMarkHedging += optionDto.getAnswerInt(); + } + isAnswered = sumMarkHedging == questionDto.getGrade(); + + //verify justification of hedging is provided if it was enabled + if (questionDto.isHedgingJustificationEnabled()) { + isAnswered &= StringUtils.isNotBlank(questionDto.getAnswerString()); + } + } + + // check all questions were answered + if (!isAnswered) { + isAllQuestionsAnswered = false; + break; + } + + } + + if ((questionDto.getType() == AssessmentConstants.QUESTION_TYPE_ESSAY) + && (questionDto.getMinWordsLimit() > 0)) { + + if (questionDto.getAnswerString() == null) { + isAllQuestionsReachedMinWordsLimit = false; + break; + + } else { + boolean isMinWordsLimitReached = ValidationUtil.isMinWordsLimitReached( + questionDto.getAnswerString(), questionDto.getMinWordsLimit(), + questionDto.isAllowRichEditor()); + // check min words limit is reached + if (!isMinWordsLimitReached) { + isAllQuestionsReachedMinWordsLimit = false; + break; + } + } + + } + } + + //if found answers required to be fixed by learners, stop here + if (!isAllQuestionsAnswered || !isAllQuestionsReachedMinWordsLimit) { + return pageCount + 1; + } + } + + return 0; + } + + /** + * Prepare data for displaying results page + */ + private void showResults(HttpServletRequest request, SessionMap sessionMap) { + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + Assessment assessment = (Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT); + Long userId = ((AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER)).getUserId(); + + int dbResultCount = service.getAssessmentResultCount(assessment.getUid(), userId); + if (dbResultCount > 0) { + + // release object from the cache (it's required when we have modified result object in the same request) + AssessmentResult result = service.getLastFinishedAssessmentResultNotFromChache(assessment.getUid(), userId); + + for (Set questionsForOnePage : pagedQuestionDtos) { + for (QuestionDTO questionDto : questionsForOnePage) { + + // find corresponding questionResult + for (AssessmentQuestionResult questionResult : result.getQuestionResults()) { + if (questionDto.getUid().equals(questionResult.getAssessmentQuestion().getUid())) { + + // copy questionResult's info to the question + questionDto.setMark(questionResult.getMark()); + questionDto.setResponseSubmitted(questionResult.getFinishDate() != null); + questionDto.setPenalty(questionResult.getPenalty()); + questionDto.setQuestionFeedback(null); + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + if (optionDto.getUid().equals(questionResult.getSubmittedOptionUid())) { + questionDto.setQuestionFeedback(optionDto.getFeedback()); + break; + } + } + // required for showing right/wrong answers icons on results page correctly + if (questionDto.getType() == AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER + || questionDto.getType() == AssessmentConstants.QUESTION_TYPE_NUMERICAL) { + boolean isAnsweredCorrectly = false; + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + if (optionDto.getUid().equals(questionResult.getSubmittedOptionUid())) { + isAnsweredCorrectly = optionDto.getGrade() > 0; + break; + } + } + questionDto.setAnswerBoolean(isAnsweredCorrectly); + } + + // required for markandpenalty area and if it's on - on question's summary page + List questionResults = service + .getAssessmentQuestionResultList(assessment.getUid(), userId, questionDto.getUid()); + questionDto.setQuestionResults(questionResults); + } + } + } + } + + Date timeTaken = result.getFinishDate() == null ? new Date(0) + : new Date(result.getFinishDate().getTime() - result.getStartDate().getTime()); + result.setTimeTaken(timeTaken); + if (assessment.isAllowOverallFeedbackAfterQuestion()) { + int percentageCorrectAnswers = (int) (result.getGrade() * 100 / result.getMaximumGrade()); + ArrayList overallFeedbacks = new ArrayList( + assessment.getOverallFeedbacks()); + int lastBorder = 0; + for (int i = overallFeedbacks.size() - 1; i >= 0; i--) { + AssessmentOverallFeedback overallFeedback = overallFeedbacks.get(i); + if ((percentageCorrectAnswers >= lastBorder) + && (percentageCorrectAnswers <= overallFeedback.getGradeBoundary())) { + result.setOverallFeedback(overallFeedback.getFeedback()); + break; + } + lastBorder = overallFeedback.getGradeBoundary(); + } + } + + // calculate whether user has failed this attempt + int passingMark = assessment.getPassingMark(); + double gradeRoundedTo2DecimalPlaces = Math.round(result.getGrade() * 100.0) / 100.0; + boolean isUserFailed = ((passingMark != 0) && (passingMark > gradeRoundedTo2DecimalPlaces)); + sessionMap.put(AssessmentConstants.ATTR_IS_USER_FAILED, isUserFailed); + + sessionMap.put(AssessmentConstants.ATTR_ASSESSMENT_RESULT, result); + + // if answers are going to be disclosed, prepare data for the table in results page + if (assessment.isAllowDiscloseAnswers()) { + // such entities should not go into session map, but as request attributes instead + SortedSet sessions = new TreeSet( + new AssessmentSessionComparator()); + sessions.addAll(service.getSessionsByContentId(assessment.getContentId())); + request.setAttribute("sessions", sessions); + + Map questionSummaries = service.getQuestionSummaryForExport(assessment); + request.setAttribute("questionSummaries", questionSummaries); + } + } + + //calculate whether isResubmitAllowed + int attemptsAllowed = assessment.getAttemptsAllowed(); + boolean isResubmitAllowed = ((attemptsAllowed > dbResultCount) | (attemptsAllowed == 0)); + sessionMap.put(AssessmentConstants.ATTR_IS_RESUBMIT_ALLOWED, isResubmitAllowed); + } + + private void loadupLastAttempt(SessionMap sessionMap) { + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + Long assessmentUid = ((Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT)).getUid(); + Long userId = ((AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER)).getUserId(); + //get the latest result (it can be unfinished one) + AssessmentResult lastResult = service.getLastAssessmentResult(assessmentUid, userId); + //if there is no results yet - no action required + if (lastResult == null) { + return; + } + + //get the latest finished result (required for mark hedging type of questions only) + AssessmentResult lastFinishedResult = null; + if (lastResult.getFinishDate() == null) { + lastFinishedResult = service.getLastFinishedAssessmentResult(assessmentUid, userId); + } + + for (Set questionsForOnePage : pagedQuestionDtos) { + for (QuestionDTO questionDto : questionsForOnePage) { + + //load last finished results for hedging type of questions (in order to prevent retry) + Set questionResults = lastResult.getQuestionResults(); + if ((questionDto.getType() == AssessmentConstants.QUESTION_TYPE_MARK_HEDGING) + && (lastResult.getFinishDate() == null) && (lastFinishedResult != null)) { + questionResults = lastFinishedResult.getQuestionResults(); + } + + for (AssessmentQuestionResult questionResult : questionResults) { + if (questionDto.getUid().equals(questionResult.getAssessmentQuestion().getUid())) { + questionDto.setAnswerBoolean(questionResult.getAnswerBoolean()); + questionDto.setAnswerFloat(questionResult.getAnswerFloat()); + questionDto.setAnswerString(questionResult.getAnswerString()); + questionDto.setMark(questionResult.getMark()); + questionDto.setResponseSubmitted(questionResult.getFinishDate() != null); + questionDto.setPenalty(questionResult.getPenalty()); + questionDto.setConfidenceLevel(questionResult.getConfidenceLevel()); + + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + + for (AssessmentOptionAnswer optionAnswer : questionResult.getOptionAnswers()) { + if (optionDto.getUid().equals(optionAnswer.getOptionUid())) { + optionDto.setAnswerBoolean(optionAnswer.getAnswerBoolean()); + optionDto.setAnswerInt(optionAnswer.getAnswerInt()); + break; + } + } + } + + //sort ordering type of question in order to show how learner has sorted them + if (questionDto.getType() == AssessmentConstants.QUESTION_TYPE_ORDERING) { + + //don't sort ordering type of questions that haven't been submitted to not break their shuffled order + boolean isOptionAnswersNeverSubmitted = true; + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + if (optionDto.getAnswerInt() != 0) { + isOptionAnswersNeverSubmitted = false; + } + } + + if (!isOptionAnswersNeverSubmitted) { + TreeSet orderedSet = new TreeSet<>(new AnswerIntComparator()); + orderedSet.addAll(questionDto.getOptionDtos()); + questionDto.setOptionDtos(orderedSet); + } + } + + // set answerTotalGrade to let jsp know whether the question was answered correctly/partly/incorrectly even if mark=0 + if (questionDto.getType() == AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE) { + float totalGrade = 0; + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + if (optionDto.getAnswerBoolean()) { + totalGrade += optionDto.getGrade(); + } + } + questionDto.setAnswerTotalGrade(totalGrade); + } + + break; + } + } + } + } + } + + /** + * Store user answers in DB in last unfinished attempt and notify teachers about it. + * + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + private boolean storeUserAnswersIntoDatabase(SessionMap sessionMap, boolean isAutosave) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + + List> pagedQuestionDtos = (List>) sessionMap + .get(AssessmentConstants.ATTR_PAGED_QUESTION_DTOS); + Long toolSessionId = (Long) sessionMap.get(AssessmentConstants.ATTR_TOOL_SESSION_ID); + Long userId = ((AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER)).getUserId(); + ToolAccessMode mode = (ToolAccessMode) sessionMap.get(AttributeNames.ATTR_MODE); + Assessment assessment = service.getAssessmentBySessionId(toolSessionId); + + boolean isResultsStored = service.storeUserAnswers(assessment, userId, pagedQuestionDtos, null, isAutosave); + + // notify teachers + if ((mode != null) && !mode.isTeacher() && !isAutosave && isResultsStored + && assessment.isNotifyTeachersOnAttemptCompletion()) { + AssessmentUser assessmentUser = getCurrentUser(toolSessionId); + String fullName = assessmentUser.getLastName() + " " + assessmentUser.getFirstName(); + service.notifyTeachersOnAttemptCompletion(toolSessionId, fullName); + } + + return isResultsStored; + } + + private AssessmentUser getCurrentUser(Long sessionId) { + // try to get form system session + HttpSession ss = SessionManager.getSession(); + // get back login user DTO + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + AssessmentUser assessmentUser = service.getUserByIDAndSession(new Long(user.getUserID().intValue()), sessionId); + + if (assessmentUser == null) { + AssessmentSession session = service.getSessionBySessionId(sessionId); + assessmentUser = new AssessmentUser(user, session); + service.createUser(assessmentUser); + } + return assessmentUser; + } + + private AssessmentUser getSpecifiedUser(IAssessmentService service, Long sessionId, Integer userId) { + AssessmentUser assessmentUser = service.getUserByIDAndSession(new Long(userId.intValue()), sessionId); + if (assessmentUser == null) { + LearningController.log.error( + "Unable to find specified user for assessment activity. Screens are likely to fail. SessionId=" + + sessionId + " UserId=" + userId); + } + return assessmentUser; + } + +} Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java =================================================================== diff -u --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (revision 0) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -0,0 +1,647 @@ +/**************************************************************** + * 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.assessment.web.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.gradebook.util.GradebookConstants; +import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; +import org.lamsfoundation.lams.tool.assessment.dto.AssessmentResultDTO; +import org.lamsfoundation.lams.tool.assessment.dto.AssessmentUserDTO; +import org.lamsfoundation.lams.tool.assessment.dto.LeaderResultsDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionSummary; +import org.lamsfoundation.lams.tool.assessment.dto.ReflectDTO; +import org.lamsfoundation.lams.tool.assessment.dto.SessionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.UserSummary; +import org.lamsfoundation.lams.tool.assessment.model.Assessment; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentSession; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; +import org.lamsfoundation.lams.tool.assessment.service.IAssessmentService; +import org.lamsfoundation.lams.tool.assessment.util.AssessmentEscapeUtils; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.ExcelCell; +import org.lamsfoundation.lams.util.ExcelUtil; +import org.lamsfoundation.lams.util.JsonUtil; +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.RequestMapping; +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; + +@Controller +@RequestMapping("/monitoring") +public class MonitoringController { + public static Logger log = Logger.getLogger(MonitoringController.class); + + @Autowired + @Qualifier("laasseAssessmentService") + private IAssessmentService service; + + @RequestMapping("/summary") + public String summary(HttpServletRequest request, + HttpServletResponse response) { + + // initialize Session Map + SessionMap sessionMap = new SessionMap<>(); + request.getSession().setAttribute(sessionMap.getSessionID(), sessionMap); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + + Long contentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + List sessionDtos = service.getSessionDtos(contentId, false); + + Assessment assessment = service.getAssessmentByContentId(contentId); + + //set SubmissionDeadline, if any + if (assessment.getSubmissionDeadline() != null) { + Date submissionDeadline = assessment.getSubmissionDeadline(); + HttpSession ss = SessionManager.getSession(); + UserDTO teacher = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone teacherTimeZone = teacher.getTimeZone(); + Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(teacherTimeZone, submissionDeadline); + request.setAttribute(AssessmentConstants.ATTR_SUBMISSION_DEADLINE, tzSubmissionDeadline.getTime()); + // use the unconverted time, as convertToStringForJSON() does the timezone conversion if needed + request.setAttribute(AssessmentConstants.ATTR_SUBMISSION_DEADLINE_DATESTRING, + DateUtil.convertToStringForJSON(submissionDeadline, request.getLocale())); + + } + + // Create reflectList if reflection is enabled. + if (assessment.isReflectOnActivity()) { + List reflectList = service.getReflectList(assessment.getContentId()); + // Add reflectList to sessionMap + sessionMap.put(AssessmentConstants.ATTR_REFLECT_LIST, reflectList); + } + + //prepare list of the questions to display in question drop down menu, filtering out questions that aren't supposed to be answered + Set questionList = new TreeSet<>(); + //in case there is at least one random question - we need to show all questions in a drop down select + if (assessment.hasRandomQuestion()) { + questionList.addAll(assessment.getQuestions()); + + //show only questions from question list otherwise + } else { + for (QuestionReference reference : (Set) assessment.getQuestionReferences()) { + questionList.add(reference.getQuestion()); + } + } + + //prepare toolOutputDefinitions and activityEvaluation + List toolOutputDefinitions = new ArrayList<>(); + toolOutputDefinitions.add(AssessmentConstants.OUTPUT_NAME_LEARNER_TOTAL_SCORE); + toolOutputDefinitions.add(AssessmentConstants.OUTPUT_NAME_BEST_SCORE); + toolOutputDefinitions.add(AssessmentConstants.OUTPUT_NAME_FIRST_SCORE); + toolOutputDefinitions.add(AssessmentConstants.OUTPUT_NAME_AVERAGE_SCORE); + String activityEvaluation = service.getActivityEvaluation(contentId); + sessionMap.put(AssessmentConstants.ATTR_TOOL_OUTPUT_DEFINITIONS, toolOutputDefinitions); + sessionMap.put(AssessmentConstants.ATTR_ACTIVITY_EVALUATION, activityEvaluation); + + // cache into sessionMap + boolean isGroupedActivity = service.isGroupedActivity(contentId); + sessionMap.put(AssessmentConstants.ATTR_IS_GROUPED_ACTIVITY, isGroupedActivity); + sessionMap.put("sessionDtos", sessionDtos); + sessionMap.put(AssessmentConstants.ATTR_ASSESSMENT, assessment); + sessionMap.put(AssessmentConstants.ATTR_QUESTION_LIST, questionList); + sessionMap.put(AssessmentConstants.ATTR_TOOL_CONTENT_ID, contentId); + sessionMap.put(AttributeNames.PARAM_CONTENT_FOLDER_ID, + WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID)); + return "pages/monitoring/monitoring"; + } + + @RequestMapping("/userMasterDetail") + public String userMasterDetail(HttpServletRequest request, HttpServletResponse response) { + Long userId = WebUtil.readLongParam(request, AttributeNames.PARAM_USER_ID); + Long sessionId = WebUtil.readLongParam(request, AssessmentConstants.PARAM_SESSION_ID); + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + AssessmentResultDTO result = service.getUserMasterDetail(sessionId, userId); + + request.setAttribute(AssessmentConstants.ATTR_ASSESSMENT_RESULT, result); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return (result == null) ? null : "pages/monitoring/parts/masterDetailLoadUp"; + } + + @RequestMapping("/questionSummary") + public String questionSummary(HttpServletRequest request, + HttpServletResponse response) { + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + + Long questionUid = WebUtil.readLongParam(request, AssessmentConstants.PARAM_QUESTION_UID); + if (questionUid.equals(-1)) { + return null; + } + Long contentId = (Long) sessionMap.get(AssessmentConstants.ATTR_TOOL_CONTENT_ID); + QuestionSummary questionSummary = service.getQuestionSummary(contentId, questionUid); + + request.setAttribute(AssessmentConstants.ATTR_QUESTION_SUMMARY, questionSummary); + return "pages/monitoring/parts/questionsummary"; + } + + @RequestMapping("/userSummary") + public String userSummary(HttpServletRequest request, HttpServletResponse response) { + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + + Long userId = WebUtil.readLongParam(request, AttributeNames.PARAM_USER_ID); + Long sessionId = WebUtil.readLongParam(request, AssessmentConstants.PARAM_SESSION_ID); + Long contentId = (Long) sessionMap.get(AssessmentConstants.ATTR_TOOL_CONTENT_ID); + UserSummary userSummary = service.getUserSummary(contentId, userId, sessionId); + + request.setAttribute(AssessmentConstants.ATTR_USER_SUMMARY, userSummary); + return "pages/monitoring/parts/usersummary"; + } + + @RequestMapping("/saveUserGrade") + public String saveUserGrade(HttpServletRequest request, HttpServletResponse response) { + + if ((request.getParameter(AssessmentConstants.PARAM_NOT_A_NUMBER) == null) + && !StringUtils.isEmpty(request.getParameter(AssessmentConstants.PARAM_QUESTION_RESULT_UID))) { + Long questionResultUid = WebUtil.readLongParam(request, AssessmentConstants.PARAM_QUESTION_RESULT_UID); + float newGrade = Float.valueOf(request.getParameter(AssessmentConstants.PARAM_GRADE)); + service.changeQuestionResultMark(questionResultUid, newGrade); + } + + return null; + } + + /** + * Set Submission Deadline + */ + @RequestMapping("/setSubmissionDeadline") + public String setSubmissionDeadline(HttpServletRequest request, HttpServletResponse response) throws IOException { + + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + Assessment assessment = service.getAssessmentByContentId(contentID); + + Long dateParameter = WebUtil.readLongParam(request, AssessmentConstants.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()); + + } + assessment.setSubmissionDeadline(tzSubmissionDeadline); + service.saveOrUpdateAssessment(assessment); + + response.setContentType("text/plain;charset=utf-8"); + response.getWriter().print(formattedDate); + return null; + } + + /** + * Set tool's activityEvaluation + * + * @param mapping + * @param form + * @param request + * @param response + * @return + * @throws JSONException + * @throws IOException + */ + @RequestMapping("/setActivityEvaluation") + public String setActivityEvaluation(HttpServletRequest request, HttpServletResponse response) throws IOException { + + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + + Long contentID = (Long) sessionMap.get(AssessmentConstants.ATTR_TOOL_CONTENT_ID); + String activityEvaluation = WebUtil.readStrParam(request, AssessmentConstants.ATTR_ACTIVITY_EVALUATION, true); + service.setActivityEvaluation(contentID, activityEvaluation); + + // update the session ready for stats tab to be reloaded otherwise flicking between tabs + // causes the old value to be redisplayed + sessionMap.put(AssessmentConstants.ATTR_ACTIVITY_EVALUATION, activityEvaluation); + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("success", "true"); + response.setContentType("application/json;charset=utf-8"); + response.getWriter().print(new String(responseJSON.toString())); + return null; + } + + /** + * Refreshes user list. + */ + @RequestMapping("/getUsers") + public String getUsers(HttpServletRequest request, HttpServletResponse res) throws IOException, ServletException { + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Assessment assessment = (Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT); + + Long sessionId = WebUtil.readLongParam(request, "sessionId"); + + // Getting the params passed in from the jqGrid + int page = WebUtil.readIntParam(request, GradebookConstants.PARAM_PAGE); + int rowLimit = WebUtil.readIntParam(request, GradebookConstants.PARAM_ROWS); + String sortOrder = WebUtil.readStrParam(request, GradebookConstants.PARAM_SORD); + String sortBy = WebUtil.readStrParam(request, GradebookConstants.PARAM_SIDX, true); + if (StringUtils.isEmpty(sortBy)) { + sortBy = "userName"; + } + String searchString = WebUtil.readStrParam(request, "userName", true); + + List userDtos = new ArrayList<>(); + int countSessionUsers = 0; + //in case of UseSelectLeaderToolOuput - display only one user + if (assessment.isUseSelectLeaderToolOuput()) { + + AssessmentSession session = service.getSessionBySessionId(sessionId); + AssessmentUser groupLeader = session.getGroupLeader(); + + if (groupLeader != null) { + + float assessmentResult = service.getLastTotalScoreByUser(assessment.getUid(), groupLeader.getUserId()); + Long portraitId = service.getPortraitId(groupLeader.getUserId()); + + AssessmentUserDTO userDto = new AssessmentUserDTO(); + userDto.setUserId(groupLeader.getUserId()); + userDto.setFirstName(groupLeader.getFirstName()); + userDto.setLastName(groupLeader.getLastName()); + userDto.setGrade(assessmentResult); + userDto.setPortraitId(portraitId); + userDtos.add(userDto); + countSessionUsers = 1; + } + + } else { + // Get the user list from the db + userDtos = service.getPagedUsersBySession(sessionId, page - 1, rowLimit, sortBy, sortOrder, searchString); + countSessionUsers = service.getCountUsersBySession(sessionId, searchString); + } + + int totalPages = new Double( + Math.ceil(new Integer(countSessionUsers).doubleValue() / new Integer(rowLimit).doubleValue())) + .intValue(); + + ArrayNode rows = JsonNodeFactory.instance.arrayNode(); + int i = 1; + for (AssessmentUserDTO userDto : userDtos) { + + ArrayNode userData = JsonNodeFactory.instance.arrayNode(); + userData.add(userDto.getUserId()); + userData.add(sessionId); + String fullName = HtmlUtils.htmlEscape(userDto.getFirstName() + " " + userDto.getLastName()); + userData.add(fullName); + userData.add(userDto.getGrade()); + if (userDto.getPortraitId() != null) { + userData.add(userDto.getPortraitId()); + } + + ObjectNode userRow = JsonNodeFactory.instance.objectNode(); + userRow.put("id", i++); + userRow.set("cell", userData); + + rows.add(userRow); + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("total", totalPages); + responseJSON.put("page", page); + responseJSON.put("records", countSessionUsers); + responseJSON.set("rows", rows); + + res.setContentType("application/json;charset=utf-8"); + res.getWriter().print(new String(responseJSON.toString())); + return null; + } + + /** + * Refreshes user list. + */ + @RequestMapping("/getUsersByQuestion") + public String getUsersByQuestion(HttpServletRequest request, HttpServletResponse res) + throws IOException, ServletException { + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + Assessment assessment = (Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT); + + Long sessionId = WebUtil.readLongParam(request, "sessionId"); + Long questionUid = WebUtil.readLongParam(request, "questionUid"); + + // Getting the params passed in from the jqGrid + int page = WebUtil.readIntParam(request, GradebookConstants.PARAM_PAGE); + int rowLimit = WebUtil.readIntParam(request, GradebookConstants.PARAM_ROWS); + String sortOrder = WebUtil.readStrParam(request, GradebookConstants.PARAM_SORD); + String sortBy = WebUtil.readStrParam(request, GradebookConstants.PARAM_SIDX, true); + if (StringUtils.isEmpty(sortBy)) { + sortBy = "userName"; + } + String searchString = WebUtil.readStrParam(request, "userName", true); + + List userDtos = new ArrayList<>(); + int countSessionUsers = 0; + //in case of UseSelectLeaderToolOuput - display only one user + if (assessment.isUseSelectLeaderToolOuput()) { + + AssessmentSession session = service.getSessionBySessionId(sessionId); + AssessmentUser groupLeader = session.getGroupLeader(); + + if (groupLeader != null) { + + AssessmentResult assessmentResult = service.getLastFinishedAssessmentResult(assessment.getUid(), + groupLeader.getUserId()); + Long questionResultUid = null; + if (assessmentResult != null) { + for (AssessmentQuestionResult dbQuestionResult : assessmentResult.getQuestionResults()) { + if (dbQuestionResult.getAssessmentQuestion().getUid().equals(questionUid)) { + questionResultUid = dbQuestionResult.getUid(); + break; + } + } + } + + AssessmentUserDTO userDto = new AssessmentUserDTO(); + userDto.setQuestionResultUid(questionResultUid); + userDto.setFirstName(groupLeader.getFirstName()); + userDto.setLastName(groupLeader.getLastName()); + userDtos.add(userDto); + countSessionUsers = 1; + } + + } else { + // Get the user list from the db + userDtos = service.getPagedUsersBySessionAndQuestion(sessionId, questionUid, page - 1, rowLimit, sortBy, + sortOrder, searchString); + countSessionUsers = service.getCountUsersBySession(sessionId, searchString); + } + + int totalPages = new Double( + Math.ceil(new Integer(countSessionUsers).doubleValue() / new Integer(rowLimit).doubleValue())) + .intValue(); + + ArrayNode rows = JsonNodeFactory.instance.arrayNode(); + int i = 1; + for (AssessmentUserDTO userDto : userDtos) { + + Long questionResultUid = userDto.getQuestionResultUid(); + String fullName = HtmlUtils.htmlEscape(userDto.getFirstName() + " " + userDto.getLastName()); + + ArrayNode userData = JsonNodeFactory.instance.arrayNode(); + if (questionResultUid != null) { + AssessmentQuestionResult questionResult = service.getAssessmentQuestionResultByUid(questionResultUid); + + userData.add(questionResultUid); + userData.add(questionResult.getMaxMark()); + userData.add(fullName); + //LDEV_NTU-11 Swapping Mark and Response columns in Assessment Monitor + userData.add(questionResult.getMark()); + // show confidence levels if this feature is turned ON + if (assessment.isEnableConfidenceLevels()) { + userData.add(questionResult.getConfidenceLevel()); + } + + userData.add(AssessmentEscapeUtils.printResponsesForJqgrid(questionResult)); + if (userDto.getPortraitId() != null) { + userData.add(userDto.getPortraitId()); + } + + } else { + userData.add(""); + userData.add(""); + userData.add(fullName); + userData.add("-"); + if (assessment.isEnableConfidenceLevels()) { + userData.add(-1); + } + userData.add("-"); + } + + ObjectNode userRow = JsonNodeFactory.instance.objectNode(); + userRow.put("id", i++); + userRow.set("cell", userData); + + rows.add(userRow); + } + + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + responseJSON.put("total", totalPages); + responseJSON.put("page", page); + responseJSON.put("records", countSessionUsers); + responseJSON.set("rows", rows); + + res.setContentType("application/json;charset=utf-8"); + res.getWriter().print(new String(responseJSON.toString())); + return null; + } + + /** + * Get the mark summary with data arranged in bands. Can be displayed graphically or in a table. + */ + @RequestMapping("/getMarkChartData") + public String getMarkChartData(HttpServletRequest request, HttpServletResponse res) + throws IOException, ServletException { + + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + + Long contentId = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_CONTENT_ID); + Assessment assessment = service.getAssessmentByContentId(contentId); + List results = null; + + if (assessment != null) { + if (assessment.isUseSelectLeaderToolOuput()) { + results = service.getMarksArrayForLeaders(contentId); + } else { + Long sessionId = WebUtil.readLongParam(request, AssessmentConstants.ATTR_TOOL_SESSION_ID); + results = service.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])); + } + + res.setContentType("application/json;charset=utf-8"); + res.getWriter().write(responseJSON.toString()); + return null; + + } + + /** + * Excel Summary Export. + */ + @RequestMapping("/exportSummary") + public String exportSummary(HttpServletRequest request, HttpServletResponse response) throws IOException { + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + String fileName = null; + boolean showUserNames = true; + + Long contentId = null; + List sessionDtos; + if (sessionMapID != null) { + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + contentId = (Long) sessionMap.get(AssessmentConstants.ATTR_TOOL_CONTENT_ID); + showUserNames = true; + sessionDtos = (List) sessionMap.get("sessionDtos"); + + } else { + contentId = WebUtil.readLongParam(request, "toolContentID"); + fileName = WebUtil.readStrParam(request, "fileName"); + showUserNames = false; + sessionDtos = service.getSessionDtos(contentId, true); + } + + Assessment assessment = service.getAssessmentByContentId(contentId); + if (assessment == null) { + return null; + } + + LinkedHashMap dataToExport = service.exportSummary(assessment, sessionDtos, + showUserNames); + + // Setting the filename if it wasn't passed in the request + if (fileName == null) { + fileName = "assessment_" + assessment.getUid() + "_export.xlsx"; + } + + response.setContentType("application/x-download"); + response.setHeader("Content-Disposition", "attachment;filename=" + fileName); + log.debug("Exporting assessment to a spreadsheet: " + assessment.getContentId()); + + // 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); + + ServletOutputStream out = response.getOutputStream(); + ExcelUtil.createExcel(out, dataToExport, service.getMessage("label.export.exported.on"), true); + + return null; + } + + @RequestMapping("/statistic") + public String statistic(HttpServletRequest request, HttpServletResponse response) { + String sessionMapID = request.getParameter(AssessmentConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession() + .getAttribute(sessionMapID); + request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); + + Long contentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + Assessment assessment = service.getAssessmentByContentId(contentId); + if (assessment != null) { + if (assessment.isUseSelectLeaderToolOuput()) { + LeaderResultsDTO leaderDto = service.getLeaderResultsDTOForLeaders(contentId); + sessionMap.put("leaderDto", leaderDto); + } else { + List sessionDtos = service.getSessionDtos(contentId, true); + sessionMap.put("sessionDtos", sessionDtos); + } + } + return "pages/monitoring/statisticpart"; + } + + /** + * Allows displaying correct answers to learners + */ + @RequestMapping("/discloseCorrectAnswers") + public String discloseCorrectAnswers(HttpServletRequest request, HttpServletResponse response) { + Long questionUid = WebUtil.readLongParam(request, "questionUid"); + Long toolContentId = WebUtil.readLongParam(request, AssessmentConstants.PARAM_TOOL_CONTENT_ID); + + AssessmentQuestion question = service.getAssessmentQuestionByUid(questionUid); + question.setCorrectAnswersDisclosed(true); + service.updateAssessmentQuestion(question); + + service.notifyLearnersOnAnswerDisclose(toolContentId); + + if (log.isDebugEnabled()) { + log.debug("Disclosed correct answers for Assessment tool content ID " + toolContentId + " and question ID " + + questionUid); + } + + return null; + } + + /** + * Allows displaying other groups' answers to learners + */ + @RequestMapping("/discloseGroupsAnswers") + public String discloseGroupsAnswers(HttpServletRequest request, HttpServletResponse response) { + Long questionUid = WebUtil.readLongParam(request, "questionUid"); + Long toolContentId = WebUtil.readLongParam(request, AssessmentConstants.PARAM_TOOL_CONTENT_ID); + + AssessmentQuestion question = service.getAssessmentQuestionByUid(questionUid); + question.setGroupsAnswersDisclosed(true); + service.updateAssessmentQuestion(question); + + service.notifyLearnersOnAnswerDisclose(toolContentId); + + if (log.isDebugEnabled()) { + log.debug("Disclosed other groups' answers for Assessment tool content ID " + toolContentId + + " and question ID " + questionUid); + } + + return null; + } + +} Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/TblMonitoringController.java =================================================================== diff -u --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/TblMonitoringController.java (revision 0) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/TblMonitoringController.java (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -0,0 +1,343 @@ +package org.lamsfoundation.lams.tool.assessment.web.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; +import org.lamsfoundation.lams.tool.assessment.dto.AssessmentResultDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionSummary; +import org.lamsfoundation.lams.tool.assessment.dto.TblAssessmentDTO; +import org.lamsfoundation.lams.tool.assessment.dto.TblAssessmentQuestionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.TblAssessmentQuestionResultDTO; +import org.lamsfoundation.lams.tool.assessment.model.Assessment; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionOption; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentSession; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; +import org.lamsfoundation.lams.tool.assessment.service.IAssessmentService; +import org.lamsfoundation.lams.tool.assessment.util.AssessmentEscapeUtils; +import org.lamsfoundation.lams.tool.assessment.util.AssessmentSessionComparator; +import org.lamsfoundation.lams.tool.assessment.util.SequencableComparator; +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.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/tblmonitoring") +public class TblMonitoringController { + private static Logger logger = Logger.getLogger(TblMonitoringController.class.getName()); + + @Autowired + @Qualifier("laasseAssessmentService") + private IAssessmentService assessmentService; + + /** + * Shows ira page in case of Assessment activity + */ + @RequestMapping("iraAssessment") + public String iraAssessment(HttpServletRequest request) throws IOException, ServletException { + Long toolContentId = WebUtil.readLongParam(request, "toolContentID"); + + String[] toolContentIds = new String[] { toolContentId.toString() }; + String[] activityTitles = new String[] { "" }; + List assessmentDtos = getAssessmentDtos(toolContentIds, activityTitles); + request.setAttribute("assessmentDtos", assessmentDtos); + + request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); + request.setAttribute("isIraAssessment", true); + return "pages/tblmonitoring/assessment"; + } + + /** + * Shows ira page in case of Assessment activity + */ + @RequestMapping("iraAssessmentStudentChoices") + public String iraAssessmentStudentChoices(HttpServletRequest request) throws IOException, ServletException { + Long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + + //prepare list of the questions, filtering out questions that aren't supposed to be answered + Set questionList = new TreeSet(); + //in case there is at least one random question - we need to show all questions in a drop down select + if (assessment.hasRandomQuestion()) { + questionList.addAll(assessment.getQuestions()); + + //show only questions from question list otherwise + } else { + for (QuestionReference reference : (Set) assessment.getQuestionReferences()) { + questionList.add(reference.getQuestion()); + } + } + //keep only MCQ type of questions + Set mcqQuestions = new TreeSet(); + int maxOptionsInQuestion = 0; + for (AssessmentQuestion question : questionList) { + if (AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE == question.getType()) { + mcqQuestions.add(question); + + //calculate maxOptionsInQuestion + if (question.getOptions().size() > maxOptionsInQuestion) { + maxOptionsInQuestion = question.getOptions().size(); + } + } + } + request.setAttribute("maxOptionsInQuestion", maxOptionsInQuestion); + + int totalNumberOfUsers = assessmentService.getCountUsersByContentId(toolContentId); + for (AssessmentQuestion question : mcqQuestions) { + + // build candidate dtos + for (AssessmentQuestionOption option : question.getOptions()) { + int optionAttemptCount = assessmentService.countAttemptsPerOption(option.getUid()); + + float percentage = (float) (optionAttemptCount * 100) / totalNumberOfUsers; + option.setPercentage(percentage); + } + } + request.setAttribute("questions", mcqQuestions); + + request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); + return "pages/tblmonitoring/iraAssessmentStudentChoices"; + } + + private List getAssessmentDtos(String[] toolContentIds, String[] activityTitles) { + List assessmentDtos = new ArrayList(); + int i = 0; + for (String toolContentIdStr : toolContentIds) { + String activityTitle = activityTitles[i++]; + + //skip empty contentIds + if (toolContentIdStr.length() == 0) { + continue; + } + Long toolContentId = Long.parseLong(toolContentIdStr); + + TblAssessmentDTO assessmentDto = new TblAssessmentDTO(); + + int attemptedLearnersNumber = assessmentService.getCountUsersByContentId(toolContentId); + assessmentDto.setAttemptedLearnersNumber(attemptedLearnersNumber); + assessmentDto.setActivityTitle(activityTitle); + + Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + assessmentDto.setAssessment(assessment); + assessmentDto.setQuestions(prepareQuestionsList(assessment)); + assessmentDtos.add(assessmentDto); + } + + return assessmentDtos; + } + + private Set prepareQuestionsList(Assessment assessment) { + // question list to display + Set questions = new TreeSet(); + boolean hasRandomQuestion = false; + for (QuestionReference reference : (Set) assessment.getQuestionReferences()) { + hasRandomQuestion |= reference.isRandomQuestion(); + } + // in case there is at least one random question - we need to show all questions + if (hasRandomQuestion) { + questions.addAll(assessment.getQuestions()); + + // show only questions from question list otherwise + } else { + for (QuestionReference reference : (Set) assessment.getQuestionReferences()) { + //sort questions the same way references are sorted (as per LKC request) + AssessmentQuestion question = reference.getQuestion(); + question.setSequenceId(reference.getSequenceId()); + questions.add(question); + } + } + return questions; + } + + /** + * Shows aes page + */ + @RequestMapping("aes") + public String aes(HttpServletRequest request) throws IOException, ServletException { + String[] toolContentIds = request.getParameter("assessmentToolContentIds").split(","); + String[] activityTitles = request.getParameter("assessmentActivityTitles").split("\\,"); + + List assessmentDtos = getAssessmentDtos(toolContentIds, activityTitles); + request.setAttribute("assessmentDtos", assessmentDtos); + + Long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID, true); + request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); + + return "pages/tblmonitoring/assessment"; + } + + /** + * Shows ira StudentChoices page + */ + @RequestMapping("aesStudentChoices") + public String aesStudentChoices(HttpServletRequest request) throws IOException, ServletException { + + Long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + Map questionSummaries = assessmentService.getQuestionSummaryForExport(assessment); + List tblQuestionDtos = new ArrayList(); + for (QuestionSummary questionSummary : questionSummaries.values()) { + AssessmentQuestion question = questionSummary.getQuestion(); + + TblAssessmentQuestionDTO tblQuestionDto = new TblAssessmentQuestionDTO(); + tblQuestionDto.setQuestion(question); + tblQuestionDto.setQuestionTypeLabel(TblMonitoringController.getAssessmentQuestionTypeLabel(question.getType())); + tblQuestionDto.setCorrectAnswer(getAssessmentCorrectAnswer(question)); + + List sessionQuestionResults = new ArrayList(); + for (List questionResultsPerSession : questionSummary + .getQuestionResultsPerSession()) { + + TblAssessmentQuestionResultDTO tblQuestionResultDto = new TblAssessmentQuestionResultDTO(); + String answer = ""; + boolean correct = false; + if (!questionResultsPerSession.isEmpty()) { + AssessmentQuestionResult questionResult = questionResultsPerSession.get(0); + answer = AssessmentEscapeUtils.printResponsesForJqgrid(questionResult); + correct = questionResult.getMaxMark() == null ? false + : (questionResult.getPenalty() + questionResult.getMark() + 0.1) >= questionResult + .getMaxMark(); + } + tblQuestionResultDto.setAnswer(answer); + tblQuestionResultDto.setCorrect(correct); + + sessionQuestionResults.add(tblQuestionResultDto); + } + tblQuestionDto.setSessionQuestionResults(sessionQuestionResults); + + tblQuestionDtos.add(tblQuestionDto); + } + + SortedSet sessions = new TreeSet(new AssessmentSessionComparator()); + sessions.addAll(assessmentService.getSessionsByContentId(assessment.getContentId())); + + request.setAttribute("sessions", sessions); + request.setAttribute("questionDtos", tblQuestionDtos); + request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); + return "pages/tblmonitoring/assessmentStudentChoices"; + } + + /** + * Used only for excell export (for getUserSummaryData() method). + */ + private static String getAssessmentQuestionTypeLabel(short type) { + switch (type) { + case AssessmentConstants.QUESTION_TYPE_ESSAY: + return "Essay"; + case AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS: + return "Matching Pairs"; + case AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE: + return "Multiple Choice"; + case AssessmentConstants.QUESTION_TYPE_NUMERICAL: + return "Numerical"; + case AssessmentConstants.QUESTION_TYPE_ORDERING: + return "Ordering"; + case AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER: + return "Short Answer"; + case AssessmentConstants.QUESTION_TYPE_TRUE_FALSE: + return "True/False"; + case AssessmentConstants.QUESTION_TYPE_MARK_HEDGING: + return "Mark Hedging"; + default: + return null; + } + } + + /** + * Used only for excell export (for getUserSummaryData() method). + */ + private String getAssessmentCorrectAnswer(AssessmentQuestion question) { + StringBuilder sb = new StringBuilder(); + + if (question != null) { + switch (question.getType()) { + case AssessmentConstants.QUESTION_TYPE_ESSAY: + return "N.A."; + + case AssessmentConstants.QUESTION_TYPE_MATCHING_PAIRS: + for (AssessmentQuestionOption option : question.getOptions()) { + sb.append((option.getQuestion() + " - " + option.getOptionString()).replaceAll("\\<.*?\\>", "") + + "
"); + } + return sb.toString(); + + case AssessmentConstants.QUESTION_TYPE_MULTIPLE_CHOICE: + case AssessmentConstants.QUESTION_TYPE_SHORT_ANSWER: + for (AssessmentQuestionOption option : question.getOptions()) { + if (option.getGrade() == 1f) { + return option.getOptionString(); + } + } + break; + + case AssessmentConstants.QUESTION_TYPE_NUMERICAL: + for (AssessmentQuestionOption option : question.getOptions()) { + if (option.getGrade() == 1f) { + return "" + option.getOptionFloat(); + } + } + break; + + case AssessmentConstants.QUESTION_TYPE_ORDERING: + TreeSet correctOptionSet = new TreeSet( + new SequencableComparator()); + correctOptionSet.addAll(question.getOptions()); + + for (AssessmentQuestionOption option : question.getOptions()) { + sb.append(option.getOptionString() + "\n"); + } + return sb.toString(); + + case AssessmentConstants.QUESTION_TYPE_TRUE_FALSE: + return new Boolean(question.getCorrectAnswer()).toString(); + + case AssessmentConstants.QUESTION_TYPE_MARK_HEDGING: + for (AssessmentQuestionOption option : question.getOptions()) { + if (option.isCorrect()) { + return option.getOptionString(); + } + } + break; + + default: + return null; + } + } + + return null; + } + + /** + * Get ModalDialog for Teams tab. + * + * @throws JSONException + * @throws IOException + */ + @RequestMapping("getModalDialogForTeamsTab") + public String getModalDialogForTeamsTab(HttpServletRequest request) throws IOException { + long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + Long userId = WebUtil.readLongParam(request, AttributeNames.PARAM_USER_ID); + + AssessmentUser user = assessmentService.getUserByIdAndContent(userId, toolContentId); + AssessmentResultDTO result = assessmentService.getUserMasterDetail(user.getSession().getSessionId(), userId); + request.setAttribute(AssessmentConstants.ATTR_ASSESSMENT_RESULT, result); + + return "pages/tblmonitoring/teams"; + } + +} Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/AssessmentForm.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/AssessmentForm.java (.../AssessmentForm.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/AssessmentForm.java (.../AssessmentForm.java) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -25,32 +25,22 @@ import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; -import org.apache.struts.action.ActionForm; -import org.apache.struts.action.ActionMapping; -import org.apache.struts.upload.FormFile; import org.lamsfoundation.lams.tool.assessment.model.Assessment; /** - * * Assessment Form. * - * - * * @author Andrey Balan */ -public class AssessmentForm extends ActionForm { - private static final long serialVersionUID = 3599879328307492312L; +public class AssessmentForm { private static Logger logger = Logger.getLogger(AssessmentForm.class.getName()); // Forum fields private String sessionMapID; private String contentFolderID; private int currentTab; - private FormFile offlineFile; - private FormFile onlineFile; private Assessment assessment; @@ -68,24 +58,18 @@ } } - @Override - public void reset(ActionMapping mapping, HttpServletRequest request) { - String param = mapping.getParameter(); - // if it is start page, all data read out from database or current session - // so need not reset checkbox to refresh value! - if (!StringUtils.equals(param, "start") && !StringUtils.equals(param, "initPage")) { - assessment.setAllowGradesAfterAttempt(false); - assessment.setAllowOverallFeedbackAfterQuestion(false); - assessment.setAllowQuestionFeedback(false); - assessment.setAllowDiscloseAnswers(false); - assessment.setAllowRightAnswersAfterQuestion(false); - assessment.setAllowWrongAnswersAfterQuestion(false); - assessment.setDefineLater(false); - assessment.setShuffled(false); - assessment.setNumbered(false); - assessment.setDisplaySummary(false); - assessment.setReflectOnActivity(false); - } + public void reset(HttpServletRequest request) { + assessment.setAllowGradesAfterAttempt(false); + assessment.setAllowOverallFeedbackAfterQuestion(false); + assessment.setAllowQuestionFeedback(false); + assessment.setAllowDiscloseAnswers(false); + assessment.setAllowRightAnswersAfterQuestion(false); + assessment.setAllowWrongAnswersAfterQuestion(false); + assessment.setDefineLater(false); + assessment.setShuffled(false); + assessment.setNumbered(false); + assessment.setDisplaySummary(false); + assessment.setReflectOnActivity(false); } public int getCurrentTab() { @@ -96,22 +80,6 @@ this.currentTab = currentTab; } - public FormFile getOfflineFile() { - return offlineFile; - } - - public void setOfflineFile(FormFile offlineFile) { - this.offlineFile = offlineFile; - } - - public FormFile getOnlineFile() { - return onlineFile; - } - - public void setOnlineFile(FormFile onlineFile) { - this.onlineFile = onlineFile; - } - public Assessment getAssessment() { return assessment; } Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/AssessmentQuestionForm.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/AssessmentQuestionForm.java (.../AssessmentQuestionForm.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/AssessmentQuestionForm.java (.../AssessmentQuestionForm.java) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -23,16 +23,13 @@ package org.lamsfoundation.lams.tool.assessment.web.form; -import org.apache.struts.action.ActionForm; - /** * Assessment Question Form. * * * @author Andrey Balan */ -public class AssessmentQuestionForm extends ActionForm { - private static final long serialVersionUID = 4900738305713649389L; +public class AssessmentQuestionForm { private String questionIndex; private String sessionMapID; Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/ReflectionForm.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/ReflectionForm.java (.../ReflectionForm.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/form/ReflectionForm.java (.../ReflectionForm.java) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -24,21 +24,12 @@ package org.lamsfoundation.lams.tool.assessment.web.form; -import org.apache.log4j.Logger; -import org.apache.struts.validator.ValidatorForm; - /** - * * Reflection Form. * - * - * * @author Andrey Balan - * */ -public class ReflectionForm extends ValidatorForm { - private static final long serialVersionUID = -9054365604649146735L; - private static Logger logger = Logger.getLogger(ReflectionForm.class.getName()); +public class ReflectionForm { private Integer userID; private String sessionMapID; Index: lams_tool_assessment/web/WEB-INF/spring-servlet.xml =================================================================== diff -u --- lams_tool_assessment/web/WEB-INF/spring-servlet.xml (revision 0) +++ lams_tool_assessment/web/WEB-INF/spring-servlet.xml (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/struts-config.xml'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_tool_assessment/web/WEB-INF/tags/AuthoringButton.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/AuthoringButton.tag (.../AuthoringButton.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/AuthoringButton.tag (.../AuthoringButton.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -32,7 +32,6 @@ <%@ tag body-content="scriptless" %> <%@ taglib uri="tags-core" prefix="c" %> <%@ taglib uri="tags-fmt" prefix="fmt" %> -<%@ taglib uri="tags-html" prefix="html" %> <%@ taglib uri="tags-lams" prefix="lams"%> <%@ attribute name="formID" required="true" rtexprvalue="true" %> @@ -123,11 +122,11 @@ } \ No newline at end of file Index: lams_tool_assessment/web/WEB-INF/tags/AuthoringRatingAllStyleCriteria.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/AuthoringRatingAllStyleCriteria.tag (.../AuthoringRatingAllStyleCriteria.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/AuthoringRatingAllStyleCriteria.tag (.../AuthoringRatingAllStyleCriteria.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -8,7 +8,6 @@ <%@ tag body-content="scriptless" %> <%@ taglib uri="tags-core" prefix="c" %> <%@ taglib uri="tags-fmt" prefix="fmt" %> -<%@ taglib uri="tags-html" prefix="html" %> <%@ taglib uri="tags-lams" prefix="lams"%> <%@ taglib uri="tags-function" prefix="fn" %> Index: lams_tool_assessment/web/WEB-INF/tags/AuthoringRatingCriteria.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/AuthoringRatingCriteria.tag (.../AuthoringRatingCriteria.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/AuthoringRatingCriteria.tag (.../AuthoringRatingCriteria.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -8,7 +8,6 @@ <%@ tag body-content="scriptless" %> <%@ taglib uri="tags-core" prefix="c" %> <%@ taglib uri="tags-fmt" prefix="fmt" %> -<%@ taglib uri="tags-html" prefix="html" %> <%@ taglib uri="tags-lams" prefix="lams"%> <%@ taglib uri="tags-function" prefix="fn" %> @@ -284,42 +283,42 @@ - - + - - + Index: lams_tool_assessment/web/WEB-INF/tags/CommentsAuthor.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/CommentsAuthor.tag (.../CommentsAuthor.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/CommentsAuthor.tag (.../CommentsAuthor.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -1,8 +1,7 @@ <%@ taglib uri="tags-core" prefix="c"%> -<%@ taglib uri="tags-html" prefix="html"%> <%@ taglib uri="tags-fmt" prefix="fmt"%> <%@ taglib uri="tags-lams" prefix="lams"%> - +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%@ attribute name="allowCommentsVariableName" required="false" rtexprvalue="true"%> <%@ attribute name="allowCommentLabelKey" required="false" rtexprvalue="true"%> <%@ attribute name="likeDislikeVariableName" required="false" rtexprvalue="true"%> @@ -42,23 +41,23 @@
-     -   +     +  
Index: lams_tool_assessment/web/WEB-INF/tags/Page.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/Page.tag (.../Page.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/Page.tag (.../Page.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -4,6 +4,7 @@ <%@ taglib uri="tags-lams" prefix="lams"%> <%@ attribute name="type" required="true" rtexprvalue="true"%> +<%@ attribute name="formID" required="false" rtexprvalue="true"%> <%@ attribute name="style" required="false" rtexprvalue="true"%> <%@ attribute name="title" required="false" rtexprvalue="true"%> <%@ attribute name="titleHelpURL" required="false" rtexprvalue="true"%> @@ -88,8 +89,8 @@ - - + + ${toolForm.toolSessionID} @@ -137,12 +138,12 @@ function restartLesson(){ if (confirm(restartLessonConfirmation)) { - window.location.href = LEARNING_URL + 'learner.do?method=restartLesson&lessonID=' + lessonId; + window.location.href = LEARNING_URL + 'learner/restartLesson.do?lessonID=' + lessonId; } } function viewNotebookEntries(){ - openPopUp(LEARNING_URL + "notebook.do?method=viewAll&lessonID=" + lessonId, + openPopUp(LEARNING_URL + "notebook/viewAll.do?lessonID=" + lessonId, "Notebook", 648,1152, "no"); @@ -244,9 +245,8 @@ if ( lessonId != "" || toolSessionId != "" ) { $.ajax({ - url : LEARNING_URL + 'learner.do', + url : LEARNING_URL + 'learner/getLessonDetails.do', data : { - 'method' : 'getLessonDetails', 'lessonID' : lessonId, 'toolSessionID' : toolSessionId, }, @@ -434,4 +434,4 @@
- + \ No newline at end of file Index: lams_tool_assessment/web/WEB-INF/tags/Rating.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/Rating.tag (.../Rating.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/Rating.tag (.../Rating.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -8,7 +8,6 @@ <%@ tag body-content="scriptless" %> <%@ taglib uri="tags-core" prefix="c" %> <%@ taglib uri="tags-fmt" prefix="fmt" %> -<%@ taglib uri="tags-html" prefix="html" %> <%@ taglib uri="tags-lams" prefix="lams"%> <%@ taglib uri="tags-function" prefix="fn" %> Index: lams_tool_assessment/web/WEB-INF/tags/StyledRating.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/StyledRating.tag (.../StyledRating.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/StyledRating.tag (.../StyledRating.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -8,7 +8,6 @@ <%@ tag body-content="scriptless" %> <%@ taglib uri="tags-core" prefix="c" %> <%@ taglib uri="tags-fmt" prefix="fmt" %> -<%@ taglib uri="tags-html" prefix="html" %> <%@ taglib uri="tags-lams" prefix="lams"%> <%@ taglib uri="tags-function" prefix="fn" %> Index: lams_tool_assessment/web/WEB-INF/tags/TabBody.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/TabBody.tag (.../TabBody.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/TabBody.tag (.../TabBody.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -34,7 +34,6 @@ <%@ attribute name="titleKey" required="false" rtexprvalue="true"%> <%@ attribute name="page" required="false" rtexprvalue="true"%> <%@ taglib uri="tags-core" prefix="c"%> -<%@ taglib uri="tags-bean" prefix="bean"%> Index: lams_tool_assessment/web/WEB-INF/tags/TabBodyArea.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/TabBodyArea.tag (.../TabBodyArea.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/TabBodyArea.tag (.../TabBodyArea.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -30,7 +30,6 @@ %> <%@ taglib uri="tags-core" prefix="c"%> -<%@ taglib uri="tags-bean" prefix="bean"%>
Index: lams_tool_assessment/web/WEB-INF/tags/TextSearch.tag =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tags/TextSearch.tag (.../TextSearch.tag) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tags/TextSearch.tag (.../TextSearch.tag) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -32,12 +32,11 @@ <%@ tag body-content="scriptless" %> <%@ taglib uri="tags-core" prefix="c" %> <%@ taglib uri="tags-fmt" prefix="fmt" %> -<%@ taglib uri="tags-html" prefix="html" %> +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%@ taglib uri="tags-lams" prefix="lams" %> <%-- Required attributes --%> <%@ attribute name="sessionMapID" required="true" rtexprvalue="true" %> -<%@ attribute name="wrapInFormTag" required="true" rtexprvalue="true" %> <%-- Optional attributes --%> <%@ attribute name="action" required="false" rtexprvalue="true" %> @@ -53,10 +52,10 @@ <%-- Default value for message key --%> - + - + @@ -85,52 +84,40 @@ - -
- - -

- - - - - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
- - - -
- - - - - - - - - + +

+ + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
\ No newline at end of file Index: lams_tool_assessment/web/WEB-INF/tlds/jstl/c.tld =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tlds/jstl/c.tld (.../c.tld) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tlds/jstl/c.tld (.../c.tld) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -101,7 +101,7 @@ JSP -The URL of the assessment to import. +The URL of the survey to import. url true @@ -110,7 +110,7 @@ Name of the exported scoped variable for the -assessment's content. The type of the scoped +survey's content. The type of the scoped variable is String. var @@ -128,7 +128,7 @@ Name of the exported scoped variable for the -assessment's content. The type of the scoped +survey's content. The type of the scoped variable is Reader. varReader @@ -138,7 +138,7 @@ Name of the context when accessing a relative -URL assessment that belongs to a foreign +URL survey that belongs to a foreign context. context @@ -148,7 +148,7 @@ Character encoding of the content at the input -assessment. +survey. charEncoding false @@ -401,7 +401,7 @@ JSP -The URL of the assessment to redirect to. +The URL of the survey to redirect to. url false @@ -410,7 +410,7 @@ Name of the context when redirecting to a relative URL -assessment that belongs to a foreign context. +survey that belongs to a foreign context. context false @@ -532,7 +532,7 @@ Name of the context when specifying a relative URL -assessment that belongs to a foreign context. +survey that belongs to a foreign context. context false Index: lams_tool_assessment/web/WEB-INF/tlds/jstl/fmt.tld =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tlds/jstl/fmt.tld (.../fmt.tld) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tlds/jstl/fmt.tld (.../fmt.tld) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -149,15 +149,15 @@ - Loads a assessment bundle to be used by its tag body + Loads a survey bundle to be used by its tag body bundle org.apache.taglibs.standard.tag.rt.fmt.BundleTag JSP -Assessment bundle base name. This is the bundle's -fully-qualified assessment name, which has the same +Survey bundle base name. This is the bundle's +fully-qualified survey name, which has the same form as a fully-qualified class name, that is, it uses "." as the package component separator and does not have any file type (such as ".class" or ".properties") @@ -180,16 +180,16 @@ - Loads a assessment bundle and stores it in the named scoped variable or + Loads a survey bundle and stores it in the named scoped variable or the bundle configuration variable setBundle org.apache.taglibs.standard.tag.rt.fmt.SetBundleTag empty -Assessment bundle base name. This is the bundle's -fully-qualified assessment name, which has the same +Survey bundle base name. This is the bundle's +fully-qualified survey name, which has the same form as a fully-qualified class name, that is, it uses "." as the package component separator and does not have any file type (such as ".class" or ".properties") @@ -238,7 +238,7 @@ -Localization context in whose assessment +Localization context in whose survey bundle the message key is looked up. bundle Index: lams_tool_assessment/web/WEB-INF/tlds/lams/lams.tld =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/tlds/lams/lams.tld (.../lams.tld) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/tlds/lams/lams.tld (.../lams.tld) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -212,192 +212,6 @@ - - STRUTS-textarea - org.lamsfoundation.lams.web.tag.MultiLinesTextareaTag - empty - - accesskey - false - true - - - alt - false - true - - - altKey - false - true - - - bundle - false - true - - - cols - false - true - - - disabled - false - true - - - errorKey - false - true - - - errorStyle - false - true - - - errorStyleClass - false - true - - - errorStyleId - false - true - - - index - false - true - - - indexed - false - true - - - name - false - true - - - onblur - false - true - - - onchange - false - true - - - onclick - false - true - - - ondblclick - false - true - - - onfocus - false - true - - - onkeydown - false - true - - - onkeypress - false - true - - - onkeyup - false - true - - - onmousedown - false - true - - - onmousemove - false - true - - - onmouseout - false - true - - - onmouseover - false - true - - - onmouseup - false - true - - - property - true - true - - - readonly - false - true - - - rows - false - true - - - style - false - true - - - styleClass - false - true - - - styleId - false - true - - - tabindex - false - true - - - title - false - true - - - titleKey - false - true - - - value - false - true - - - Small portrait of a user User Portrait Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-bean-el.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-bean.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-html-el.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-html.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-logic-el.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-logic.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-nested.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-tiles-el.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/tlds/struts/struts-tiles.tld'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 4353f26f9509808acdf41d0deef6cf5fd458a2b7 refers to a dead (removed) revision in file `lams_tool_assessment/web/WEB-INF/validation.xml'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_tool_assessment/web/WEB-INF/web.xml =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/WEB-INF/web.xml (.../web.xml) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/WEB-INF/web.xml (.../web.xml) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -73,25 +73,11 @@ - action - org.apache.struts.action.ActionServlet - - config - /WEB-INF/struts-config.xml - - - debug - 999 - - - detail - 1 - - - validate - true - - 2 + spring + + org.springframework.web.servlet.DispatcherServlet + + 1 @@ -122,7 +108,7 @@ - action + spring *.do @@ -138,42 +124,6 @@ - - - - tags-bean - /WEB-INF/tlds/struts/struts-bean.tld - - - tags-html - /WEB-INF/tlds/struts/struts-html.tld - - - tags-logic - /WEB-INF/tlds/struts/struts-logic.tld - - - tags-tiles - /WEB-INF/tlds/struts/struts-tiles.tld - - - - tags-bean-el - /WEB-INF/tlds/struts/struts-bean-el.tld - - - tags-html-el - /WEB-INF/tlds/struts/struts-html-el.tld - - - tags-logic-el - /WEB-INF/tlds/struts/struts-logic-el.tld - - - tags-tiles-el - /WEB-INF/tlds/struts/struts-tiles-el.tld - - Index: lams_tool_assessment/web/common/header.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/common/header.jsp (.../header.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/common/header.jsp (.../header.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -5,7 +5,7 @@ - + Index: lams_tool_assessment/web/common/messages.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/common/messages.jsp (.../messages.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/common/messages.jsp (.../messages.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -1,8 +1,9 @@ <%-- Error Messages --%> - - - -
-
-
-
\ No newline at end of file + + + + + + + + \ No newline at end of file Index: lams_tool_assessment/web/common/tabbedheader.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/common/tabbedheader.jsp (.../tabbedheader.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/common/tabbedheader.jsp (.../tabbedheader.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -5,7 +5,7 @@ - + Index: lams_tool_assessment/web/common/taglibs.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/common/taglibs.jsp (.../taglibs.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/common/taglibs.jsp (.../taglibs.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -1,11 +1,8 @@ <%@ page language="java" errorPage="/error.jsp" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %> -<%@ taglib uri="tags-bean" prefix="bean" %> -<%@ taglib uri="tags-html" prefix="html" %> -<%@ taglib uri="tags-logic" prefix="logic" %> <%@ taglib uri="tags-function" prefix="fn" %> <%@ taglib uri="tags-core" prefix="c" %> <%@ taglib uri="tags-fmt" prefix="fmt" %> -<%@ taglib uri="tags-xml" prefix="x" %> <%@ taglib uri="tags-lams" prefix="lams" %> +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> Index: lams_tool_assessment/web/pages/authoring/advance.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/pages/authoring/advance.jsp (.../advance.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/pages/authoring/advance.jsp (.../advance.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -1,5 +1,4 @@ <%@ include file="/common/taglibs.jsp"%> - - - - - - - - + + + + + @@ -76,9 +75,9 @@
+ contentFolderID="${assessmentForm.contentFolderID}" />
@@ -87,6 +86,6 @@
-
+ Index: lams_tool_assessment/web/pages/authoring/basic.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/pages/authoring/basic.jsp (.../basic.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/pages/authoring/basic.jsp (.../basic.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -1,6 +1,5 @@ <%@ include file="/common/taglibs.jsp"%> - - + @@ -116,7 +115,7 @@ tb_init('a.thickbox, area.thickbox, input.thickbox');//pass where to apply thickbox }; function reinitializePassingMarkSelect(isPageFresh){ - var oldValue = (isPageFresh) ? "${formBean.assessment.passingMark}" : $("#passingMark").val(); + var oldValue = (isPageFresh) ? "${assessmentForm.assessment.passingMark}" : $("#passingMark").val(); $('#passingMark').empty(); $('#passingMark').append( new Option("",0) ); @@ -162,20 +161,20 @@ - +
- +
- + <%@ include file="/pages/authoring/parts/questionlist.jsp"%>
Index: lams_tool_assessment/web/pages/authoring/parts/addessay.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/pages/authoring/parts/addessay.jsp (.../addessay.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/pages/authoring/parts/addessay.jsp (.../addessay.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -5,7 +5,7 @@ <%@ include file="/common/header.jsp"%> - + @@ -119,31 +119,30 @@ - - - - - + + + +
- +
- +
@@ -153,42 +152,42 @@ : " alt=""> - +
- +
- +
-
in form-group"> - +
in form-group"> +
- +
Index: lams_tool_assessment/web/pages/authoring/parts/addmarkhedging.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/pages/authoring/parts/addmarkhedging.jsp (.../addmarkhedging.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/pages/authoring/parts/addmarkhedging.jsp (.../addmarkhedging.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -5,7 +5,7 @@ <%@ include file="/common/header.jsp"%> - + - + - + - + - + - + - + @@ -87,31 +87,30 @@
- - - + + - +
- +
- +
@@ -121,19 +120,19 @@ : " alt=""> - + - +
-
in form-group"> - +
in form-group"> +
@@ -143,31 +142,31 @@ - - - - + + + +
-
in"> +
in">
- +
- +
- +
Index: lams_tool_assessment/web/pages/authoring/parts/gradeselector.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/pages/authoring/parts/gradeselector.jsp (.../gradeselector.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/pages/authoring/parts/gradeselector.jsp (.../gradeselector.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -1,43 +1,50 @@ - - 100 % - 90 % - 83.333 % - 80 % - 75% - 70 % - 66.666 % - 60 % - 50 % - 40 % - 33.333 % - 30 % - 25 % - 20 % - 16.666 % - 14.2857 % - 12.5 % - 11.111 % - 10 % - 5 % - - -5 % - -10 % - -11.111 % - -12.5 % - -14.2857 % - -16.666 % - -20 % - -25 % - -30 % - -33.333 % - -40 % - -50 % - -60 % - -66.666 % - -70 % - -75 % - -80 % - -83.333 % - -90 % - -100 % - +<%@ include file="/common/taglibs.jsp"%> + + + + Index: lams_tool_assessment/web/pages/authoring/start.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/pages/authoring/start.jsp (.../start.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/pages/authoring/start.jsp (.../start.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -3,14 +3,14 @@ - + - - - - + + + + Index: lams_tool_assessment/web/pages/monitoring/parts/usersummary.jsp =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r4353f26f9509808acdf41d0deef6cf5fd458a2b7 --- lams_tool_assessment/web/pages/monitoring/parts/usersummary.jsp (.../usersummary.jsp) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_tool_assessment/web/pages/monitoring/parts/usersummary.jsp (.../usersummary.jsp) (revision 4353f26f9509808acdf41d0deef6cf5fd458a2b7) @@ -10,7 +10,7 @@ - +