Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20190722.sql =================================================================== diff -u -rc62f9090c850644a135f716d794329f8d34adbf7 -r7b01f2faaa0d72d872e4624687e4c269b8bf522a --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20190722.sql (.../patch20190722.sql) (revision c62f9090c850644a135f716d794329f8d34adbf7) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20190722.sql (.../patch20190722.sql) (revision 7b01f2faaa0d72d872e4624687e4c269b8bf522a) @@ -618,7 +618,7 @@ -- fill table with options matching unique QB questions inserted above INSERT INTO lams_qb_option (qb_question_uid, display_order, name, matching_pair, numerical_option, max_mark, accepted_error, feedback) SELECT q.uid, o.sequence_id, IFNULL(o.option_string, ''), o.question, o.option_float, - o.grade, o.accepted_error, o.feedback + IF(o.correct = 1, 1, o.grade), o.accepted_error, o.feedback FROM tl_laasse10_question_option AS o JOIN lams_qb_question AS q ON o.question_uid = q.tmp_question_id Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentImportContentVersionFilter.java =================================================================== diff -u -r2b3d3b2f61d37cf2fa7cccc0cf4565e5dfc4b88e -r7b01f2faaa0d72d872e4624687e4c269b8bf522a --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentImportContentVersionFilter.java (.../AssessmentImportContentVersionFilter.java) (revision 2b3d3b2f61d37cf2fa7cccc0cf4565e5dfc4b88e) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentImportContentVersionFilter.java (.../AssessmentImportContentVersionFilter.java) (revision 7b01f2faaa0d72d872e4624687e4c269b8bf522a) @@ -20,13 +20,28 @@ * **************************************************************** */ - package org.lamsfoundation.lams.tool.assessment.service; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + import org.lamsfoundation.lams.learningdesign.service.ToolContentVersionFilter; +import org.lamsfoundation.lams.qb.QbUtils; import org.lamsfoundation.lams.tool.assessment.model.Assessment; import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.XMLUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; /** * Import filter class for different version of Assessment content. @@ -57,7 +72,7 @@ this.renameField(AssessmentQuestion.class, "questionOptions", "options"); this.addField(AssessmentQuestion.class, "answerRequired", "true"); } - + /** * Import 20140428 version content to 20140707 version tool server. */ @@ -73,28 +88,211 @@ this.removeField(AssessmentQuestion.class, "mark"); this.removeField(AssessmentQuestion.class, "penalty"); this.removeField(AssessmentQuestion.class, "answerTotalGrade"); - + // this.removeField(AssessmentQuestionOption.class, "answerInt"); // this.removeField(AssessmentQuestionOption.class, "answerBoolean"); } - + /** * Import 20140428 version content to 20140707 version tool server. */ public void up20170315To20190110() { this.renameField(QuestionReference.class, "defaultGrade", "maxMark"); this.renameField(QuestionReference.class, "sequenceId", "displayOrder"); } - + /** * Import 20190110 version content to 20190517 version tool server. */ public void up20190110To20190517() { this.removeField(AssessmentQuestion.class, "questionHash"); } - + public void up20190517To20190704() { this.removeField(QuestionReference.class, "title"); this.removeField(QuestionReference.class, "type"); } + + /** + * Migration to Question Bank + */ + public void up20190704To20190809(String toolFilePath) throws IOException { + // find LD's content folder ID to use it in new QB questions + String contentFolderId = null; + try { + File ldFile = new File(new File(toolFilePath).getParentFile().getParentFile(), "learning_design.xml"); + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = docBuilder.parse(new FileInputStream(ldFile)); + Element ldRoot = doc.getDocumentElement(); + contentFolderId = XMLUtil.getChildElementValue(ldRoot, "contentFolderID", null); + } catch (Exception e) { + throw new IOException("Error while extracting LD content folder ID for Question Bank migration", e); + } + final String contentFolderIdFinal = contentFolderId; + + // tell which file to process and what to do with its root element + transformXML(toolFilePath, toolRoot -> { + Document document = toolRoot.getOwnerDocument(); + + // first find questions + NodeList assessmentQuestions = toolRoot + .getElementsByTagName("org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion"); + if (assessmentQuestions.getLength() == 0) { + return; + } + + String defaultCreateDate = new SimpleDateFormat(DateUtil.EXPORT_LD_FORMAT).format(new Date()); + + // go through each question + for (int assessmentQuestionIndex = 0; assessmentQuestionIndex < assessmentQuestions + .getLength(); assessmentQuestionIndex++) { + Element assessmentQuestion = (Element) assessmentQuestions.item(assessmentQuestionIndex); + // create an element for QbQuestion + Element qbQuestion = document.createElement("qbQuestion"); + assessmentQuestion.appendChild(qbQuestion); + + // transform Assessment data into QB structure + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "type", "type", null, false, true); + // Question ID will be filled later as it requires QbService + XMLUtil.addTextElement(qbQuestion, "version", "1"); + XMLUtil.addTextElement(qbQuestion, "contentFolderId", contentFolderIdFinal); + XMLUtil.rewriteTextElement(toolRoot, qbQuestion, "created", "createDate", defaultCreateDate, true, + false); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "title", "name", null, false, true, + QbUtils.QB_MIGRATION_CKEDITOR_CLEANER, QbUtils.QB_MIGRATION_TAG_CLEANER); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "question", "description", null, false, true, + QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "defaultGrade", "maxMark", "1", false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "generalFeedback", "feedback", null, false, + true, QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + XMLUtil.rewriteTextElement(assessmentQuestion, assessmentQuestion, "sequenceId", "displayOrder", null, + false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "penaltyFactor", "penaltyFactor", "0", false, + true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "answerRequired", "answerRequired", "false", + false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "multipleAnswersAllowed", + "multipleAnswersAllowed", "false", false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "incorrectAnswerNullifiesMark", + "incorrectAnswerNullifiesMark", "false", false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "feedbackOnCorrect", "feedbackOnCorrect", + null, false, true, QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "feedbackOnPartiallyCorrect", + "feedbackOnPartiallyCorrect", null, false, true, QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "feedbackOnIncorrect", "feedbackOnIncorrect", + null, false, true, QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "shuffle", "shuffle", "false", false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "prefixAnswersWithLetters", + "prefixAnswersWithLetters", "false", false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "caseSensitive", "caseSensitive", "false", + false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "correctAnswer", "correctAnswer", "false", + false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "allowRichEditor", "allowRichEditor", + "false", false, true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "maxWordsLimit", "maxWordsLimit", "0", false, + true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "minWordsLimit", "minWordsLimit", "0", false, + true); + XMLUtil.rewriteTextElement(assessmentQuestion, qbQuestion, "hedgingJustificationEnabled", + "hedgingJustificationEnabled", "false", false, true); + + // get rid of junk + XMLUtil.removeElement(assessmentQuestion, "questionHash"); + + // now it's time for options + NodeList assessmentOptions = assessmentQuestion + .getElementsByTagName("org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionOption"); + if (assessmentOptions.getLength() == 0) { + continue; + } + + boolean shiftOptionsDisplayOrder = false; + List assessmentOptionsSequenceIds = XMLUtil + .findChildren((Element) assessmentOptions.item(0).getParentNode(), "sequenceId"); + for (Element sequenceIdElement : assessmentOptionsSequenceIds) { + int sequenceId = Integer.parseInt(sequenceIdElement.getTextContent()); + if (sequenceId == 0) { + shiftOptionsDisplayOrder = true; + break; + } + } + + Element qbOptions = document.createElement("qbOptions"); + qbQuestion.appendChild(qbOptions); + int maxDisplayOrder = 0; + for (int assessmentOptionIndex = 0; assessmentOptionIndex < assessmentOptions + .getLength(); assessmentOptionIndex++) { + Element assessmentOption = (Element) assessmentOptions.item(assessmentOptionIndex); + Element qbOption = document.createElement("org.lamsfoundation.lams.qb.model.QbOption"); + qbOptions.appendChild(qbOption); + + String sequenceIdString = XMLUtil.getChildElementValue(assessmentOption, "sequenceId", null); + Integer sequenceId = sequenceIdString == null ? null : Integer.valueOf(sequenceIdString); + if (sequenceId == null) { + sequenceId = ++maxDisplayOrder; + } + if (shiftOptionsDisplayOrder) { + sequenceId++; + } + XMLUtil.rewriteTextElement(assessmentOption, qbOption, "sequenceId", "displayOrder", + String.valueOf(sequenceId)); + String correct = XMLUtil.getChildElementValue(assessmentOption, "correct", "false"); + if (Boolean.TRUE.toString().equalsIgnoreCase(correct)) { + XMLUtil.addTextElement(qbOption, "maxMark", "1.0"); + } else { + XMLUtil.rewriteTextElement(assessmentOption, qbOption, "grade", "maxMark", "0"); + } + XMLUtil.rewriteTextElement(assessmentOption, qbOption, "optionString", "name", null, false, false, + QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + XMLUtil.rewriteTextElement(assessmentOption, qbOption, "feedback", "feedback", null); + XMLUtil.rewriteTextElement(assessmentOption, qbOption, "question", "matchingPair", null, false, + false, QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + XMLUtil.rewriteTextElement(assessmentOption, qbOption, "acceptedError", "acceptedError", null); + XMLUtil.rewriteTextElement(assessmentOption, qbOption, "optionFloat", "numericalOption", null); + } + + // get rid of junk + assessmentQuestion.removeChild(assessmentOptions.item(0).getParentNode()); + + // now rewrite units + NodeList assessmentUnits = assessmentQuestion + .getElementsByTagName("org.lamsfoundation.lams.tool.assessment.model.AssessmentUnit"); + if (assessmentUnits.getLength() == 0) { + continue; + } + + Element qbUnits = document.createElement("units"); + qbQuestion.appendChild(qbUnits); + + for (int assessmentUnitIndex = 0; assessmentUnitIndex < assessmentUnits + .getLength(); assessmentUnitIndex++) { + Element assessmentUnit = (Element) assessmentUnits.item(assessmentUnitIndex); + Element qbUnit = document.createElement("org.lamsfoundation.lams.qb.model.QbQuestionUnit"); + qbUnits.appendChild(qbUnit); + + XMLUtil.rewriteTextElement(assessmentUnit, qbUnit, "sequenceId", "displayOrder", null); + XMLUtil.rewriteTextElement(assessmentUnit, qbUnit, "unit", "name", null); + XMLUtil.rewriteTextElement(assessmentUnit, qbUnit, "multiplier", "multiplier", null); + } + + // remove old units section from the legacy assessment question + assessmentQuestion.removeChild(assessmentUnits.item(0).getParentNode()); + } + + // now rewrite question references + NodeList questionReferences = toolRoot + .getElementsByTagName("org.lamsfoundation.lams.tool.assessment.model.QuestionReference"); + if (questionReferences.getLength() == 0) { + return; + } + + for (int questionReferenceIndex = 0; questionReferenceIndex < questionReferences + .getLength(); questionReferenceIndex++) { + Element questionReference = (Element) questionReferences.item(questionReferenceIndex); + XMLUtil.rewriteTextElement(questionReference, questionReference, "defaultGrade", "maxMark", "1", false, + true); + } + }); + } } Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r2b3d3b2f61d37cf2fa7cccc0cf4565e5dfc4b88e -r7b01f2faaa0d72d872e4624687e4c269b8bf522a --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 2b3d3b2f61d37cf2fa7cccc0cf4565e5dfc4b88e) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 7b01f2faaa0d72d872e4624687e4c269b8bf522a) @@ -2606,6 +2606,10 @@ } toolContentObj = Assessment.newInstance(toolContentObj, toolContentId); + for (AssessmentQuestion assessmentQuestion : toolContentObj.getQuestions()) { + qbService.prepareQuestionForExport(assessmentQuestion.getQbQuestion()); + } + try { exportContentService.exportToolContent(toolContentId, toolContentObj, assessmentToolContentHandler, rootPath); @@ -2644,6 +2648,26 @@ } toolContentObj.setCreatedBy(user); + long publicQbCollectionUid = qbService.getPublicCollection().getUid(); + + // we need to save QB questions and options first + for (AssessmentQuestion assessmentQuestion : toolContentObj.getQuestions()) { + QbQuestion qbQuestion = assessmentQuestion.getQbQuestion(); + + // try to match the question to an existing QB question in DB + QbQuestion existingQuestion = qbService.getQuestionByUUID(qbQuestion.getUuid()); + if (existingQuestion == null) { + // none found, create a new QB question + qbService.insertQuestion(qbQuestion); + qbService.addQuestionToCollection(publicQbCollectionUid, qbQuestion.getQuestionId(), false); + } else { + // found, use the existing one + assessmentQuestion.setQbQuestion(existingQuestion); + } + + assessmentDao.insert(assessmentQuestion); + } + saveOrUpdateAssessment(toolContentObj); } catch (ImportToolContentException e) { throw new ToolException(e);