diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java index 9b19ebdd7da2a89265465c91c4e14cfb7db44823..6f54181f45b6f72182abc34e3ece7a5c4fcd964e 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentHtmlBuilder.java @@ -21,6 +21,7 @@ package org.olat.ims.qti21.model.xml; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; import java.util.List; @@ -33,7 +34,7 @@ import org.olat.core.gui.render.StringOutput; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; -import org.olat.core.util.filter.FilterFactory; +import org.olat.core.util.filter.impl.NekoHTMLFilter; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -41,6 +42,7 @@ import org.w3c.dom.Node; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager; import uk.ac.ed.ph.jqtiplus.exception.QtiModelException; @@ -76,7 +78,24 @@ public class AssessmentHtmlBuilder { } public boolean containsSomething(String html) { - return StringHelper.containsNonWhitespace(FilterFactory.getHtmlTagsFilter().filter(html)); + if(!StringHelper.containsNonWhitespace(html)) return false; + + try { + SAXParser parser = new SAXParser(); + ContentDetectionHandler contentHandler = new ContentDetectionHandler(); + parser.setContentHandler(contentHandler); + parser.parse(new InputSource(new StringReader(html))); + return contentHandler.isContentAvailable(); + } catch (SAXException e) { + log.error("", e); + return false; + } catch (IOException e) { + log.error("", e); + return false; + } catch (Exception e) { + log.error("", e); + return false; + } } public String flowStaticString(List<? extends FlowStatic> statics) { @@ -497,4 +516,44 @@ public class AssessmentHtmlBuilder { // } } + + private static class ContentDetectionHandler extends DefaultHandler { + + private boolean collect = false; + private boolean content = false; + + public boolean isContentAvailable() { + return content; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) { + String elem = localName.toLowerCase(); + if("script".equals(elem)) { + collect = false; + } else if(!NekoHTMLFilter.blockTags.contains(localName)) { + content = true; + } + } + + @Override + public void characters(char[] chars, int offset, int length) { + if(!content && collect && offset >= 0 && length > 0) { + String text = new String(chars, offset, length); + if(text.trim().length() > 0) { + content = true; + } + } + } + + @Override + public void endElement(String uri, String localName, String qName) { + String elem = localName.toLowerCase(); + if("script".equals(elem)) { + collect = true; + } else if(!NekoHTMLFilter.blockTags.contains(localName)) { + content = true; + } + } + } } diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java index 4c57f68c40a58beea305d16206e5a69f63f85b73..af2d32f20c86a06a6995a8134645f638a642a6ba 100644 --- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java +++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java @@ -367,8 +367,8 @@ public class AssessmentTestDisplayController extends BasicController implements private void initQtiWorks(UserRequest ureq) { qtiWorksCtrl = new QtiWorksController(ureq, getWindowControl()); - listenTo(qtiWorksCtrl); - mainVC.put("qtirun", qtiWorksCtrl.getInitialComponent()); + listenTo(qtiWorksCtrl); + mainVC.put("qtirun", qtiWorksCtrl.getInitialComponent()); } @Override diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java index 94438a7f5f99a8791f2093831b5acf3250779b30..323a255b914408e91b32e4074450998a460dea7e 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java +++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java @@ -297,7 +297,7 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe } if(writeRubrics) { - sb.append("<div class='o_info o_assessmentsection_rubrics'>"); + sb.append("<div class='o_info o_assessmentsection_rubrics clearfix'>"); //write the titles first if(writeTitles) { sb.append("<h4>"); @@ -316,7 +316,6 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe sb.append("</h4>"); } - for(int i=sectionParentLine.size(); i-->0; ) { AssessmentSection selectedSection = sectionParentLine.get(i); for(RubricBlock rubricBlock:selectedSection.getRubricBlocks()) { diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionEditorController.java index 3289b05ecbb24afb9d62df65da32e27bd15a4e78..ddf06f62955c65ead81087c91170330f98c425ed 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionEditorController.java @@ -19,6 +19,8 @@ */ package org.olat.ims.qti21.ui.editor; +import java.io.File; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.tabbedpane.TabbedPane; @@ -28,6 +30,7 @@ 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.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.ui.AssessmentTestDisplayController; import org.olat.ims.qti21.ui.editor.events.AssessmentSectionEvent; @@ -44,6 +47,10 @@ public class AssessmentSectionEditorController extends BasicController { private final TabbedPane tabbedPane; private final VelocityContainer mainVC; + private final File testFile; + private final File rootDirectory; + private final VFSContainer rootContainer; + private final AssessmentSection section; private final boolean editable; @@ -53,10 +60,14 @@ public class AssessmentSectionEditorController extends BasicController { private AssessmentSectionExpertOptionsEditorController expertOptionsCtrl; public AssessmentSectionEditorController(UserRequest ureq, WindowControl wControl, - AssessmentSection section, boolean restrictedEdit, boolean editable) { + AssessmentSection section, File rootDirectory, VFSContainer rootContainer, File testFile, + boolean restrictedEdit, boolean editable) { super(ureq, wControl, Util.createPackageTranslator(AssessmentTestDisplayController.class, ureq.getLocale())); this.section = section; this.editable = editable; + this.testFile = testFile; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; this.restrictedEdit = restrictedEdit; mainVC = createVelocityContainer("assessment_test_editor"); @@ -71,7 +82,7 @@ public class AssessmentSectionEditorController extends BasicController { } private void initSectionEditor(UserRequest ureq) { - optionsCtrl = new AssessmentSectionOptionsEditorController(ureq, getWindowControl(), section, restrictedEdit, editable); + optionsCtrl = new AssessmentSectionOptionsEditorController(ureq, getWindowControl(), section, rootDirectory, rootContainer, testFile, restrictedEdit, editable); listenTo(optionsCtrl); expertOptionsCtrl = new AssessmentSectionExpertOptionsEditorController(ureq, getWindowControl(), section, restrictedEdit, editable); listenTo(expertOptionsCtrl); diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionOptionsEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionOptionsEditorController.java index a38369ed5fb82c7b100d7934f61321f3346c9db9..9b67a4ecb88f7a86092aec572e7bb74c816f131c 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionOptionsEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentSectionOptionsEditorController.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti21.ui.editor; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -35,6 +36,7 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.qti21.model.xml.AssessmentHtmlBuilder; import org.olat.ims.qti21.ui.AssessmentTestDisplayController; import org.olat.ims.qti21.ui.editor.events.AssessmentSectionEvent; @@ -57,6 +59,10 @@ public class AssessmentSectionOptionsEditorController extends FormBasicControlle private SingleSelection shuffleEl, randomSelectedEl; private List<RichTextElement> rubricEls = new ArrayList<>(); + private final File testFile; + private final File rootDirectory; + private final VFSContainer rootContainer; + private final AssessmentSection section; private final AssessmentHtmlBuilder htmlBuilder; @@ -66,10 +72,14 @@ public class AssessmentSectionOptionsEditorController extends FormBasicControlle private static final String[] yesnoKeys = new String[]{ "y", "n"}; public AssessmentSectionOptionsEditorController(UserRequest ureq, WindowControl wControl, - AssessmentSection section, boolean restrictedEdit, boolean editable) { + AssessmentSection section, File rootDirectory, VFSContainer rootContainer, File testFile, + boolean restrictedEdit, boolean editable) { super(ureq, wControl, Util.createPackageTranslator(AssessmentTestDisplayController.class, ureq.getLocale())); this.section = section; this.editable = editable; + this.testFile = testFile; + this.rootDirectory = rootDirectory; + this.rootContainer = rootContainer; this.restrictedEdit = restrictedEdit; htmlBuilder = new AssessmentHtmlBuilder(); initForm(ureq); @@ -87,9 +97,11 @@ public class AssessmentSectionOptionsEditorController extends FormBasicControlle titleEl = uifactory.addTextElement("title", "form.metadata.title", 255, title, formLayout); titleEl.setEnabled(editable); titleEl.setMandatory(true); - + + String relativePath = rootDirectory.toPath().relativize(testFile.toPath().getParent()).toString(); + VFSContainer itemContainer = (VFSContainer)rootContainer.resolve(relativePath); if(section.getRubricBlocks().isEmpty()) { - RichTextElement rubricEl = uifactory.addRichTextElementForQTI21("rubric" + counter++, "form.imd.rubric", "", 8, -1, null, + RichTextElement rubricEl = uifactory.addRichTextElementForQTI21("rubric" + counter++, "form.imd.rubric", "", 12, -1, itemContainer, formLayout, ureq.getUserSession(), getWindowControl()); rubricEl.getEditorConfiguration().setFileBrowserUploadRelPath("media"); rubricEl.setEnabled(editable); @@ -97,7 +109,7 @@ public class AssessmentSectionOptionsEditorController extends FormBasicControlle } else { for(RubricBlock rubricBlock:section.getRubricBlocks()) { String rubric = htmlBuilder.blocksString(rubricBlock.getBlocks()); - RichTextElement rubricEl = uifactory.addRichTextElementForQTI21("rubric" + counter++, "form.imd.rubric", rubric, 8, -1, null, + RichTextElement rubricEl = uifactory.addRichTextElementForQTI21("rubric" + counter++, "form.imd.rubric", rubric, 12, -1, itemContainer, formLayout, ureq.getUserSession(), getWindowControl()); rubricEl.getEditorConfiguration().setFileBrowserUploadRelPath("media"); rubricEl.setEnabled(editable); @@ -191,7 +203,7 @@ public class AssessmentSectionOptionsEditorController extends FormBasicControlle //rubrics List<RubricBlock> rubricBlocks = new ArrayList<>(); for(RichTextElement rubricEl:rubricEls) { - String rubric = rubricEl.getValue(); + String rubric = rubricEl.getRawValue(); if(htmlBuilder.containsSomething(rubric)) { RubricBlock rubricBlock = (RubricBlock)rubricEl.getUserObject(); if(rubricBlock == null) { diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java index bd2159fa6e1f8ea8a288696d8360e960516407d0..3ee926485319e2a78a467252771e42df2ded378f 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java @@ -1120,8 +1120,10 @@ public class AssessmentTestComposerController extends MainLayoutBasicController currentEditorCtrl = new AssessmentTestPartEditorController(ureq, getWindowControl(), (TestPart)uobject, restrictedEdit, assessmentTestBuilder.isEditable()); } else if(uobject instanceof AssessmentSection) { + URI testURI = resolvedAssessmentTest.getTestLookup().getSystemId(); + File testFile = new File(testURI); currentEditorCtrl = new AssessmentSectionEditorController(ureq, getWindowControl(), (AssessmentSection)uobject, - restrictedEdit, assessmentTestBuilder.isEditable()); + unzippedDirRoot, unzippedContRoot, testFile, restrictedEdit, assessmentTestBuilder.isEditable()); } else if(uobject instanceof AssessmentItemRef) { AssessmentItemRef itemRef = (AssessmentItemRef)uobject; ResolvedAssessmentItem item = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef);