Index: idea_project/.idea/.gitignore =================================================================== diff -u -r5150cf36d19aed2cc450c7406876b50e1affe83b -rdb5bb77b5d15931da025332510f19535146c4333 --- idea_project/.idea/.gitignore (.../.gitignore) (revision 5150cf36d19aed2cc450c7406876b50e1affe83b) +++ idea_project/.idea/.gitignore (.../.gitignore) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -15,4 +15,6 @@ /dataSources.xml /sqldialects.xml /uiDesigner.xml -/encodings.xml \ No newline at end of file +/encodings.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions \ No newline at end of file Index: idea_project/idea_project.iml =================================================================== diff -u -r10ada76a9a401c0fb4842e107613be3ffcc8430b -rdb5bb77b5d15931da025332510f19535146c4333 --- idea_project/idea_project.iml (.../idea_project.iml) (revision 10ada76a9a401c0fb4842e107613be3ffcc8430b) +++ idea_project/idea_project.iml (.../idea_project.iml) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -2,7 +2,9 @@ - + + + \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20240315.sql =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20240315.sql (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20240315.sql (revision db5bb77b5d15931da025332510f19535146c4333) @@ -0,0 +1,17 @@ +-- Turn off autocommit, so nothing is committed if there is an error +SET AUTOCOMMIT = 0; +SET FOREIGN_KEY_CHECKS=0; +-- Put all sql statements below here + +-- LDEV-5452 Allow removing questions in live lesson + +ALTER TABLE lams_learner_interaction_event DROP FOREIGN KEY lams_learner_interaction_event_FK2; +ALTER TABLE lams_learner_interaction_event ADD CONSTRAINT `lams_learner_interaction_event_FK` FOREIGN KEY (`qb_tool_question_uid`) REFERENCES `lams_qb_tool_question` (`tool +_question_uid`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- Put all sql statements above here + +-- If there were no errors, commit and restore autocommit to on +COMMIT; +SET AUTOCOMMIT = 1; +SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r8f973f68eb4f1e56c4254e911fc700032677b2a9 -rdb5bb77b5d15931da025332510f19535146c4333 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 8f973f68eb4f1e56c4254e911fc700032677b2a9) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -868,7 +868,7 @@ .append(" \"").append(assessmentResult.getUser().getLoginName()).append("\" for question ") .append(questionDto.getUid()).append(" for question result ").append(questionResult.getUid()) .append(" answer was \"").append(questionResult.getAnswer()) - .append("\" and it is now blank, skipping save."); + .append("\" and it is now blank, skipping save to DB."); log.warn(autosaveLogBuilder); if (logAutosave.isTraceEnabled()) { logAutosave.trace(autosaveLogBuilder); @@ -2612,45 +2612,48 @@ for (AssessmentQuestion newQuestion : newQuestions) { if (oldQuestion.getDisplayOrder() == newQuestion.getDisplayOrder()) { - boolean isQuestionModified = false; + boolean isQuestionModified = !oldQuestion.getQbQuestion().getUid() + .equals(newQuestion.getQbQuestion().getUid()); + if (!isQuestionModified) { - // title or question is different - do nothing. Also question grade can't be changed + // title or question is different - do nothing. Also question grade can't be changed - //QbQuestion.TYPE_TRUE_FALSE - if (oldQuestion.getQbQuestion().getCorrectAnswer() != newQuestion.getQbQuestion() - .getCorrectAnswer()) { - isQuestionModified = true; - } + //QbQuestion.TYPE_TRUE_FALSE + if (oldQuestion.getQbQuestion().getCorrectAnswer() != newQuestion.getQbQuestion() + .getCorrectAnswer()) { + isQuestionModified = true; + } - // options are different - List oldOptions = oldQuestion.getQbQuestion().getQbOptions(); - List newOptions = newQuestion.getQbQuestion().getQbOptions(); - for (QbOption oldOption : oldOptions) { - for (QbOption newOption : newOptions) { - if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { + // options are different + List oldOptions = oldQuestion.getQbQuestion().getQbOptions(); + List newOptions = newQuestion.getQbQuestion().getQbOptions(); + for (QbOption oldOption : oldOptions) { + for (QbOption newOption : newOptions) { + if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { - //short answer - if (((oldQuestion.getType() == QbQuestion.TYPE_VERY_SHORT_ANSWERS) - && !StringUtils.equals(oldOption.getName(), newOption.getName())) - //numbering - || (oldOption.getNumericalOption() != newOption.getNumericalOption()) || ( - oldOption.getAcceptedError() != newOption.getAcceptedError()) - //option grade - || (oldOption.getMaxMark() != newOption.getMaxMark()) - //changed correct option - || (oldOption.isCorrect() != newOption.isCorrect())) { - isQuestionModified = true; - break; + //short answer + if (((oldQuestion.getType() == QbQuestion.TYPE_VERY_SHORT_ANSWERS) + && !StringUtils.equals(oldOption.getName(), newOption.getName())) + //numbering + || (oldOption.getNumericalOption() != newOption.getNumericalOption()) || ( + oldOption.getAcceptedError() != newOption.getAcceptedError()) + //option grade + || (oldOption.getMaxMark() != newOption.getMaxMark()) + //changed correct option + || (oldOption.isCorrect() != newOption.isCorrect())) { + isQuestionModified = true; + break; + } } } + if (isQuestionModified) { + break; + } } - if (isQuestionModified) { - break; + if (oldOptions.size() != newOptions.size()) { + isQuestionModified = true; } } - if (oldOptions.size() != newOptions.size()) { - isQuestionModified = true; - } if (isQuestionModified) { modifiedQuestions.add(newQuestion); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java =================================================================== diff -u -rc6bcdb38a7fee9aa1d1b4d107515d6b0de6fef60 -rdb5bb77b5d15931da025332510f19535146c4333 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (.../AuthoringController.java) (revision c6bcdb38a7fee9aa1d1b4d107515d6b0de6fef60) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (.../AuthoringController.java) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -785,7 +785,13 @@ qbService.fillVersionMap(newQbQuestion); question.setQbQuestion(newQbQuestion); - return "pages/authoring/parts/questionlist"; + //in case of edit in monitor and at least one attempted user, we show authoring page with restricted options + boolean isAuthoringRestricted = (boolean) sessionMap.get(AssessmentConstants.ATTR_IS_AUTHORING_RESTRICTED); + if (isAuthoringRestricted) { + return "pages/authoring/parts/questionlistRestricted"; + } else { + return "pages/authoring/parts/questionlist"; + } } @RequestMapping("/getAllQbQuestionUids") Index: lams_tool_assessment/web/pages/authoring/authoring.jsp =================================================================== diff -u -r7521e0cd5b5f4b01d188ca0ddad1e5f59d3bde27 -rdb5bb77b5d15931da025332510f19535146c4333 --- lams_tool_assessment/web/pages/authoring/authoring.jsp (.../authoring.jsp) (revision 7521e0cd5b5f4b01d188ca0ddad1e5f59d3bde27) +++ lams_tool_assessment/web/pages/authoring/authoring.jsp (.../authoring.jsp) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -51,7 +51,8 @@ $("#display-summary").removeAttr("disabled", "disabled"); $("#questionsPerPage").prop('disabled', false); - $('#syncRatQuestions').val(hasMatchingRatActivity && questionsEdited && + let isAuthoringRestricted = ${not empty sessionMap.isAuthoringRestricted and sessionMap.isAuthoringRestricted}; + $('#syncRatQuestions').val(!isAuthoringRestricted && hasMatchingRatActivity && questionsEdited && confirm("")); return true; Index: lams_tool_assessment/web/pages/authoring/parts/questionlistRestricted.jsp =================================================================== diff -u -rf7837f2892736221011a0eb6cbef1027b6692224 -rdb5bb77b5d15931da025332510f19535146c4333 --- lams_tool_assessment/web/pages/authoring/parts/questionlistRestricted.jsp (.../questionlistRestricted.jsp) (revision f7837f2892736221011a0eb6cbef1027b6692224) +++ lams_tool_assessment/web/pages/authoring/parts/questionlistRestricted.jsp (.../questionlistRestricted.jsp) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -32,13 +32,7 @@ - - - - v. ${question.qbQuestion.version} - - - + @@ -71,26 +65,67 @@ - + + + + + + + + + + + + + + + + - + - " + " alt="" onClick="javascript:toggleQuestionRequired(this)"> - + "> - + - + \ No newline at end of file Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java =================================================================== diff -u -r899c6912c8a159c4aaab1d37d64500ba0b191a8e -rdb5bb77b5d15931da025332510f19535146c4333 --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision 899c6912c8a159c4aaab1d37d64500ba0b191a8e) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -564,36 +564,38 @@ public void recalculateUserAnswers(Scratchie scratchie, Set oldItems, Set newItems, String oldPresetMarks) { // create list of modified questions - List modifiedItems = new ArrayList<>(); + + Map modifiedItems = new LinkedHashMap<>(); for (ScratchieItem oldItem : oldItems) { for (ScratchieItem newItem : newItems) { if (oldItem.getDisplayOrder() == newItem.getDisplayOrder()) { + boolean isItemModified = !oldItem.getQbQuestion().getUid().equals(newItem.getQbQuestion().getUid()); + if (!isItemModified) { + // title or question is different - do nothing - // title or question is different - do nothing + // options are different + List oldOptions = oldItem.getQbQuestion().getQbOptions(); + List newOptions = newItem.getQbQuestion().getQbOptions(); - // options are different - List oldOptions = oldItem.getQbQuestion().getQbOptions(); - List newOptions = newItem.getQbQuestion().getQbOptions(); - boolean isItemModified = oldOptions.size() != newOptions.size(); + for (QbOption oldOption : oldOptions) { + if (isItemModified) { + break; + } - for (QbOption oldOption : oldOptions) { - if (isItemModified) { - break; - } + for (QbOption newOption : newOptions) { + if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { - for (QbOption newOption : newOptions) { - if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { - - if (oldOption.isCorrect() != newOption.isCorrect()) { - isItemModified = true; - break; + if (oldOption.isCorrect() != newOption.isCorrect()) { + isItemModified = true; + break; + } } } } } if (isItemModified) { - modifiedItems.add(newItem); + modifiedItems.put(oldItem, newItem); } } } @@ -602,30 +604,59 @@ List sessionList = scratchieSessionDao.getByContentId(scratchie.getContentId()); for (ScratchieSession session : sessionList) { Long toolSessionId = session.getSessionId(); - List visitLogsToDelete = new ArrayList<>(); String newPresetMarks = scratchie.getPresetMarks(); boolean isRecalculateMarks = oldPresetMarks == null ? newPresetMarks != null : newPresetMarks == null || !oldPresetMarks.equals(newPresetMarks); - // remove all scratches for modified items + List visitLogsToDelete = new LinkedList<>(); // [+] if the question is modified - for (ScratchieItem modifiedItem : modifiedItems) { - List visitLogs = scratchieAnswerVisitDao.getLogsBySessionAndItem(toolSessionId, - modifiedItem.getUid()); - visitLogsToDelete.addAll(visitLogs); + for (Map.Entry modifiedItem : modifiedItems.entrySet()) { + ScratchieItem oldItem = modifiedItem.getKey(); + ScratchieItem newItem = modifiedItem.getValue(); + List oldVisitLogs = scratchieAnswerVisitDao.getLogsBySessionAndItem( + toolSessionId, oldItem.getUid()); + if (oldVisitLogs.isEmpty()) { + continue; + } + isRecalculateMarks = true; + boolean correctAnswerFound = false; + for (ScratchieAnswerVisitLog visitLog : oldVisitLogs) { + if (correctAnswerFound) { + visitLogsToDelete.add(visitLog); + continue; + } + visitLog.setQbToolQuestion(newItem); + + int oldOptionDisplayOrder = 1; + for (QbOption oldOption : oldItem.getQbQuestion().getQbOptions()) { + if (oldOption.getUid().equals(visitLog.getQbOption().getUid())) { + int newOptionDisplayOrder = 1; + for (QbOption newOption : newItem.getQbQuestion().getQbOptions()) { + if (oldOptionDisplayOrder == newOptionDisplayOrder) { + visitLog.setQbOption(newOption); + scratchieAnswerVisitDao.saveObject(visitLog); + if (newOption.isCorrect()) { + correctAnswerFound = true; + } + break; + } + newOptionDisplayOrder++; + } + break; + } + oldOptionDisplayOrder++; + } + } } // remove all visit logs marked for deletion Iterator iter = visitLogsToDelete.iterator(); while (iter.hasNext()) { ScratchieAnswerVisitLog visitLogToDelete = iter.next(); - iter.remove(); scratchieAnswerVisitDao.removeObject(ScratchieAnswerVisitLog.class, visitLogToDelete.getUid()); - isRecalculateMarks = true; } - // [+] doing nothing if the new question was added // recalculate marks if it's required Index: lams_tool_scratchie/web/pages/authoring/authoring.jsp =================================================================== diff -u -r7521e0cd5b5f4b01d188ca0ddad1e5f59d3bde27 -rdb5bb77b5d15931da025332510f19535146c4333 --- lams_tool_scratchie/web/pages/authoring/authoring.jsp (.../authoring.jsp) (revision 7521e0cd5b5f4b01d188ca0ddad1e5f59d3bde27) +++ lams_tool_scratchie/web/pages/authoring/authoring.jsp (.../authoring.jsp) (revision db5bb77b5d15931da025332510f19535146c4333) @@ -3,6 +3,7 @@ <%@ page import="org.lamsfoundation.lams.tool.scratchie.ScratchieConstants"%> + @@ -37,19 +38,19 @@ .question-version-dropdown { margin-top: -3px; } - + .question-version-dropdown .dropdown-menu { min-width: 160px; } - + .question-version-dropdown li a { display: inline-block; } .question-version-dropdown li.disabled a:first-child { text-decoration: underline; } - +