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 8e8126d6bf9d5b2de642663553a21e19b983efc1..db32dc6dd560a767b8d9b184453b1bb8f7ba5129 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 c5297fc16a748d74febce497d14153324be0c6ce..3a454dda847a0bf18f1f2224b63413562e26b167 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 99c4812b0ba137853295c953bdb3c731c0c1d3d6..38d786ed00e2c38bf0f92b6784719a78055dd4f0 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 9412f5971c886fb7e0f106bfeb1ffa04d6bac431..39197ca44abf8486d6db971f3f9f50243a4dad50 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 0000000000000000000000000000000000000000..d9eec727b5090e47fcd619ba467b897c56b74310 --- /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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a52735a0088327a60389edbd3df45ba966ad5c3a 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 c2b47023b2312bb006dbd39f7010cfbe0ac3da1b..e8d3836e62f7523c0d94950e31f0a1dcbb5fd43a 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 519d916d2da237de873f30ebd2770731f8848ec4..c815989b441ffbfb2a9a47a2af1f4afb2743c66b 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 414ad7657fff1031a4e53390d8a403bf94e4f904..cd29a5b65c9318f10fa9a535222d8a22d1036f27 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); }