From 3610c04466010c8473e75dc46f8c93b5bb35f959 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Mon, 15 Feb 2016 18:40:30 +0100 Subject: [PATCH] OO-1593: add preview and editor for QTI 2.1 questions --- .../ims/qti21/pool/QTI21EditorController.java | 45 ++++++++++++- .../ims/qti21/pool/QTI21ImportProcessor.java | 2 - .../qti21/pool/QTI21PreviewController.java | 3 +- .../qti21/pool/QTI21QPoolServiceProvider.java | 13 +++- .../ims/qti21/pool/_content/pool_editor.html | 1 + .../ims/qti21/pool/_content/qti_preview.html | 1 + .../ui/AssessmentItemDisplayController.java | 66 +++++++++++++++---- .../AssessmentItemEditorController.java | 23 ++++++- .../SingleChoiceEditorController.java | 6 ++ 9 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/olat/ims/qti21/pool/_content/pool_editor.html diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java b/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java index 8e8126d6bf9..db32dc6dd56 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21EditorController.java @@ -19,14 +19,26 @@ */ package org.olat.ims.qti21.pool; +import java.io.File; +import java.net.URI; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.ims.qti21.QTI21Service; +import org.olat.ims.qti21.ui.editor.AssessmentItemEditorController; +import org.olat.ims.qti21.ui.editor.events.AssessmentItemEvent; import org.olat.modules.qpool.QPoolItemEditorController; +import org.olat.modules.qpool.QPoolService; import org.olat.modules.qpool.QuestionItem; +import org.springframework.beans.factory.annotation.Autowired; + +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; /** * @@ -37,13 +49,33 @@ import org.olat.modules.qpool.QuestionItem; public class QTI21EditorController extends BasicController implements QPoolItemEditorController { private final VelocityContainer mainVC; + private AssessmentItemEditorController editorCtrl; + private File resourceFile; private QuestionItem questionItem; + @Autowired + private QPoolService qpoolService; + @Autowired + private QTI21Service qtiService; + public QTI21EditorController(UserRequest ureq, WindowControl wControl, QuestionItem questionItem) { super(ureq, wControl); this.questionItem = questionItem; - mainVC = createVelocityContainer("editor_wrapper"); + mainVC = createVelocityContainer("pool_editor"); + + File resourceDirectory = qpoolService.getRootDirectory(questionItem); + resourceFile = qpoolService.getRootFile(questionItem); + URI assessmentItemUri = resourceFile.toURI(); + + ResolvedAssessmentItem resolvedAssessmentItem = qtiService + .loadAndResolveAssessmentItem(assessmentItemUri, resourceDirectory); + + editorCtrl = new AssessmentItemEditorController(ureq, wControl, + resolvedAssessmentItem, resourceDirectory, resourceFile); + listenTo(editorCtrl); + mainVC.put("editor", editorCtrl.getInitialComponent()); + putInitialPanel(mainVC); } @@ -56,6 +88,17 @@ public class QTI21EditorController extends BasicController implements QPoolItemE protected void doDispose() { // } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == editorCtrl) { + if(event instanceof AssessmentItemEvent) { + AssessmentItemEvent aie = (AssessmentItemEvent)event; + AssessmentItem assessmentItem = aie.getAssessmentItem(); + qtiService.persistAssessmentObject(resourceFile, assessmentItem); + } + } + } @Override protected void event(UserRequest ureq, Component source, Event event) { diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java b/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java index c5297fc16a7..3a454dda847 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java @@ -120,6 +120,4 @@ public class QTI21ImportProcessor { questionItemDao.persist(owner, poolItem); return poolItem; } - - } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21PreviewController.java b/src/main/java/org/olat/ims/qti21/pool/QTI21PreviewController.java index 99c4812b0ba..38d786ed00e 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21PreviewController.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21PreviewController.java @@ -63,10 +63,11 @@ public class QTI21PreviewController extends BasicController { } else { File resourceDirectory = qpoolService.getRootDirectory(qitem); URI assessmentItemUri = file.toURI(); + File itemFile = qpoolService.getRootFile(qitem); ResolvedAssessmentItem resolvedAssessmentItem = qtiService .loadAndResolveAssessmentItem(assessmentItemUri, resourceDirectory); - previewCtrl = new AssessmentItemDisplayController(ureq, wControl, true, resolvedAssessmentItem, resourceDirectory); + previewCtrl = new AssessmentItemDisplayController(ureq, wControl, resolvedAssessmentItem, resourceDirectory, itemFile); listenTo(previewCtrl); mainVC.put("preview", previewCtrl.getInitialComponent()); } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java index 9412f5971c8..39197ca44ab 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java @@ -46,12 +46,14 @@ import org.olat.ims.qti.fileresource.TestFileResource; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; +import org.olat.ims.qti21.model.xml.ManifestPackage; import org.olat.ims.qti21.model.xml.interactions.EssayAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.KPrimAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.SingleChoiceAssessmentItemBuilder; import org.olat.ims.qti21.pool.QTI21AssessmentItemFactory.Type; import org.olat.ims.resources.IMSEntityResolver; +import org.olat.imscp.xml.manifest.ManifestType; import org.olat.modules.qpool.ExportFormatOptions; import org.olat.modules.qpool.ExportFormatOptions.Outcome; import org.olat.modules.qpool.QItemFactory; @@ -220,8 +222,9 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { } @Override - public Controller getEditableController(UserRequest ureq, WindowControl wControl, QuestionItem item) { - return null; + public Controller getEditableController(UserRequest ureq, WindowControl wControl, QuestionItem qitem) { + Controller editorCtrl = new QTI21EditorController(ureq, wControl, qitem); + return editorCtrl; } public QuestionItem createItem(Identity identity, Type type, String title, Locale locale) { @@ -247,6 +250,12 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { VFSLeaf leaf = baseDir.createChildLeaf(qitem.getRootFilename()); File itemFile = ((LocalImpl)leaf).getBasefile(); qtiService.persistAssessmentObject(itemFile, assessmentItem); + + //create imsmanifest + ManifestType manifestType = ManifestPackage.createEmptyManifest(); + ManifestPackage.appendAssessmentItem(itemFile.getName(), manifestType); + ManifestPackage.write(manifestType, new File(itemFile.getParentFile(), "imsmanifest.xml")); + return qitem; } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/pool/_content/pool_editor.html b/src/main/java/org/olat/ims/qti21/pool/_content/pool_editor.html new file mode 100644 index 00000000000..d9eec727b50 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/pool/_content/pool_editor.html @@ -0,0 +1 @@ +$r.render("editor") \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/pool/_content/qti_preview.html b/src/main/java/org/olat/ims/qti21/pool/_content/qti_preview.html index e69de29bb2d..a52735a0088 100644 --- a/src/main/java/org/olat/ims/qti21/pool/_content/qti_preview.html +++ b/src/main/java/org/olat/ims/qti21/pool/_content/qti_preview.html @@ -0,0 +1 @@ +$r.render("preview") \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java index c2b47023b23..e8d3836e62f 100644 --- a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java +++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java @@ -86,7 +86,7 @@ public class AssessmentItemDisplayController extends BasicController implements private final String mapperUri; private final File fUnzippedDirRoot; - private final AssessmentItemRef itemRef; + private final File itemFileRef; private final ResolvedAssessmentItem resolvedAssessmentItem; private CandidateEvent lastEvent; @@ -96,28 +96,70 @@ public class AssessmentItemDisplayController extends BasicController implements @Autowired private QTI21Service qtiService; - public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl, - boolean authorMode, ResolvedAssessmentItem resolvedAssessmentItem, File fUnzippedDirRoot) { - this(ureq, wControl, null, null, authorMode, false, resolvedAssessmentItem, null, fUnzippedDirRoot); + /** + * OPen in memory session + * @param ureq + * @param wControl + * @param authorMode + * @param resolvedAssessmentItem + * @param fUnzippedDirRoot + * @param itemFileRef + */ + public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl, ResolvedAssessmentItem resolvedAssessmentItem, + File fUnzippedDirRoot, File itemFileRef) { + super(ureq, wControl); + + this.itemFileRef = itemFileRef; + this.fUnzippedDirRoot = fUnzippedDirRoot; + this.resolvedAssessmentItem = resolvedAssessmentItem; + currentRequestTimestamp = ureq.getRequestTimestamp(); + candidateSession = new InMemoryAssessmentTestSession(); + mapperUri = registerCacheableMapper(null, UUID.randomUUID().toString(), new ResourcesMapper(itemFileRef.toURI())); + + itemSessionController = enterSession(ureq); + + if (itemSessionController.getItemSessionState().isEnded()) { + mainVC = createVelocityContainer("end"); + } else { + mainVC = createVelocityContainer("run"); + initQtiWorks(ureq); + } + putInitialPanel(mainVC); } public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl, - RepositoryEntry testEntry, AssessmentEntry assessmentEntry, boolean authorMode, boolean persistent, ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef, File fUnzippedDirRoot) { super(ureq, wControl); - this.itemRef = itemRef; + this.itemFileRef = new File(fUnzippedDirRoot, itemRef.getHref().toString()); this.fUnzippedDirRoot = fUnzippedDirRoot; this.resolvedAssessmentItem = resolvedAssessmentItem; currentRequestTimestamp = ureq.getRequestTimestamp(); - if(persistent) { - candidateSession = qtiService.createAssessmentTestSession(getIdentity(), assessmentEntry, testEntry, itemRef.getIdentifier().toString(), testEntry, authorMode); + candidateSession = new InMemoryAssessmentTestSession(); + mapperUri = registerCacheableMapper(null, UUID.randomUUID().toString(), new ResourcesMapper(itemFileRef.toURI())); + + itemSessionController = enterSession(ureq); + + if (itemSessionController.getItemSessionState().isEnded()) { + mainVC = createVelocityContainer("end"); } else { - candidateSession = new InMemoryAssessmentTestSession(); + mainVC = createVelocityContainer("run"); + initQtiWorks(ureq); } + putInitialPanel(mainVC); + } + + public AssessmentItemDisplayController(UserRequest ureq, WindowControl wControl, + RepositoryEntry testEntry, AssessmentEntry assessmentEntry, boolean authorMode, + ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef, File fUnzippedDirRoot) { + super(ureq, wControl); - File assessmentObjectUri = itemRef == null ? null : new File(fUnzippedDirRoot, itemRef.getHref().toString()); - mapperUri = registerCacheableMapper(null, UUID.randomUUID().toString(), new ResourcesMapper(assessmentObjectUri.toURI())); + this.itemFileRef = new File(fUnzippedDirRoot, itemRef.getHref().toString()); + this.fUnzippedDirRoot = fUnzippedDirRoot; + this.resolvedAssessmentItem = resolvedAssessmentItem; + currentRequestTimestamp = ureq.getRequestTimestamp(); + candidateSession = qtiService.createAssessmentTestSession(getIdentity(), assessmentEntry, testEntry, itemRef.getIdentifier().toString(), testEntry, authorMode); + mapperUri = registerCacheableMapper(null, UUID.randomUUID().toString(), new ResourcesMapper(itemFileRef.toURI())); itemSessionController = enterSession(ureq); @@ -131,7 +173,7 @@ public class AssessmentItemDisplayController extends BasicController implements } private void initQtiWorks(UserRequest ureq) { - String filename = itemRef.getHref().toString(); + String filename = itemFileRef.getName(); qtiWorksCtrl = new QtiWorksController(ureq, getWindowControl(), filename); listenTo(qtiWorksCtrl); mainVC.put("qtirun", qtiWorksCtrl.getInitialComponent()); diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java index 519d916d2da..c815989b441 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java @@ -83,6 +83,27 @@ public class AssessmentItemEditorController extends BasicController { @Autowired private AssessmentService assessmentService; + + public AssessmentItemEditorController(UserRequest ureq, WindowControl wControl, + ResolvedAssessmentItem resolvedAssessmentItem, File unzippedDirectory, File itemFile) { + super(ureq, wControl); + this.itemRef = null; + this.resolvedAssessmentItem = resolvedAssessmentItem; + + mainVC = createVelocityContainer("assessment_item_editor"); + tabbedPane = new TabbedPane("itemTabs", getLocale()); + tabbedPane.addListener(this); + mainVC.put("tabbedpane", tabbedPane); + + initItemEditor(ureq); + + displayCtrl = new AssessmentItemDisplayController(ureq, getWindowControl(), resolvedAssessmentItem, unzippedDirectory, itemFile); + listenTo(displayCtrl); + tabbedPane.addTab("Preview", displayCtrl.getInitialComponent()); + + putInitialPanel(mainVC); + } + public AssessmentItemEditorController(UserRequest ureq, WindowControl wControl, RepositoryEntry testEntry, ResolvedAssessmentItem resolvedAssessmentItem, AssessmentItemRef itemRef, File unzippedDirectory) { super(ureq, wControl); @@ -98,7 +119,7 @@ public class AssessmentItemEditorController extends BasicController { AssessmentEntry assessmentEntry = assessmentService.getOrCreateAssessmentEntry(getIdentity(), testEntry, null, testEntry); displayCtrl = new AssessmentItemDisplayController(ureq, getWindowControl(), - testEntry, assessmentEntry, true, true, resolvedAssessmentItem, itemRef, unzippedDirectory); + testEntry, assessmentEntry, true, resolvedAssessmentItem, itemRef, unzippedDirectory); listenTo(displayCtrl); tabbedPane.addTab("Preview", displayCtrl.getInitialComponent()); diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java index 414ad7657ff..cd29a5b65c9 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java @@ -175,6 +175,12 @@ public class SingleChoiceEditorController extends FormBasicController { allOk &= false; } + String correctAnswer = ureq.getParameter("correct"); + if(!StringHelper.containsNonWhitespace(correctAnswer)) { + allOk &= false; + textEl.setErrorKey("form.legende.mandatory", null); + } + return allOk & super.validateFormLogic(ureq); } -- GitLab