diff --git a/pom.xml b/pom.xml index 272c5dc91be3474cd9f5b21b2a6ce788c7899b66..1a20a051b5510ed5bdc9df1c7e6870c3de1fefb4 100644 --- a/pom.xml +++ b/pom.xml @@ -1528,7 +1528,7 @@ <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>1.4</version> + <version>2.4</version> </dependency> <dependency> <groupId>org.mnode.ical4j</groupId> diff --git a/src/main/java/org/olat/core/util/ZipUtil.java b/src/main/java/org/olat/core/util/ZipUtil.java index 36f4e3c851ff66bc70ff0a9e89cc0e291867be46..c64e6a4ad3300a40b533eaccb3fd5ac1a65e05e7 100644 --- a/src/main/java/org/olat/core/util/ZipUtil.java +++ b/src/main/java/org/olat/core/util/ZipUtil.java @@ -30,6 +30,7 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -45,6 +46,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; +import org.apache.commons.io.IOUtils; import org.olat.core.commons.modules.bc.meta.MetaInfo; import org.olat.core.commons.modules.bc.meta.MetaInfoHelper; import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged; @@ -125,6 +127,33 @@ public class ZipUtil { return unzip(zipLeaf, targetDir, null, false); } + /** + * Unzip a file in the target dir with the restricted version + * @param zipFile + * @param targetDir + * @return + */ + public static boolean unzipStrict(File zipFile, VFSContainer targetDir) { + if (targetDir instanceof LocalFolderImpl) { + String outdir = ((LocalFolderImpl) targetDir).getBasefile().getAbsolutePath(); + InputStream in = null; + try { + long s = System.currentTimeMillis(); + in = new FileInputStream(zipFile); + xxunzip (in, outdir); + log.info("unzip file="+zipFile.getName()+" to="+outdir +" t="+Long.toString(System.currentTimeMillis()-s)); + return true; + } catch (IOException e) { + log.error("I/O failure while unzipping "+zipFile.getName()+" to "+outdir); + return false; + } finally { + IOUtils.closeQuietly(in); + } + } + return false; + } + + /** * Unzip a file to a directory using the versioning system of VFS * @param zipLeaf The file to unzip diff --git a/src/main/java/org/olat/ims/qti/QTI12PreviewController.java b/src/main/java/org/olat/ims/qti/QTI12PreviewController.java index 2196fcdfec57a86bfafc0215fefad39994ef486f..89b87e97ae78ba6cc4b40f9119e370b5024c1016 100644 --- a/src/main/java/org/olat/ims/qti/QTI12PreviewController.java +++ b/src/main/java/org/olat/ims/qti/QTI12PreviewController.java @@ -19,27 +19,36 @@ */ package org.olat.ims.qti; -import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; +import javax.servlet.http.HttpServletRequest; + import org.dom4j.Document; import org.dom4j.Element; +import org.olat.core.CoreSpringFactory; +import org.olat.core.dispatcher.mapper.Mapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.panel.Panel; 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.gui.media.MediaResource; +import org.olat.core.gui.media.NotFoundMediaResource; import org.olat.core.gui.translator.Translator; import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSMediaResource; import org.olat.core.util.xml.XMLParser; import org.olat.ims.qti.editor.ItemPreviewController; import org.olat.ims.qti.editor.QTIEditorPackage; import org.olat.ims.qti.editor.beecom.objects.Item; import org.olat.ims.qti.editor.beecom.parser.ParserManager; import org.olat.ims.resources.IMSEntityResolver; - +import org.olat.modules.qpool.QuestionItem; +import org.olat.modules.qpool.QuestionPoolService; /** * * Initial date: 21.02.2013<br> @@ -50,30 +59,36 @@ public class QTI12PreviewController extends BasicController { private final Panel mainPanel; private ItemPreviewController previewCtrl; + private final QuestionPoolService qpoolService; - public QTI12PreviewController(UserRequest ureq, WindowControl wControl) { + public QTI12PreviewController(UserRequest ureq, WindowControl wControl, QuestionItem qitem) { super(ureq, wControl); - + qpoolService = CoreSpringFactory.getImpl(QuestionPoolService.class); mainPanel = new Panel("qti12preview"); - Item item = readItemXml(); - if(item != null) { - Translator translator = Util.createPackageTranslator(QTIEditorPackage.class, getLocale()); - previewCtrl = new ItemPreviewController(wControl, item, "/Users/srosse", translator); - listenTo(previewCtrl); - mainPanel.setContent(previewCtrl.getInitialComponent()); + VFSLeaf leaf = qpoolService.getRootFile(qitem); + if(leaf == null) { + //no data to preview + } else { + Item item = readItemXml(leaf); + if(item != null) { + Translator translator = Util.createPackageTranslator(QTIEditorPackage.class, getLocale()); + VFSContainer directory = qpoolService.getRootDirectory(qitem); + String mapperUrl = registerMapper(ureq, new QItemDirectoryMapper(directory)); + previewCtrl = new ItemPreviewController(wControl, item, mapperUrl, translator); + listenTo(previewCtrl); + mainPanel.setContent(previewCtrl.getInitialComponent()); + } } putInitialPanel(mainPanel); } - private Item readItemXml() { - - XMLParser xmlParser = new XMLParser(new IMSEntityResolver()); + private Item readItemXml(VFSLeaf leaf) { Document doc = null; try { - File itemXml = new File("/Users/srosse/Desktop/mchc_i_001.xml"); - InputStream is = new FileInputStream(itemXml); + InputStream is = leaf.getInputStream(); + XMLParser xmlParser = new XMLParser(new IMSEntityResolver()); doc = xmlParser.parse(is, false); Element item = (Element)doc.selectSingleNode("questestinterop/item"); @@ -81,7 +96,6 @@ public class QTI12PreviewController extends BasicController { Item qtiItem = (Item)parser.parse(item); is.close(); - return qtiItem; } catch (Exception e) { logError("", e); @@ -98,4 +112,23 @@ public class QTI12PreviewController extends BasicController { protected void event(UserRequest ureq, Component source, Event event) { // } + + private class QItemDirectoryMapper implements Mapper { + private final VFSContainer itemBaseContainer; + + private QItemDirectoryMapper(VFSContainer container) { + itemBaseContainer = container; + } + + public MediaResource handle(String relPath, HttpServletRequest request) { + VFSItem vfsItem = itemBaseContainer.resolve(relPath); + MediaResource mr; + if (vfsItem == null || !(vfsItem instanceof VFSLeaf)) { + mr = new NotFoundMediaResource(relPath); + } else { + mr = new VFSMediaResource((VFSLeaf) vfsItem); + } + return mr; + } + } } diff --git a/src/main/java/org/olat/ims/qti/QTIQuestionPoolServiceProvider.java b/src/main/java/org/olat/ims/qti/QTIQuestionPoolServiceProvider.java index f91d11c5b4a6f99ee8fbc7fecc2613ada9a9b140..807ea9e54b821ca8734cbd02817f63acbd69a976 100644 --- a/src/main/java/org/olat/ims/qti/QTIQuestionPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti/QTIQuestionPoolServiceProvider.java @@ -19,9 +19,13 @@ */ package org.olat.ims.qti; +import java.io.File; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.ims.qti.fileresource.ItemFileResourceValidator; import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionPoolSPI; @@ -33,20 +37,34 @@ import org.olat.modules.qpool.QuestionPoolSPI; */ public class QTIQuestionPoolServiceProvider implements QuestionPoolSPI { + @Override + public int getPriority() { + return 10; + } + @Override public String getFormat() { return QTIConstants.QTI_12_FORMAT; } + @Override + public boolean isCompatible(String filename, File file) { + return new ItemFileResourceValidator().validate(filename, file); + } + @Override + public boolean isCompatible(String filename, VFSLeaf file) { + return new ItemFileResourceValidator().validate(filename, file); + } + @Override public Controller getPreviewController(UserRequest ureq, WindowControl wControl, QuestionItem item) { - QTI12PreviewController previewCtrl = new QTI12PreviewController(ureq, wControl); + QTI12PreviewController previewCtrl = new QTI12PreviewController(ureq, wControl, item); return previewCtrl; } @Override public Controller getEditableController(UserRequest ureq, WindowControl wControl, QuestionItem item) { - QTI12PreviewController previewCtrl = new QTI12PreviewController(ureq, wControl); + QTI12PreviewController previewCtrl = new QTI12PreviewController(ureq, wControl, item); return previewCtrl; } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/fileresource/ItemFileResourceValidator.java b/src/main/java/org/olat/ims/qti/fileresource/ItemFileResourceValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..afd27c2a787f735ec7b932957930dc08dca43986 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/fileresource/ItemFileResourceValidator.java @@ -0,0 +1,189 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti.fileresource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.commons.io.IOUtils; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.ims.resources.IMSEntityResolver; +import org.olat.search.service.document.file.utils.ShieldInputStream; +import org.xml.sax.Attributes; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * Initial date: 27.02.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ItemFileResourceValidator { + + private static final OLog log = Tracing.createLoggerFor(ItemFileResourceValidator.class); + + public boolean validate(String filename, File file) { + InputStream in = null; + try { + in = new FileInputStream(file); + return validate(filename, in); + } catch (FileNotFoundException e) { + return false; + } finally { + IOUtils.closeQuietly(in); + } + } + + public boolean validate(String filename, VFSLeaf file) { + InputStream in = null; + try { + in = file.getInputStream(); + return validate(filename, in); + } catch (Exception e) { + return false; + } finally { + IOUtils.closeQuietly(in); + } + } + + public boolean validate(String filename, InputStream in) { + boolean valid = false; + + if(filename.toLowerCase().endsWith(".xml")) { + valid = validateXml(in); + IOUtils.closeQuietly(in); + } else if(filename.toLowerCase().endsWith(".zip")) { + ZipInputStream oZip = new ZipInputStream(in); + try { + ZipEntry oEntr = oZip.getNextEntry(); + while (oEntr != null) { + if (!oEntr.isDirectory()) { + if(validateXml(new ShieldInputStream(oZip))) { + valid = true; + } + } + oZip.closeEntry(); + oEntr = oZip.getNextEntry(); + } + } catch(Exception e) { + log.error("", e); + valid = false; + } finally { + IOUtils.closeQuietly(oZip); + IOUtils.closeQuietly(in); + } + } + + return valid; + } + + private boolean validateXml(InputStream in) { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setValidating(true); + factory.setNamespaceAware(true); + + SimpleErrorHandler errorHandler = new SimpleErrorHandler(); + ItemContentHandler contentHandler = new ItemContentHandler(); + + SAXParser parser = factory.newSAXParser(); + XMLReader reader = parser.getXMLReader(); + reader.setEntityResolver(new IMSEntityResolver()); + reader.setErrorHandler(errorHandler); + reader.setContentHandler(contentHandler); + reader.parse(new InputSource(in)); + return errorHandler.isValid() && contentHandler.isItem(); + } catch (ParserConfigurationException e) { + return false; + } catch (SAXException e) { + return false; + } catch (Exception e) { + return false; + } + } + + private static class SimpleErrorHandler implements ErrorHandler { + private int error = 0; + + public boolean isValid() { + return error == 0; + } + + @Override + public void warning(SAXParseException exception) throws SAXException { + // + } + + @Override + public void error(SAXParseException exception) throws SAXException { + error++; + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + error++; + } + } + + private static class ItemContentHandler extends DefaultHandler { + private boolean interop; + private boolean item; + + public boolean isItem() { + return item; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + if("questestinterop".equals(qName)) { + interop = true; + } else if("item".equals(localName)) { + if(interop) { + item = true; + } + } + } + + @Override + public void endElement(String uri, String localName, String qName) + throws SAXException { + if("questestinterop".equals(qName)) { + interop = false; + } + } + } +} diff --git a/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java b/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java index 637036c3e857880b11b59235aec51f6bcce8f369..8732dcc45d0e4472db6fffe56c394c8c41e5186a 100644 --- a/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java +++ b/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java @@ -154,7 +154,7 @@ public abstract class QTIHandler extends FileHandler implements RepositoryHandle File fUnzippedDir = FileResourceManager.getInstance().unzipFileResource(tempFr); File changeLogDir = new File(fUnzippedDir, "changelog"); if(changeLogDir.exists()) { - boolean changeLogDeleted = FileUtils.deleteDirsAndFiles(changeLogDir, true, true); + FileUtils.deleteDirsAndFiles(changeLogDir, true, true); } File targetZipFile = sourceFile; FileUtils.deleteDirsAndFiles(targetZipFile.getParentFile(), true, false); diff --git a/src/main/java/org/olat/ims/resources/dtd/ims_qtiasiv1p2.dtd b/src/main/java/org/olat/ims/resources/dtd/ims_qtiasiv1p2.dtd new file mode 100644 index 0000000000000000000000000000000000000000..3ba16b4cd936ce9028b3ce467b9770bb75b1cfe0 --- /dev/null +++ b/src/main/java/org/olat/ims/resources/dtd/ims_qtiasiv1p2.dtd @@ -0,0 +1 @@ +<?xml version='1.0' encoding='UTF-8' ?> <!--Generated by XML Authority--> <!-- ******************************************************* --> <!-- --> <!-- TITLE: ims_qtiasiv1p2.dtd --> <!-- TYPE: IMS Question and Test Interoperability --> <!-- Assessment, Section, Item structure and --> <!-- Objects-bank. --> <!-- --> <!-- REVISION HISTORY: --> <!-- Date Author --> <!-- ==== ====== --> <!-- 22nd Jan 2002 Colin Smythe --> <!-- --> <!-- This specification has been approved as a PDS release. --> <!-- --> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- ROOT DEFINITION --> <!-- ******************************************************* --> <!ELEMENT questestinterop (qticomment? , (objectbank | assessment | (section | item)+))> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- ENTITY DEFINITIONS --> <!-- ******************************************************* --> <!ENTITY % I_Testoperator " testoperator (EQ | NEQ | LT | LTE | GT | GTE ) #REQUIRED"> <!ENTITY % I_Pname " pname CDATA #REQUIRED"> <!ENTITY % I_Class " class CDATA 'Block'"> <!ENTITY % I_Mdoperator " mdoperator (EQ | NEQ | LT | LTE | GT | GTE ) #REQUIRED"> <!ENTITY % I_Mdname " mdname CDATA #REQUIRED"> <!ENTITY % I_Title " title CDATA #IMPLIED"> <!ENTITY % I_Label " label CDATA #IMPLIED"> <!ENTITY % I_Ident " ident CDATA #REQUIRED"> <!ENTITY % I_View " view (All | Administrator | AdminAuthority | Assessor | Author | Candidate | InvigilatorProctor | Psychometrician | Scorer | Tutor ) 'All'"> <!ENTITY % I_FeedbackSwitch " feedbackswitch (Yes | No ) 'Yes'"> <!ENTITY % I_HintSwitch " hintswitch (Yes | No ) 'Yes'"> <!ENTITY % I_SolutionSwitch " solutionswitch (Yes | No ) 'Yes'"> <!ENTITY % I_Rcardinality " rcardinality (Single | Multiple | Ordered ) 'Single'"> <!ENTITY % I_Rtiming " rtiming (Yes | No ) 'No'"> <!ENTITY % I_Uri " uri CDATA #IMPLIED"> <!ENTITY % I_X0 " x0 CDATA #IMPLIED"> <!ENTITY % I_Y0 " y0 CDATA #IMPLIED"> <!ENTITY % I_Height " height CDATA #IMPLIED"> <!ENTITY % I_Width " width CDATA #IMPLIED"> <!ENTITY % I_Embedded " embedded CDATA 'base64'"> <!ENTITY % I_LinkRefId " linkrefid CDATA #REQUIRED"> <!ENTITY % I_VarName " varname CDATA 'SCORE'"> <!ENTITY % I_RespIdent " respident CDATA #REQUIRED"> <!ENTITY % I_Continue " continue (Yes | No ) 'No'"> <!ENTITY % I_CharSet " charset CDATA 'ascii-us'"> <!ENTITY % I_ScoreModel " scoremodel CDATA #IMPLIED"> <!ENTITY % I_MinNumber " minnumber CDATA #IMPLIED"> <!ENTITY % I_MaxNumber " maxnumber CDATA #IMPLIED"> <!ENTITY % I_FeedbackStyle " feedbackstyle (Complete | Incremental | Multilevel | Proprietary ) 'Complete'"> <!ENTITY % I_Case " case (Yes | No ) 'No'"> <!ENTITY % I_EntityRef " entityref ENTITY #IMPLIED"> <!ENTITY % I_Index " index CDATA #IMPLIED"> <!ELEMENT qmd_computerscored (#PCDATA)> <!ELEMENT qmd_feedbackpermitted (#PCDATA)> <!ELEMENT qmd_hintspermitted (#PCDATA)> <!ELEMENT qmd_itemtype (#PCDATA)> <!ELEMENT qmd_maximumscore (#PCDATA)> <!ELEMENT qmd_renderingtype (#PCDATA)> <!ELEMENT qmd_responsetype (#PCDATA)> <!ELEMENT qmd_scoringpermitted (#PCDATA)> <!ELEMENT qmd_solutionspermitted (#PCDATA)> <!ELEMENT qmd_status (#PCDATA)> <!ELEMENT qmd_timedependence (#PCDATA)> <!ELEMENT qmd_timelimit (#PCDATA)> <!ELEMENT qmd_toolvendor (#PCDATA)> <!ELEMENT qmd_topic (#PCDATA)> <!ELEMENT qmd_material (#PCDATA)> <!ELEMENT qmd_typeofsolution (#PCDATA)> <!ELEMENT qmd_levelofdifficulty (#PCDATA)> <!ELEMENT qmd_weighting (#PCDATA)> <!ELEMENT qtimetadata (vocabulary? , qtimetadatafield+)> <!ELEMENT vocabulary (#PCDATA)> <!ATTLIST vocabulary %I_Uri; %I_EntityRef; vocab_type CDATA #IMPLIED > <!ELEMENT qtimetadatafield (fieldlabel , fieldentry)> <!ATTLIST qtimetadatafield xml:lang CDATA #IMPLIED > <!ELEMENT fieldlabel (#PCDATA)> <!ELEMENT fieldentry (#PCDATA)> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- COMMON OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT qticomment (#PCDATA)> <!ATTLIST qticomment xml:lang CDATA #IMPLIED> <!ELEMENT material (qticomment? , (mattext | matemtext | matimage | mataudio | matvideo | matapplet | matapplication | matref | matbreak | mat_extension)+ , altmaterial*)> <!ATTLIST material %I_Label; xml:lang CDATA #IMPLIED > <!ELEMENT mattext (#PCDATA)> <!ATTLIST mattext texttype CDATA 'text/plain' %I_Label; %I_CharSet; %I_Uri; xml:space (preserve | default ) 'default' xml:lang CDATA #IMPLIED %I_EntityRef; %I_Width; %I_Height; %I_Y0; %I_X0; > <!ELEMENT matemtext (#PCDATA)> <!ATTLIST matemtext texttype CDATA 'text/plain' %I_Label; %I_CharSet; %I_Uri; xml:space (preserve | default ) 'default' xml:lang CDATA #IMPLIED %I_EntityRef; %I_Width; %I_Height; %I_Y0; %I_X0; > <!ELEMENT matimage (#PCDATA)> <!ATTLIST matimage imagtype CDATA 'image/jpeg' %I_Label; %I_Height; %I_Uri; %I_Embedded; %I_Width; %I_Y0; %I_X0; %I_EntityRef; > <!ELEMENT mataudio (#PCDATA)> <!ATTLIST mataudio audiotype CDATA 'audio/base' %I_Label; %I_Uri; %I_Embedded; %I_EntityRef; > <!ELEMENT matvideo (#PCDATA)> <!ATTLIST matvideo videotype CDATA 'video/avi' %I_Label; %I_Uri; %I_Width; %I_Height; %I_Y0; %I_X0; %I_Embedded; %I_EntityRef; > <!ELEMENT matapplet (#PCDATA)> <!ATTLIST matapplet %I_Label; %I_Uri; %I_Y0; %I_Height; %I_Width; %I_X0; %I_Embedded; %I_EntityRef; > <!ELEMENT matapplication (#PCDATA)> <!ATTLIST matapplication apptype CDATA #IMPLIED %I_Label; %I_Uri; %I_Embedded; %I_EntityRef; > <!ELEMENT matbreak EMPTY> <!ELEMENT matref EMPTY> <!ATTLIST matref %I_LinkRefId; > <!ELEMENT material_ref EMPTY> <!ATTLIST material_ref %I_LinkRefId; > <!ELEMENT altmaterial (qticomment? , (mattext | matemtext | matimage | mataudio | matvideo | matapplet | matapplication | matref | matbreak | mat_extension)+)> <!ATTLIST altmaterial xml:lang CDATA #IMPLIED > <!ELEMENT decvar (#PCDATA)> <!ATTLIST decvar %I_VarName; vartype (Integer | String | Decimal | Scientific | Boolean | Enumerated | Set ) 'Integer' defaultval CDATA #IMPLIED minvalue CDATA #IMPLIED maxvalue CDATA #IMPLIED members CDATA #IMPLIED cutvalue CDATA #IMPLIED > <!ELEMENT setvar (#PCDATA)> <!ATTLIST setvar %I_VarName; action (Set | Add | Subtract | Multiply | Divide ) 'Set' > <!ELEMENT interpretvar (material | material_ref)> <!ATTLIST interpretvar %I_View; %I_VarName; > <!ELEMENT conditionvar (not | and | or | unanswered | other | varequal | varlt | varlte | vargt | vargte | varsubset | varinside | varsubstring | durequal | durlt | durlte | durgt | durgte | var_extension)+> <!ELEMENT not (and | or | not | unanswered | other | varequal | varlt | varlte | vargt | vargte | varsubset | varinside | varsubstring | durequal | durlt | durlte | durgt | durgte)> <!ELEMENT and (not | and | or | unanswered | other | varequal | varlt | varlte | vargt | vargte | varsubset | varinside | varsubstring | durequal | durlt | durlte | durgt | durgte)+> <!ELEMENT or (not | and | or | unanswered | other | varequal | varlt | varlte | vargt | vargte | varsubset | varinside | varsubstring | durequal | durlt | durlte | durgt | durgte)+> <!ELEMENT varequal (#PCDATA)> <!ATTLIST varequal %I_Case; %I_RespIdent; %I_Index; > <!ELEMENT varlt (#PCDATA)> <!ATTLIST varlt %I_RespIdent; %I_Index; > <!ELEMENT varlte (#PCDATA)> <!ATTLIST varlte %I_RespIdent; %I_Index; > <!ELEMENT vargt (#PCDATA)> <!ATTLIST vargt %I_RespIdent; %I_Index; > <!ELEMENT vargte (#PCDATA)> <!ATTLIST vargte %I_RespIdent; %I_Index; > <!ELEMENT varsubset (#PCDATA)> <!ATTLIST varsubset %I_RespIdent; setmatch (Exact | Partial ) 'Exact' %I_Index; > <!ELEMENT varinside (#PCDATA)> <!ATTLIST varinside areatype (Ellipse | Rectangle | Bounded ) #REQUIRED %I_RespIdent; %I_Index; > <!ELEMENT varsubstring (#PCDATA)> <!ATTLIST varsubstring %I_Index; %I_RespIdent; %I_Case; > <!ELEMENT durequal (#PCDATA)> <!ATTLIST durequal %I_Index; %I_RespIdent; > <!ELEMENT durlt (#PCDATA)> <!ATTLIST durlt %I_Index; %I_RespIdent; > <!ELEMENT durlte (#PCDATA)> <!ATTLIST durlte %I_Index; %I_RespIdent; > <!ELEMENT durgt (#PCDATA)> <!ATTLIST durgt %I_Index; %I_RespIdent; > <!ELEMENT durgte (#PCDATA)> <!ATTLIST durgte %I_Index; %I_RespIdent; > <!ELEMENT unanswered (#PCDATA)> <!ATTLIST unanswered %I_RespIdent; > <!ELEMENT other (#PCDATA)> <!ELEMENT duration (#PCDATA)> <!ELEMENT displayfeedback (#PCDATA)> <!ATTLIST displayfeedback feedbacktype (Response | Solution | Hint ) 'Response' %I_LinkRefId; > <!ELEMENT objectives (qticomment? , (material+ | flow_mat+))> <!ATTLIST objectives %I_View; > <!ELEMENT rubric (qticomment? , (material+ | flow_mat+))> <!ATTLIST rubric %I_View; > <!ELEMENT flow_mat (flow_mat | material | material_ref)+> <!ATTLIST flow_mat %I_Class; > <!ELEMENT presentation_material (qticomment? , flow_mat+)> <!ELEMENT reference (qticomment | material | mattext | matemtext | matimage | mataudio | matvideo | matapplet | matapplication | matbreak | mat_extension)+> <!ELEMENT selection_ordering (qticomment? , sequence_parameter? , selection* , order?)> <!ATTLIST selection_ordering sequence_type CDATA #IMPLIED > <!ELEMENT outcomes_processing (qticomment? , outcomes , objects_condition* , processing_parameter* , map_output* , outcomes_feedback_test*)> <!ATTLIST outcomes_processing %I_ScoreModel; > <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- EXTENSION DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT mat_extension ANY> <!ELEMENT var_extension ANY> <!ELEMENT response_extension ANY> <!ELEMENT render_extension ANY> <!ELEMENT assessproc_extension ANY> <!ELEMENT sectionproc_extension ANY> <!ELEMENT itemproc_extension ANY> <!ELEMENT respcond_extension ANY> <!ELEMENT selection_extension ANY> <!ELEMENT objectscond_extension (#PCDATA)> <!ELEMENT order_extension ANY> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- OBJECT-BANK OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT objectbank (qticomment? , qtimetadata* , (section | item)+)> <!ATTLIST objectbank %I_Ident; > <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- ASSESSMENT OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT assessment (qticomment? , duration? , qtimetadata* , objectives* , assessmentcontrol* , rubric* , presentation_material? , outcomes_processing* , assessproc_extension? , assessfeedback* , selection_ordering? , reference? , (sectionref | section)+)> <!ATTLIST assessment %I_Ident; %I_Title; xml:lang CDATA #IMPLIED > <!ELEMENT assessmentcontrol (qticomment?)> <!ATTLIST assessmentcontrol %I_HintSwitch; %I_SolutionSwitch; %I_View; %I_FeedbackSwitch; > <!ELEMENT assessfeedback (qticomment? , (material+ | flow_mat+))> <!ATTLIST assessfeedback %I_View; %I_Ident; %I_Title; > <!ELEMENT sectionref (#PCDATA)> <!ATTLIST sectionref %I_LinkRefId; > <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- SECTION OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT section (qticomment? , duration? , qtimetadata* , objectives* , sectioncontrol* , sectionprecondition* , sectionpostcondition* , rubric* , presentation_material? , outcomes_processing* , sectionproc_extension? , sectionfeedback* , selection_ordering? , reference? , (itemref | item | sectionref | section)*)> <!ATTLIST section %I_Ident; %I_Title; xml:lang CDATA #IMPLIED > <!ELEMENT sectionprecondition (#PCDATA)> <!ELEMENT sectionpostcondition (#PCDATA)> <!ELEMENT sectioncontrol (qticomment?)> <!ATTLIST sectioncontrol %I_FeedbackSwitch; %I_HintSwitch; %I_SolutionSwitch; %I_View; > <!ELEMENT itemref (#PCDATA)> <!ATTLIST itemref %I_LinkRefId; > <!ELEMENT sectionfeedback (qticomment? , (material+ | flow_mat+))> <!ATTLIST sectionfeedback %I_View; %I_Ident; %I_Title; > <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- ITEM OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT item (qticomment? , duration? , itemmetadata? , objectives* , itemcontrol* , itemprecondition* , itempostcondition* , (itemrubric | rubric)* , presentation? , resprocessing* , itemproc_extension? , itemfeedback* , reference?)> <!ATTLIST item maxattempts CDATA #IMPLIED %I_Label; %I_Ident; %I_Title; xml:lang CDATA #IMPLIED > <!ELEMENT itemmetadata (qtimetadata* , qmd_computerscored? , qmd_feedbackpermitted? , qmd_hintspermitted? , qmd_itemtype? , qmd_levelofdifficulty? , qmd_maximumscore? , qmd_renderingtype* , qmd_responsetype* , qmd_scoringpermitted? , qmd_solutionspermitted? , qmd_status? , qmd_timedependence? , qmd_timelimit? , qmd_toolvendor? , qmd_topic? , qmd_weighting? , qmd_material* , qmd_typeofsolution?)> <!ELEMENT itemcontrol (qticomment?)> <!ATTLIST itemcontrol %I_FeedbackSwitch; %I_HintSwitch; %I_SolutionSwitch; %I_View; > <!ELEMENT itemprecondition (#PCDATA)> <!ELEMENT itempostcondition (#PCDATA)> <!ELEMENT itemrubric (material)> <!ATTLIST itemrubric %I_View; > <!ELEMENT presentation (qticomment? , (flow | (material | response_lid | response_xy | response_str | response_num | response_grp | response_extension)+))> <!ATTLIST presentation %I_Label; xml:lang CDATA #IMPLIED %I_Y0; %I_X0; %I_Width; %I_Height; > <!ELEMENT flow (flow | material | material_ref | response_lid | response_xy | response_str | response_num | response_grp | response_extension)+> <!ATTLIST flow %I_Class; > <!ELEMENT response_lid ((material | material_ref)? , (render_choice | render_hotspot | render_slider | render_fib | render_extension) , (material | material_ref)?)> <!ATTLIST response_lid %I_Rcardinality; %I_Rtiming; %I_Ident; > <!ELEMENT response_xy ((material | material_ref)? , (render_choice | render_hotspot | render_slider | render_fib | render_extension) , (material | material_ref)?)> <!ATTLIST response_xy %I_Rcardinality; %I_Rtiming; %I_Ident; > <!ELEMENT response_str ((material | material_ref)? , (render_choice | render_hotspot | render_slider | render_fib | render_extension) , (material | material_ref)?)> <!ATTLIST response_str %I_Rcardinality; %I_Ident; %I_Rtiming; > <!ELEMENT response_num ((material | material_ref)? , (render_choice | render_hotspot | render_slider | render_fib | render_extension) , (material | material_ref)?)> <!ATTLIST response_num numtype (Integer | Decimal | Scientific ) 'Integer' %I_Rcardinality; %I_Ident; %I_Rtiming; > <!ELEMENT response_grp ((material | material_ref)? , (render_choice | render_hotspot | render_slider | render_fib | render_extension) , (material | material_ref)?)> <!ATTLIST response_grp %I_Rcardinality; %I_Ident; %I_Rtiming; > <!ELEMENT response_label (#PCDATA | qticomment | material | material_ref | flow_mat)*> <!ATTLIST response_label rshuffle (Yes | No ) 'Yes' rarea (Ellipse | Rectangle | Bounded ) 'Ellipse' rrange (Exact | Range ) 'Exact' labelrefid CDATA #IMPLIED %I_Ident; match_group CDATA #IMPLIED match_max CDATA #IMPLIED > <!ELEMENT flow_label (flow_label | response_label)+> <!ATTLIST flow_label %I_Class; > <!ELEMENT response_na ANY> <!ELEMENT render_choice ((material | material_ref | response_label | flow_label)* , response_na?)> <!ATTLIST render_choice shuffle (Yes | No ) 'No' %I_MinNumber; %I_MaxNumber; > <!ELEMENT render_hotspot ((material | material_ref | response_label | flow_label)* , response_na?)> <!ATTLIST render_hotspot %I_MaxNumber; %I_MinNumber; showdraw (Yes | No ) 'No' > <!ELEMENT render_slider ((material | material_ref | response_label | flow_label)* , response_na?)> <!ATTLIST render_slider orientation (Horizontal | Vertical ) 'Horizontal' lowerbound CDATA #REQUIRED upperbound CDATA #REQUIRED step CDATA #IMPLIED startval CDATA #IMPLIED steplabel (Yes | No ) 'No' %I_MaxNumber; %I_MinNumber; > <!ELEMENT render_fib ((material | material_ref | response_label | flow_label)* , response_na?)> <!ATTLIST render_fib encoding CDATA 'UTF_8' fibtype (String | Integer | Decimal | Scientific ) 'String' rows CDATA #IMPLIED maxchars CDATA #IMPLIED prompt (Box | Dashline | Asterisk | Underline ) #IMPLIED columns CDATA #IMPLIED %I_CharSet; %I_MaxNumber; %I_MinNumber; > <!ELEMENT resprocessing (qticomment? , outcomes , (respcondition | itemproc_extension)+)> <!ATTLIST resprocessing %I_ScoreModel; > <!ELEMENT outcomes (qticomment? , (decvar , interpretvar*)+)> <!ELEMENT respcondition (qticomment? , conditionvar , setvar* , displayfeedback* , respcond_extension?)> <!ATTLIST respcondition %I_Continue; %I_Title; > <!ELEMENT itemfeedback ((flow_mat | material) | solution | hint)+> <!ATTLIST itemfeedback %I_View; %I_Ident; %I_Title; > <!ELEMENT solution (qticomment? , solutionmaterial+)> <!ATTLIST solution %I_FeedbackStyle; > <!ELEMENT solutionmaterial (material+ | flow_mat+)> <!ELEMENT hint (qticomment? , hintmaterial+)> <!ATTLIST hint %I_FeedbackStyle; > <!ELEMENT hintmaterial (material+ | flow_mat+)> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- SELECTION AND ORDERING OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT selection (sourcebank_ref? , selection_number? , selection_metadata? , (and_selection | or_selection | not_selection | selection_extension)?)> <!ELEMENT order (order_extension?)> <!ATTLIST order order_type CDATA #REQUIRED > <!ELEMENT selection_number (#PCDATA)> <!ELEMENT selection_metadata (#PCDATA)> <!ATTLIST selection_metadata %I_Mdname; %I_Mdoperator;> <!ELEMENT sequence_parameter (#PCDATA)> <!ELEMENT sourcebank_ref (#PCDATA)> <!ATTLIST sequence_parameter %I_Pname;> <!ELEMENT and_selection (selection_metadata | and_selection | or_selection | not_selection)+> <!ELEMENT or_selection (selection_metadata | and_selection | or_selection | not_selection)+> <!ELEMENT not_selection (selection_metadata | and_selection | or_selection | not_selection)> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- OUTCOMES PREOCESSING OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT objects_condition (qticomment? , (outcomes_metadata | and_objects | or_objects | not_objects)? , objects_parameter* , map_input* , objectscond_extension?)> <!ELEMENT map_output (#PCDATA)> <!ATTLIST map_output %I_VarName;> <!ELEMENT map_input (#PCDATA)> <!ATTLIST map_input %I_VarName;> <!ELEMENT outcomes_feedback_test (test_variable , displayfeedback+)> <!ATTLIST outcomes_feedback_test %I_Title; > <!ELEMENT outcomes_metadata (#PCDATA)> <!ATTLIST outcomes_metadata %I_Mdname; %I_Mdoperator;> <!ELEMENT and_objects (outcomes_metadata | and_objects | or_objects | not_objects)+> <!ELEMENT or_objects (outcomes_metadata | and_objects | or_objects | not_objects)+> <!ELEMENT not_objects (outcomes_metadata | and_objects | or_objects | not_objects)> <!ELEMENT test_variable (variable_test | and_test | or_test | not_test)> <!ELEMENT processing_parameter (#PCDATA)> <!ATTLIST processing_parameter %I_Pname;> <!ELEMENT and_test (variable_test | and_test | or_test | not_test)+> <!ELEMENT or_test (variable_test | and_test | or_test | not_test)+> <!ELEMENT not_test (variable_test | and_test | or_test | not_test)> <!ELEMENT variable_test (#PCDATA)> <!ATTLIST variable_test %I_VarName; %I_Testoperator;> <!ELEMENT objects_parameter (#PCDATA)> <!ATTLIST objects_parameter %I_Pname;> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/resources/dtd/ims_qtiresv1p2.dtd b/src/main/java/org/olat/ims/resources/dtd/ims_qtiresv1p2.dtd new file mode 100644 index 0000000000000000000000000000000000000000..3c6fa40423dadae02e5aa8f02361c1f61f67b837 --- /dev/null +++ b/src/main/java/org/olat/ims/resources/dtd/ims_qtiresv1p2.dtd @@ -0,0 +1 @@ +<?xml version='1.0' encoding='UTF-8' ?> <!--Generated by XML Authority--> <!-- ******************************************************* --> <!-- --> <!-- TITLE: ims_qtiresv1p2.dtd --> <!-- TYPE: IMS Question and Test Interoperability --> <!-- Results Reporting --> <!-- --> <!-- REVISION HISTORY: --> <!-- Date Author --> <!-- ==== ====== --> <!-- 922th Jan 2002 Colin Smythe --> <!-- --> <!-- This specification has been approved as a Final release. --> <!-- --> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- ENTITY DEFINITIONS --> <!-- ******************************************************* --> <!ENTITY % I_Vartype " vartype (Integer | Decimal | Scientific | String | Boolean | Set | Enumerated ) 'Integer'"> <!ENTITY % I_Varname " varname CDATA 'SCORE'"> <!ENTITY % I_Identref " ident_ref CDATA #IMPLIED"> <!ENTITY % I_Evaltitle " asi_title CDATA #IMPLIED"> <!ENTITY % I_Presented " presented (Yes | No | Unknown ) 'Yes'"> <!ENTITY % I_Status " status (Valid | Noanswer | Error ) 'Valid'"> <!ENTITY % I_Uri " uri CDATA #IMPLIED"> <!ENTITY % I_Entityref " entityref ENTITY #IMPLIED"> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- ROOT DEFINITION --> <!-- ******************************************************* --> <!ELEMENT qti_result_report (qti_comment? , result+)> <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!ELEMENT qti_comment (#PCDATA)> <!ATTLIST qti_comment xml:lang CDATA #IMPLIED > <!-- ******************************************************* --> <!-- CORE OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT result (qti_comment? , context , (summary_result | assessment_result | section_result | item_result) , extension_result?)> <!ELEMENT context (qti_comment? , name? , generic_identifier* , date* , extension_context?)> <!ELEMENT summary_result (qti_comment? , type_label? , generic_identifier* , date* , status? , duration? , score? , grade? , outcomes? , extension_summary_result?)> <!ATTLIST summary_result %I_Evaltitle; > <!ELEMENT assessment_result (qti_comment? , asi_metadata* , asi_description? , date* , duration? , objective* , control? , outcomes? , feedback_displayed* , num_items? , num_sections? , num_items_presented? , num_items_attempted? , num_sections_presented? , section_result* , extension_assessment_result?)> <!ATTLIST assessment_result %I_Identref; %I_Evaltitle; > <!ELEMENT section_result (qti_comment? , asi_metadata* , asi_description? , date* , duration? , objective* , control? , outcomes? , feedback_displayed* , num_items? , num_sections? , num_items_presented? , num_items_attempted? , num_sections_presented? , (section_result | item_result)* , extension_section_result?)> <!ATTLIST section_result %I_Evaltitle; %I_Identref; %I_Presented; > <!ELEMENT item_result (qti_comment? , asi_metadata* , asi_description? , date* , duration? , objective* , control? , response* , outcomes? , feedback_displayed* , extension_item_result?)> <!ATTLIST item_result %I_Evaltitle; %I_Identref; %I_Presented; > <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- ******************************************************* --> <!-- SUPPORT OBJECT DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT name (#PCDATA)> <!ELEMENT generic_identifier (type_label? , identifier_string)> <!ELEMENT date (type_label? , datetime)> <!ELEMENT score_min (#PCDATA)> <!ELEMENT score_max (#PCDATA)> <!ELEMENT score_value (#PCDATA)> <!ELEMENT grade (grade_value , grade_cut? , extension_grade?)> <!ATTLIST grade %I_Varname; %I_Status; members CDATA #IMPLIED > <!ELEMENT duration (#PCDATA)> <!ELEMENT type_label (#PCDATA)> <!ATTLIST type_label source CDATA #IMPLIED > <!ELEMENT status (type_label? , datetime? , status_value)> <!ELEMENT objective (#PCDATA)> <!ATTLIST objective view (All | Administrator | AdminAuthority | Assessor | Author | Candidate | InvigilatorProctor | Psychometrician | Scorer | Tutor ) 'All' %I_Uri; %I_Entityref; > <!ELEMENT control (#PCDATA)> <!ATTLIST control hint_switch (Yes | No ) 'No' solution_switch (Yes | No ) 'No' feedback_switch (Yes | No ) 'No' > <!ELEMENT asi_metadata (vocabulary? , asi_metadatafield+)> <!ELEMENT response (qti_comment? , response_form? , num_attempts? , response_value* , extension_response?)> <!ATTLIST response %I_Identref; > <!ELEMENT response_value (#PCDATA)> <!ATTLIST response_value %I_Uri; %I_Entityref; response_status (Null | Valid | NA | Invalid ) 'Valid' response_time CDATA #IMPLIED > <!ELEMENT feedback_displayed (#PCDATA)> <!ATTLIST feedback_displayed %I_Uri; %I_Entityref; %I_Identref; %I_Evaltitle; > <!ELEMENT num_items (#PCDATA)> <!ELEMENT num_sections (#PCDATA)> <!ELEMENT num_sections_presented (#PCDATA)> <!ELEMENT num_items_attempted (#PCDATA)> <!ELEMENT num_items_presented (#PCDATA)> <!ELEMENT response_form (correct_response* , extension_responseform?)> <!ATTLIST response_form cardinality (single | multiple | ordered | extension ) #IMPLIED render_type (choice | hotspot | slider | fib | extension ) #IMPLIED timing (Yes | No ) #IMPLIED response_type (lid | xy | str | num | grp | extension ) #IMPLIED > <!ELEMENT correct_response (#PCDATA)> <!ELEMENT outcomes (status? , (score | grade)*)> <!ELEMENT score (score_value , score_interpretation? , score_min? , score_max? , score_normalized? , score_average? , score_std_error? , score_reliability? , score_cut? , extension_score?)> <!ATTLIST score %I_Varname; %I_Vartype; %I_Status; > <!ELEMENT score_cut (#PCDATA)> <!ELEMENT grade_value (#PCDATA)> <!ELEMENT grade_cut (#PCDATA)> <!ELEMENT score_average (#PCDATA)> <!ELEMENT score_std_error (#PCDATA)> <!ELEMENT num_attempts (#PCDATA)> <!ELEMENT asi_description (#PCDATA)> <!ATTLIST asi_description %I_Uri; %I_Entityref; > <!ELEMENT identifier_string (#PCDATA)> <!ELEMENT datetime (#PCDATA)> <!ELEMENT status_value (#PCDATA)> <!ELEMENT vocabulary (#PCDATA)> <!ATTLIST vocabulary %I_Uri; %I_Entityref; vocab_type CDATA #IMPLIED > <!ELEMENT asi_metadatafield (field_name , field_value)> <!ATTLIST asi_metadatafield xml:lang CDATA #IMPLIED > <!ELEMENT field_name (#PCDATA)> <!ELEMENT field_value (#PCDATA)> <!ELEMENT score_reliability (#PCDATA)> <!ELEMENT score_interpretation (#PCDATA)> <!ATTLIST score_interpretation %I_Uri; %I_Entityref; > <!-- ******************************************************* --> <!-- EXTENSION DEFINITIONS --> <!-- ******************************************************* --> <!ELEMENT extension_context ANY> <!ELEMENT extension_assessment_result ANY> <!ELEMENT extension_section_result ANY> <!ELEMENT extension_score ANY> <!ELEMENT extension_summary_result ANY> <!ELEMENT extension_item_result ANY> <!ELEMENT extension_response ANY> <!ELEMENT extension_responseform ANY> <!ELEMENT extension_grade ANY> <!ELEMENT extension_result ANY> <!ELEMENT score_normalized (#PCDATA)> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/qpool/QuestionItem.java b/src/main/java/org/olat/modules/qpool/QuestionItem.java index 3fe974ff063421633122a186978129ea97afa2ed..ed6057940ed0c17e2f2d70069d647777f7912b57 100644 --- a/src/main/java/org/olat/modules/qpool/QuestionItem.java +++ b/src/main/java/org/olat/modules/qpool/QuestionItem.java @@ -37,6 +37,7 @@ public interface QuestionItem extends QuestionItemShort { public String getLevel(); + public String getDirectory(); public String getEditor(); diff --git a/src/main/java/org/olat/modules/qpool/QuestionItemShort.java b/src/main/java/org/olat/modules/qpool/QuestionItemShort.java index fe8aad00a2133d8d8a39c2e3741e6f07de343c1d..b53b5c8090a38058a698c2a8dfd99dc4289f05f0 100644 --- a/src/main/java/org/olat/modules/qpool/QuestionItemShort.java +++ b/src/main/java/org/olat/modules/qpool/QuestionItemShort.java @@ -34,6 +34,8 @@ public interface QuestionItemShort extends OLATResourceable { public Long getKey(); + public String getUuid(); + public Date getCreationDate(); public Date getLastModified(); diff --git a/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java b/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java index 15b9e5e2278f14c2c7434ad3095f140a2c514536..9f4485b891f3423bb8f3d71017f3d4af95bbf113 100644 --- a/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java +++ b/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java @@ -19,12 +19,18 @@ */ package org.olat.modules.qpool; +import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.configuration.AbstractOLATModule; import org.olat.core.configuration.ConfigOnOff; import org.olat.core.configuration.PersistedProperties; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.modules.qpool.impl.PdfQuestionPoolServiceProvider; +import org.olat.modules.qpool.impl.TextQuestionPoolServiceProvider; /** * @@ -36,9 +42,12 @@ public class QuestionPoolModule extends AbstractOLATModule implements ConfigOnOf private final List<QuestionPoolSPI> questionPoolProviders = new ArrayList<QuestionPoolSPI>(); + private VFSContainer rootContainer; + @Override public void init() { - // + addQuestionPoolProvider(new TextQuestionPoolServiceProvider()); + addQuestionPoolProvider(new PdfQuestionPoolServiceProvider()); } @Override @@ -60,14 +69,25 @@ public class QuestionPoolModule extends AbstractOLATModule implements ConfigOnOf public boolean isEnabled() { return true; } + + public VFSContainer getRootContainer() { + if(rootContainer == null) { + rootContainer = new OlatRootFolderImpl(File.separator + "qpool", null); + } + return rootContainer; + } public List<QuestionPoolSPI> getQuestionPoolProviders() { - return new ArrayList<QuestionPoolSPI>(questionPoolProviders); + List<QuestionPoolSPI> providers = new ArrayList<QuestionPoolSPI>(questionPoolProviders); + Collections.sort(providers, new QuestionPoolSPIComparator()); + return providers; } public void setQuestionPoolProviders(List<QuestionPoolSPI> providers) { if(providers != null) { - questionPoolProviders.addAll(providers); + for(QuestionPoolSPI provider:providers) { + addQuestionPoolProvider(provider); + } } } @@ -81,8 +101,19 @@ public class QuestionPoolModule extends AbstractOLATModule implements ConfigOnOf } public void addQuestionPoolProvider(QuestionPoolSPI provider) { - questionPoolProviders.add(provider); + int currentIndex = -1; + for(int i=questionPoolProviders.size(); i-->0; ) { + QuestionPoolSPI currentProvider = questionPoolProviders.get(i); + if(provider.getFormat() != null && + provider.getFormat().equals(currentProvider.getFormat())) { + currentIndex = i; + } + } + + if(currentIndex >= 0) { + questionPoolProviders.set(currentIndex, provider); + } else { + questionPoolProviders.add(provider); + } } - - } diff --git a/src/main/java/org/olat/modules/qpool/QuestionPoolSPI.java b/src/main/java/org/olat/modules/qpool/QuestionPoolSPI.java index 82099f5bfe9144f5a6a9cedf7bb396c14e451cbd..877e85b06679c44ea8b7609c0ae63f59c3e70310 100644 --- a/src/main/java/org/olat/modules/qpool/QuestionPoolSPI.java +++ b/src/main/java/org/olat/modules/qpool/QuestionPoolSPI.java @@ -19,9 +19,12 @@ */ package org.olat.modules.qpool; +import java.io.File; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.vfs.VFSLeaf; /** * @@ -31,8 +34,14 @@ import org.olat.core.gui.control.WindowControl; */ public interface QuestionPoolSPI { + public int getPriority(); + public String getFormat(); + public boolean isCompatible(String filename, File file); + + public boolean isCompatible(String filename, VFSLeaf file); + public Controller getPreviewController(UserRequest ureq, WindowControl wControl, QuestionItem item); public Controller getEditableController(UserRequest ureq, WindowControl wControl, QuestionItem item); diff --git a/src/main/java/org/olat/modules/qpool/QuestionPoolSPIComparator.java b/src/main/java/org/olat/modules/qpool/QuestionPoolSPIComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..69f7f0f589077a7839f82c360c724e7f9acdd9b0 --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/QuestionPoolSPIComparator.java @@ -0,0 +1,38 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool; + +import java.util.Comparator; + +/** + * Sort by priority + * Initial date: 27.02.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QuestionPoolSPIComparator implements Comparator<QuestionPoolSPI> { + + @Override + public int compare(QuestionPoolSPI o1, QuestionPoolSPI o2) { + int p1 = o1.getPriority(); + int p2 = o2.getPriority(); + return p1 < p2 ? 1 : (p1==p2 ? 0 : -1); + } +} diff --git a/src/main/java/org/olat/modules/qpool/QuestionPoolService.java b/src/main/java/org/olat/modules/qpool/QuestionPoolService.java index 45a4c105d8c9ae4c465953ed76c57a5107aaa589..43819bf5310e5df4455643ad4cefa58405a41cf0 100644 --- a/src/main/java/org/olat/modules/qpool/QuestionPoolService.java +++ b/src/main/java/org/olat/modules/qpool/QuestionPoolService.java @@ -19,10 +19,13 @@ */ package org.olat.modules.qpool; +import java.io.File; import java.util.List; import org.olat.core.commons.persistence.SortKey; import org.olat.core.id.Identity; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSLeaf; import org.olat.group.BusinessGroup; import org.olat.resource.OLATResource; @@ -40,6 +43,12 @@ public interface QuestionPoolService { public void addAuthors(List<Identity> authors, List<QuestionItem> items); + public QuestionItem importItem(Identity owner, String filename, File file); + + public VFSLeaf getRootFile(QuestionItem item); + + public VFSContainer getRootDirectory(QuestionItem item); + public int countItems(Identity author); diff --git a/src/main/java/org/olat/modules/qpool/impl/PdfQuestionPoolServiceProvider.java b/src/main/java/org/olat/modules/qpool/impl/PdfQuestionPoolServiceProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..87e64c5631b341760063f11d25ab7604fd8e2191 --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/impl/PdfQuestionPoolServiceProvider.java @@ -0,0 +1,76 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.impl; + +import java.io.File; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.modules.qpool.QuestionItem; +import org.olat.modules.qpool.QuestionPoolSPI; + +/** + * + * Initial date: 26.02.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class PdfQuestionPoolServiceProvider implements QuestionPoolSPI { + + public static final String PDF_FORMAT = "pdf"; + + @Override + public int getPriority() { + return 0; + } + + @Override + public String getFormat() { + return PDF_FORMAT; + } + + @Override + public boolean isCompatible(String filename, File file) { + return !filename.toLowerCase().endsWith(".xml") + && !filename.toLowerCase().endsWith(".txt") + && !filename.toLowerCase().endsWith(".zip"); + } + + @Override + public boolean isCompatible(String filename, VFSLeaf file) { + return isCompatible(filename, (File)null); + } + + @Override + public Controller getPreviewController(UserRequest ureq, WindowControl wControl, QuestionItem item) { + return null; + } + + @Override + public Controller getEditableController(UserRequest ureq, WindowControl wControl, QuestionItem item) { + return null; + } + + + + +} diff --git a/src/main/java/org/olat/modules/qpool/impl/TextQuestionPoolServiceProvider.java b/src/main/java/org/olat/modules/qpool/impl/TextQuestionPoolServiceProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..efd6b386755711602bef4b5b939accca57dff2d0 --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/impl/TextQuestionPoolServiceProvider.java @@ -0,0 +1,70 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.impl; + +import java.io.File; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.modules.qpool.QuestionItem; +import org.olat.modules.qpool.QuestionPoolSPI; + +/** + * + * Initial date: 26.02.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class TextQuestionPoolServiceProvider implements QuestionPoolSPI { + + public static final String TXT_FORMAT = "txt"; + + @Override + public int getPriority() { + return 1; + } + + @Override + public String getFormat() { + return TXT_FORMAT; + } + + @Override + public boolean isCompatible(String filename, File file) { + return filename.toLowerCase().endsWith(".txt"); + } + + @Override + public boolean isCompatible(String filename, VFSLeaf file) { + return isCompatible(filename, (File)null); + } + + @Override + public Controller getPreviewController(UserRequest ureq, WindowControl wControl, QuestionItem item) { + return null; + } + + @Override + public Controller getEditableController(UserRequest ureq, WindowControl wControl, QuestionItem item) { + return null; + } +} diff --git a/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java b/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java index a1be17f554d54fb536e9bf33581b470657618504..685691003bac9ad421f533cb6f92e40e5eb16519 100644 --- a/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java +++ b/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java @@ -94,7 +94,7 @@ public class NullPoolService implements ApplicationListener<ContextRefreshedEven for(int i=0; i<200; i++) { long randomIndex = Math.round(Math.random() * (fields.size() - 1)); StudyField field = fields.get((int)randomIndex); - QuestionItem item = questionItemDao.create(null, "NGC " + i, QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), field, randomType()); + QuestionItem item = questionItemDao.create(null, "NGC " + i, QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), field, null, randomType()); poolDao.addItemToPool(item, pools.get(0)); } } diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java b/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java index 2375e92a5337836ea12e03f78846f26afbeb282c..935de6092c2a10f7412f3ee7848a61c169dd0cda 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java @@ -22,6 +22,7 @@ package org.olat.modules.qpool.manager; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.UUID; import javax.persistence.EntityManager; import javax.persistence.LockModeType; @@ -59,18 +60,33 @@ public class QuestionItemDAO { @Autowired private BaseSecurity securityManager; + public QuestionItem create(Identity owner, String subject, String format, String language, - StudyField field, QuestionType type) { + StudyField field, String rootFilename, QuestionType type) { + + String uuid = UUID.randomUUID().toString(); + return create(owner, subject, format, language, field, uuid, rootFilename, type) ; + } + + public QuestionItem create(Identity owner, String subject, String format, String language, + StudyField field, String uuid, String rootFilename, QuestionType type) { QuestionItemImpl item = new QuestionItemImpl(); + + + item.setUuid(uuid); item.setCreationDate(new Date()); item.setLastModified(new Date()); item.setSubject(subject); item.setStatus(QuestionStatus.inWork.name()); item.setUsage(0); - item.setType(type.name()); + if(type != null) { + item.setType(type.name()); + } item.setFormat(format); item.setLanguage(language); item.setStudyField(field); + item.setDirectory(generateDir(uuid)); + item.setRootFilename(rootFilename); SecurityGroup authorGroup = securityManager.createAndPersistSecurityGroup(); item.setAuthorGroup(authorGroup); dbInstance.getCurrentEntityManager().persist(item); @@ -80,6 +96,21 @@ public class QuestionItemDAO { return item; } + public String generateDir(String uuid) { + String cleanUuid = uuid.replace("-", ""); + String firstToken = cleanUuid.substring(0, 2); + String secondToken = cleanUuid.substring(2, 4); + String thirdToken = cleanUuid.substring(4, 6); + String forthToken = cleanUuid.substring(6, 8); + StringBuilder sb = new StringBuilder(); + sb.append(firstToken).append("/") + .append(secondToken).append("/") + .append(thirdToken).append("/") + .append(forthToken).append("/"); + return sb.toString(); + } + + public void addAuthors(List<Identity> authors, QuestionItem item) { QuestionItemImpl lockedItem = loadForUpdate(item.getKey()); SecurityGroup secGroup = lockedItem.getAuthorGroup(); @@ -149,11 +180,11 @@ public class QuestionItemDAO { } } - public QuestionItem loadById(Long key) { + public QuestionItemImpl loadById(Long key) { StringBuilder sb = new StringBuilder(); sb.append("select item from questionitem item where item.key=:key"); - List<QuestionItem> items = dbInstance.getCurrentEntityManager() - .createQuery(sb.toString(), QuestionItem.class) + List<QuestionItemImpl> items = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), QuestionItemImpl.class) .setParameter("key", key) .getResultList(); diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java index c7cb6c619c230003caeb3babe1b900c64648aac6..aea658ec407965e54a95e440d984b975c86a91cf 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java @@ -19,16 +19,32 @@ */ package org.olat.modules.qpool.manager; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.UUID; +import org.apache.commons.io.IOUtils; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.SortKey; import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.ZipUtil; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; import org.olat.group.BusinessGroup; import org.olat.modules.qpool.Pool; import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionItemCollection; +import org.olat.modules.qpool.QuestionPoolModule; +import org.olat.modules.qpool.QuestionPoolSPI; import org.olat.modules.qpool.QuestionPoolService; import org.olat.modules.qpool.model.QuestionItemImpl; import org.olat.resource.OLATResource; @@ -44,6 +60,8 @@ import org.springframework.stereotype.Service; @Service("qpoolService") public class QuestionPoolServiceImpl implements QuestionPoolService { + private static final OLog log = Tracing.createLoggerFor(QuestionPoolServiceImpl.class); + @Autowired private DB dbInstance; @Autowired @@ -54,6 +72,9 @@ public class QuestionPoolServiceImpl implements QuestionPoolService { private StudyFieldDAO studyFieldDao; @Autowired private QuestionItemDAO questionItemDao; + @Autowired + private QuestionPoolModule qpoolModule; + @Override public String getMateriliazedPathOfStudyFields(QuestionItem item) { @@ -84,6 +105,113 @@ public class QuestionPoolServiceImpl implements QuestionPoolService { } } + @Override + public QuestionItem importItem(Identity owner, String filename, File file) { + QuestionItem importedItem = null; + List<QuestionPoolSPI> providers = qpoolModule.getQuestionPoolProviders(); + for(QuestionPoolSPI provider:providers) { + if(provider.isCompatible(filename, file)) { + importedItem = importItem(owner, filename, file, provider); + } + } + return importedItem; + } + + @Override + public VFSLeaf getRootFile(QuestionItem item) { + QuestionItemImpl reloadedItem = questionItemDao.loadById(item.getKey()); + VFSContainer root = qpoolModule.getRootContainer(); + VFSItem dir = root.resolve(reloadedItem.getDirectory()); + if(dir instanceof VFSContainer) { + VFSContainer itemContainer = (VFSContainer)dir; + VFSItem rootLeaf = itemContainer.resolve(reloadedItem.getRootFilename()); + if(rootLeaf instanceof VFSLeaf) { + return (VFSLeaf)rootLeaf; + } + } + return null; + } + + @Override + public VFSContainer getRootDirectory(QuestionItem item) { + QuestionItemImpl reloadedItem = questionItemDao.loadById(item.getKey()); + VFSContainer root = qpoolModule.getRootContainer(); + VFSItem dir = root.resolve(reloadedItem.getDirectory()); + if(dir instanceof VFSContainer) { + return (VFSContainer)dir; + } + return null; + } + + private QuestionItem importItem(Identity owner, String filename, File file, QuestionPoolSPI provider) { + String uuid = UUID.randomUUID().toString(); + VFSContainer root = qpoolModule.getRootContainer(); + VFSContainer itemDir = getDirectory(root, uuid); + + String rootFilename = filename; + if(filename.toLowerCase().endsWith(".zip")) { + ZipUtil.unzipStrict(file, itemDir); + rootFilename = searchRootFilename("", itemDir, provider); + } else { + //copy + VFSLeaf leaf = itemDir.createChildLeaf(filename); + OutputStream out = leaf.getOutputStream(false); + InputStream in = null; + try { + in = new FileInputStream(file); + IOUtils.copy(in, out); + } catch (FileNotFoundException e) { + log.error("", e); + } catch (IOException e) { + log.error("", e); + } finally { + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(out); + } + } + + return questionItemDao.create(owner, filename, provider.getFormat(), "de", null, uuid, rootFilename, null); + } + + private String searchRootFilename(String path, VFSContainer dir, QuestionPoolSPI provider) { + for(VFSItem item:dir.getItems()) { + if(item instanceof VFSContainer) { + String root = searchRootFilename(path + "/" + item.getName(), (VFSContainer)item, provider); + if(root != null) { + return root; + } + } else if(item instanceof VFSLeaf) { + if(provider.isCompatible(item.getName(), (VFSLeaf)item)) { + return path + item.getName(); + } + } + } + return null; + } + + private VFSContainer getDirectory(VFSContainer rootContainer, String uuid) { + String cleanUuid = uuid.replace("-", ""); + String firstToken = cleanUuid.substring(0, 2); + VFSContainer firstContainer = getNextDirectory(rootContainer, firstToken); + String secondToken = cleanUuid.substring(2, 4); + VFSContainer secondContainer = getNextDirectory(firstContainer, secondToken); + String thirdToken = cleanUuid.substring(4, 6); + VFSContainer thridContainer = getNextDirectory(secondContainer, thirdToken); + String forthToken = cleanUuid.substring(6, 8); + return getNextDirectory(thridContainer, forthToken); + } + + private VFSContainer getNextDirectory(VFSContainer container, String token) { + VFSItem nextContainer = container.resolve(token); + if(nextContainer instanceof VFSContainer) { + return (VFSContainer)nextContainer; + } else if (nextContainer instanceof VFSLeaf) { + log.error(""); + return null; + } + return container.createChildContainer(token); + } + @Override public int countItems(Identity author) { return questionItemDao.countItems(author); diff --git a/src/main/java/org/olat/modules/qpool/manager/StudyFieldDAO.java b/src/main/java/org/olat/modules/qpool/manager/StudyFieldDAO.java index 6d89ea04122557b707e04658161cac83998bdd78..160ce247c7b3ea6a60c31d784cc35bbc8a3ff04b 100644 --- a/src/main/java/org/olat/modules/qpool/manager/StudyFieldDAO.java +++ b/src/main/java/org/olat/modules/qpool/manager/StudyFieldDAO.java @@ -87,6 +87,10 @@ public class StudyFieldDAO { } public String getMaterializedPath(StudyField field) { + if(field == null) { + return ""; + } + List<StudyField> parentLine = new ArrayList<StudyField>(); StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java b/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java index 7d499ccb7f7d5ea4a84cd0d4179cee5a8b276d87..af13a894d1790277716cc6d429c5853952be7456 100644 --- a/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java +++ b/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java @@ -67,6 +67,8 @@ public class QuestionItemImpl implements QuestionItem, CreateInfo, ModifiedInfo, @GenericGenerator(name = "system-uuid", strategy = "hilo") @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) private Long key; + @Column(name="q_uuid", nullable=false, insertable=true, updatable=false) + private String uuid; //general @Column(name="q_subject", nullable=false, insertable=true, updatable=true) @@ -119,8 +121,12 @@ public class QuestionItemImpl implements QuestionItem, CreateInfo, ModifiedInfo, private Date lastModified; @Column(name="q_version", nullable=true, insertable=true, updatable=true) private String itemVersion; - - + + @Column(name="q_dir", nullable=true, insertable=true, updatable=false) + private String directory; + @Column(name="q_root_filename", nullable=true, insertable=true, updatable=false) + private String rootFilename; + @Override public Long getKey() { @@ -131,6 +137,15 @@ public class QuestionItemImpl implements QuestionItem, CreateInfo, ModifiedInfo, this.key = key; } + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + @Override public String getResourceableTypeName() { return "QuestionItem"; @@ -320,6 +335,22 @@ public class QuestionItemImpl implements QuestionItem, CreateInfo, ModifiedInfo, this.itemVersion = itemVersion; } + public String getDirectory() { + return directory; + } + + public void setDirectory(String directory) { + this.directory = directory; + } + + public String getRootFilename() { + return rootFilename; + } + + public void setRootFilename(String rootFilename) { + this.rootFilename = rootFilename; + } + @Override public int hashCode() { return key == null ? 97489 : key.hashCode(); diff --git a/src/main/java/org/olat/modules/qpool/ui/ImportQuestionItemController.java b/src/main/java/org/olat/modules/qpool/ui/ImportQuestionItemController.java new file mode 100644 index 0000000000000000000000000000000000000000..1e305b301a1c04cf6660d124efa3f483a66a7031 --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/ui/ImportQuestionItemController.java @@ -0,0 +1,84 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.ui; + +import java.io.File; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FileElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.qpool.QuestionPoolService; + +/** + * + * Initial date: 26.02.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ImportQuestionItemController extends FormBasicController { + + private FileElement fileEl; + private final QuestionPoolService qpoolservice; + + public ImportQuestionItemController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + qpoolservice = CoreSpringFactory.getImpl(QuestionPoolService.class); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + fileEl = uifactory.addFileElement("item", "import.item", formLayout); + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + buttonsCont.setRootForm(mainForm); + formLayout.add(buttonsCont); + uifactory.addFormSubmitButton("ok", "ok", buttonsCont); + uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl()); + } + + @Override + protected void doDispose() { + // + } + + public File getFile() { + return fileEl.getUploadFile(); + } + + @Override + protected void formOK(UserRequest ureq) { + String filename = fileEl.getUploadFileName(); + File file = fileEl.getUploadFile(); + qpoolservice.importItem(getIdentity(), filename, file); + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/modules/qpool/ui/QuestionItemRow.java b/src/main/java/org/olat/modules/qpool/ui/QuestionItemRow.java index 48ae0df36c44fc32bbbd1d2ecda8d4a153510d5d..026b4f3cd105eafe1f60b76bef607d8f4a10b756 100644 --- a/src/main/java/org/olat/modules/qpool/ui/QuestionItemRow.java +++ b/src/main/java/org/olat/modules/qpool/ui/QuestionItemRow.java @@ -48,6 +48,11 @@ public class QuestionItemRow implements QuestionItemShort { return delegate.getKey(); } + @Override + public String getUuid() { + return delegate.getUuid(); + } + @Override public String getResourceableTypeName() { return delegate.getResourceableTypeName(); diff --git a/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java b/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java index 3d859aefc497fc05fdbebeef2f51f3db3699d4ff..142661ae58e9b3a7a94c89d3f4bcfb6f74a6c82c 100644 --- a/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java +++ b/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java @@ -69,7 +69,7 @@ import org.olat.modules.qpool.ui.QuestionItemDataModel.Cols; */ public class QuestionListController extends FormBasicController implements StackedControllerAware, ItemRowsSource { - private FormLink createList, shareItem, deleteItem, authorItem; + private FormLink createList, shareItem, deleteItem, authorItem, importItem; private FlexiTableElement itemsTable; private QuestionItemDataModel model; @@ -80,6 +80,7 @@ public class QuestionListController extends FormBasicController implements Stack private SelectBusinessGroupController selectGroupCtrl; private CreateCollectionController createCollectionCtrl; private StepsMainRunController importAuthorsWizard; + private ImportQuestionItemController importItemCtrl; private final MarkManager markManager; private final QuestionPoolService qpoolService; @@ -121,6 +122,7 @@ public class QuestionListController extends FormBasicController implements Stack createList = uifactory.addFormLink("create.list", formLayout, Link.BUTTON); shareItem = uifactory.addFormLink("share.item", formLayout, Link.BUTTON); + importItem = uifactory.addFormLink("import.item", formLayout, Link.BUTTON); authorItem = uifactory.addFormLink("author.item", formLayout, Link.BUTTON); deleteItem = uifactory.addFormLink("delete.item", formLayout, Link.BUTTON); } @@ -170,6 +172,8 @@ public class QuestionListController extends FormBasicController implements Stack List<QuestionItem> items = getQuestionItems(selections); doChooseAuthoren(ureq, items); } + } else if(link == importItem) { + doOpenImport(ureq); } else if("select".equals(link.getCmd())) { QuestionItemRow row = (QuestionItemRow)link.getUserObject(); doSelect(ureq, row.getItem()); @@ -226,6 +230,12 @@ public class QuestionListController extends FormBasicController implements Stack removeAsListenerAndDispose(importAuthorsWizard); importAuthorsWizard = null; } + } else if(source == importItemCtrl) { + if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) { + // + } + cmc.deactivate(); + cleanUp(); } else if(source == confirmDeleteBox) { boolean delete = DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event); if(delete) { @@ -241,9 +251,11 @@ public class QuestionListController extends FormBasicController implements Stack private void cleanUp() { removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(importItemCtrl); removeAsListenerAndDispose(selectGroupCtrl); removeAsListenerAndDispose(createCollectionCtrl); cmc = null; + importItemCtrl = null; selectGroupCtrl = null; createCollectionCtrl = null; } @@ -267,6 +279,17 @@ public class QuestionListController extends FormBasicController implements Stack return null; } + private void doOpenImport(UserRequest ureq) { + removeAsListenerAndDispose(importItemCtrl); + importItemCtrl = new ImportQuestionItemController(ureq, getWindowControl()); + listenTo(importItemCtrl); + + cmc = new CloseableModalController(getWindowControl(), translate("close"), + importItemCtrl.getInitialComponent(), true, translate("import.item")); + cmc.activate(); + listenTo(cmc); + } + private void doAskCollectionName(UserRequest ureq, List<QuestionItem> items) { removeAsListenerAndDispose(createCollectionCtrl); createCollectionCtrl = new CreateCollectionController(ureq, getWindowControl()); diff --git a/src/main/java/org/olat/modules/qpool/ui/_content/item_list.html b/src/main/java/org/olat/modules/qpool/ui/_content/item_list.html index 5037cd65133b9b8199bff14d079c8d0d9c6e1026..964db69037e3ef2d3fe0f3a50a621eb65842dcfe 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_content/item_list.html +++ b/src/main/java/org/olat/modules/qpool/ui/_content/item_list.html @@ -3,6 +3,7 @@ $r.render("items") <div class="o_qpool_button_bar"> $r.render("create.list") $r.render("share.item") + $r.render("import.item") $r.render("author.item") $r.render("delete.item") </div> diff --git a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties index b7cbb05ecb13f2da906391ccc1e54d44d8de1e37..ae0b8e6fb1137770e8a73dff6f80d8c6e55f5bb9 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties @@ -66,4 +66,5 @@ author.confirm.title=Best table.user.login=$org.olat.admin.user\:table.user.login share.item=Freigeben select.group=Freigeben -select.item=Bearbeiten \ No newline at end of file +select.item=Bearbeiten +import.item=Import \ No newline at end of file diff --git a/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql b/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql index bdeb81e0ec98d2bd66d24788687c904c2c6e7b1a..308df5e13cbb2e6502a3409f1b0f575ace13cc46 100644 --- a/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql +++ b/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql @@ -26,10 +26,11 @@ create table if not exists o_qp_study_field ( create table if not exists o_qp_item ( id bigint not null, + q_uuid varchar(36) not null, q_subject varchar(255) not null, q_keywords varchar(2048), - q_type varchar(64) not null, - q_language varchar(16) not null, + q_type varchar(64), + q_language varchar(16), q_status varchar(32) not null, q_description varchar(4000), q_copyright varchar(2048), @@ -44,6 +45,8 @@ create table if not exists o_qp_item ( creationdate datetime not null, lastmodified datetime not null, q_version varchar(32), + q_dir varchar(32), + q_root_filename varchar(255), fk_study_field bigint, fk_author_grp_id bigint, primary key (id) diff --git a/src/test/java/org/olat/modules/ims/qti/fileresource/FileResourceValidatorTest.java b/src/test/java/org/olat/modules/ims/qti/fileresource/FileResourceValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c28b7e1eb5522cd75903044dae423ac97d292996 --- /dev/null +++ b/src/test/java/org/olat/modules/ims/qti/fileresource/FileResourceValidatorTest.java @@ -0,0 +1,46 @@ +package org.olat.modules.ims.qti.fileresource; + +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; + +import junit.framework.Assert; + +import org.junit.Test; +import org.olat.ims.qti.fileresource.ItemFileResourceValidator; + +/** + * + * Initial date: 27.02.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class FileResourceValidatorTest { + + @Test + public void testItemValidation_xml() throws IOException, URISyntaxException { + URL itemUrl = FileResourceValidatorTest.class.getResource("mchc_ir_005.xml"); + assertNotNull(itemUrl); + File itemFile = new File(itemUrl.toURI()); + + ItemFileResourceValidator validator = new ItemFileResourceValidator(); + boolean valid = validator.validate(itemFile.getName(), itemFile); + Assert.assertTrue(valid); + } + + + @Test + public void testItemValidation_zip() throws IOException, URISyntaxException { + URL itemUrl = FileResourceValidatorTest.class.getResource("mchc_i_002.zip"); + assertNotNull(itemUrl); + File itemFile = new File(itemUrl.toURI()); + + ItemFileResourceValidator validator = new ItemFileResourceValidator(); + boolean valid = validator.validate(itemFile.getName(), itemFile); + Assert.assertTrue(valid); + } + +} diff --git a/src/test/java/org/olat/modules/ims/qti/fileresource/mchc_i_002.zip b/src/test/java/org/olat/modules/ims/qti/fileresource/mchc_i_002.zip new file mode 100644 index 0000000000000000000000000000000000000000..48e31fd9ff28948e18f0bcd256acbbaa318f8533 Binary files /dev/null and b/src/test/java/org/olat/modules/ims/qti/fileresource/mchc_i_002.zip differ diff --git a/src/test/java/org/olat/modules/ims/qti/fileresource/mchc_ir_005.xml b/src/test/java/org/olat/modules/ims/qti/fileresource/mchc_ir_005.xml new file mode 100644 index 0000000000000000000000000000000000000000..b285c730163307dd2f6e89b38fc68a66588f7705 --- /dev/null +++ b/src/test/java/org/olat/modules/ims/qti/fileresource/mchc_ir_005.xml @@ -0,0 +1 @@ +<?xml version = "1.0" encoding = "UTF-8" standalone = "no"?> <!DOCTYPE questestinterop SYSTEM "ims_qtiasiv1p2.dtd"> <!-- Author: Colin Smythe --> <!-- Date: 22nd January, 2002 --> <!-- Version 1.2 Compliant Example: BasicExample008b --> <!-- Basic Example with response processing --> <questestinterop> <qticomment>This is a multiple-choice with slider rendering.</qticomment> <item title = "Multiple Choice with Slider rendering Item" ident = "IMS_V01_I_mchc_ir_005"> <presentation label = "BasicExample008b"> <flow> <material> <mattext>What is the value of 2 * 3 ?</mattext> </material> <flow> <response_lid ident = "MC05" rcardinality = "Single" rtiming = "No"> <render_slider lowerbound = "2" upperbound = "10" step = "2" startval = "4" steplabel = "Yes"> <response_label ident = "A" rrange = "Exact">2</response_label> <response_label ident = "B" rrange = "Exact">4</response_label> <response_label ident = "C" rrange = "Exact">6</response_label> <response_label ident = "D" rrange = "Exact">8</response_label> <response_label ident = "E" rrange = "Exact">10</response_label> </render_slider> </response_lid> </flow> </flow> </presentation> <resprocessing> <outcomes> <decvar varname = "SLIDECHOICE" vartype = "Integer" defaultval = "0"/> </outcomes> <respcondition> <qticomment>Scoring for the correct answer.</qticomment> <conditionvar> <varequal respident = "MC05">C</varequal> </conditionvar> <setvar action = "Add" varname = "SLIDECHOICE">5</setvar> <displayfeedback feedbacktype = "Response" linkrefid = "Correct"/> </respcondition> <respcondition> <qticomment>Detecting the worng answer.</qticomment> <conditionvar> <or> <varequal respident = "MC05">A</varequal> <varequal respident = "MC05">B</varequal> <varequal respident = "MC05">D</varequal> <varequal respident = "MC05">E</varequal> </or> </conditionvar> <displayfeedback feedbacktype = "Response" linkrefid = "Incorrect"/> </respcondition> </resprocessing> <itemfeedback ident = "Correct" view = "Candidate"> <flow_mat> <material> <mattext>Correct.</mattext> </material> </flow_mat> </itemfeedback> <itemfeedback ident = "Incorrect" view = "Candidate"> <flow_mat> <material> <mattext>The correct answer is 6.</mattext> </material> </flow_mat> </itemfeedback> <itemfeedback ident = "Incorrect" view = "Tutor"> <flow_mat> <material> <mattext>The student chose the wrong answer.</mattext> </material> </flow_mat> </itemfeedback> </item> </questestinterop> \ No newline at end of file diff --git a/src/test/java/org/olat/modules/qpool/manager/CollectionDAOTest.java b/src/test/java/org/olat/modules/qpool/manager/CollectionDAOTest.java index 6e1f7440f4218fb581777c6cd30b614699f6a64f..a10b1336d4ad315880cb5a7b9f366efdb7492edb 100644 --- a/src/test/java/org/olat/modules/qpool/manager/CollectionDAOTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/CollectionDAOTest.java @@ -79,7 +79,7 @@ public class CollectionDAOTest extends OlatTestCase { public void addItemToCollectionById() { Identity id = JunitTestHelper.createAndPersistIdentityAsUser("Coll-Onwer-2-" + UUID.randomUUID().toString()); QuestionItemCollection coll = collectionDao.createCollection("NGC collection 2", id); - QuestionItem item = questionDao.create(null, "NGC 89", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, QuestionType.FIB); + QuestionItem item = questionDao.create(null, "NGC 89", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, null, QuestionType.FIB); dbInstance.commitAndCloseSession(); //add the item to the collection @@ -92,8 +92,8 @@ public class CollectionDAOTest extends OlatTestCase { //create a collection with 2 items Identity id = JunitTestHelper.createAndPersistIdentityAsUser("Coll-Onwer-3-" + UUID.randomUUID().toString()); QuestionItemCollection coll = collectionDao.createCollection("NGC collection 3", id); - QuestionItem item1 = questionDao.create(null, "NGC 92", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, QuestionType.FIB); - QuestionItem item2 = questionDao.create(null, "NGC 97", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, QuestionType.FIB); + QuestionItem item1 = questionDao.create(null, "NGC 92", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, null, QuestionType.FIB); + QuestionItem item2 = questionDao.create(null, "NGC 97", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, null, QuestionType.FIB); collectionDao.addItemToCollection(item1, coll); collectionDao.addItemToCollection(item2, coll); dbInstance.commit();//check if it's alright diff --git a/src/test/java/org/olat/modules/qpool/manager/PoolDAOTest.java b/src/test/java/org/olat/modules/qpool/manager/PoolDAOTest.java index 22fc57739fb7712fcc9f48ce31232e109802e054..72f243c78ab86987f1f171207af11949db2eaa8b 100644 --- a/src/test/java/org/olat/modules/qpool/manager/PoolDAOTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/PoolDAOTest.java @@ -92,7 +92,7 @@ public class PoolDAOTest extends OlatTestCase { String name = "NGC-" + UUID.randomUUID().toString(); Pool pool = poolDao.createPool(name); Assert.assertNotNull(pool); - QuestionItem item = questionItemDao.create(null, "Galaxy", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item = questionItemDao.create(null, "Galaxy", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); Assert.assertNotNull(item); dbInstance.commitAndCloseSession(); @@ -106,7 +106,7 @@ public class PoolDAOTest extends OlatTestCase { //create a pool String name = "NGC-" + UUID.randomUUID().toString(); Pool pool = poolDao.createPool(name); - QuestionItem item = questionItemDao.create(null, "Galaxy", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item = questionItemDao.create(null, "Galaxy", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); poolDao.addItemToPool(item, pool); dbInstance.commitAndCloseSession(); @@ -121,7 +121,7 @@ public class PoolDAOTest extends OlatTestCase { //create a pool with an item String name = "NGC-" + UUID.randomUUID().toString(); Pool pool = poolDao.createPool(name); - QuestionItem item = questionItemDao.create(null, "Galaxy", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item = questionItemDao.create(null, "Galaxy", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); poolDao.addItemToPool(item, pool); dbInstance.commitAndCloseSession(); diff --git a/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java b/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java index 3a90b5090a3b32fc940065052ce2f4cc768d349f..5efa4c1028810f2a953a70072ef4305c6851a0e4 100644 --- a/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java @@ -57,9 +57,10 @@ public class QuestionDAOTest extends OlatTestCase { @Test public void createQuestion() { - QuestionItem item = questionDao.create(null, "Stars", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.FIB); + QuestionItem item = questionDao.create(null, "Stars", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.FIB); Assert.assertNotNull(item); Assert.assertNotNull(item.getKey()); + Assert.assertNotNull(item.getUuid()); Assert.assertNotNull(item.getCreationDate()); Assert.assertNotNull(item.getLastModified()); Assert.assertNotNull(item.getQuestionType()); @@ -71,7 +72,7 @@ public class QuestionDAOTest extends OlatTestCase { @Test public void createQuestion_withOwner() { Identity id = JunitTestHelper.createAndPersistIdentityAsUser("QOwn-1-" + UUID.randomUUID().toString()); - QuestionItem item = questionDao.create(id, "My fav. stars", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.FIB); + QuestionItem item = questionDao.create(id, "My fav. stars", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.FIB); Assert.assertNotNull(item); Assert.assertNotNull(item.getKey()); Assert.assertNotNull(item.getCreationDate()); @@ -86,8 +87,8 @@ public class QuestionDAOTest extends OlatTestCase { public void getItems_byAuthor() { //create an author with 2 items Identity id = JunitTestHelper.createAndPersistIdentityAsUser("QOwn-2-" + UUID.randomUUID().toString()); - QuestionItem item1 = questionDao.create(id, "NGC 2171", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.FIB); - QuestionItem item2 = questionDao.create(id, "NGC 2172", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.FIB); + QuestionItem item1 = questionDao.create(id, "NGC 2171", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.FIB); + QuestionItem item2 = questionDao.create(id, "NGC 2172", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.FIB); dbInstance.commitAndCloseSession(); //count the items of the author @@ -103,7 +104,7 @@ public class QuestionDAOTest extends OlatTestCase { @Test public void getNumOfQuestions() { - QuestionItem item = questionDao.create(null, "NGC 1277", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item = questionDao.create(null, "NGC 1277", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); Assert.assertNotNull(item); dbInstance.commitAndCloseSession(); @@ -115,9 +116,9 @@ public class QuestionDAOTest extends OlatTestCase { @Test public void getFavoritItems() { Identity id = JunitTestHelper.createAndPersistIdentityAsUser("fav-item-" + UUID.randomUUID().toString()); - QuestionItem item1 = questionDao.create(id, "NGC 55", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); - QuestionItem item2 = questionDao.create(id, "NGC 253", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); - QuestionItem item3 = questionDao.create(id, "NGC 292", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item1 = questionDao.create(id, "NGC 55", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); + QuestionItem item2 = questionDao.create(id, "NGC 253", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); + QuestionItem item3 = questionDao.create(id, "NGC 292", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); markManager.setMark(item1, id, null, "[QuestionItem:" + item1 + "]"); markManager.setMark(item2, id, null, "[QuestionItem:" + item2 + "]"); dbInstance.commitAndCloseSession(); @@ -134,8 +135,8 @@ public class QuestionDAOTest extends OlatTestCase { public void shareItems() { //create a group to share 2 items BusinessGroup group = businessGroupDao.createAndPersist(null, "gdao", "gdao-desc", -1, -1, false, false, false, false, false); - QuestionItem item1 = questionDao.create(null, "Share-Item-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); - QuestionItem item2 = questionDao.create(null, "Share-Item-2", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item1 = questionDao.create(null, "Share-Item-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); + QuestionItem item2 = questionDao.create(null, "Share-Item-2", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); dbInstance.commit(); //share them @@ -154,7 +155,7 @@ public class QuestionDAOTest extends OlatTestCase { public void shareItems_avoidDuplicates() { //create a group to share 2 items BusinessGroup group = businessGroupDao.createAndPersist(null, "gdao", "gdao-desc", -1, -1, false, false, false, false, false); - QuestionItem item = questionDao.create(null, "Share-Item-Dup-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item = questionDao.create(null, "Share-Item-Dup-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); dbInstance.commit(); //share them @@ -174,7 +175,7 @@ public class QuestionDAOTest extends OlatTestCase { //create a group to share 2 items Identity id = JunitTestHelper.createAndPersistIdentityAsUser("Share-item-" + UUID.randomUUID().toString()); BusinessGroup group = businessGroupDao.createAndPersist(id, "gdao", "gdao-desc", -1, -1, false, false, false, false, false); - QuestionItem item = questionDao.create(id, "Share-Item-Dup-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item = questionDao.create(id, "Share-Item-Dup-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); dbInstance.commit(); //share them @@ -194,7 +195,7 @@ public class QuestionDAOTest extends OlatTestCase { //create a group to share 2 items Identity id = JunitTestHelper.createAndPersistIdentityAsUser("Share-rm-" + UUID.randomUUID().toString()); BusinessGroup group = businessGroupDao.createAndPersist(id, "gdrm", "gdrm-desc", -1, -1, false, false, false, false, false); - QuestionItem item = questionDao.create(id, "Share-item-rm-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item = questionDao.create(id, "Share-item-rm-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); dbInstance.commit(); //share them questionDao.share(item, group.getResource()); diff --git a/src/test/java/org/olat/modules/qpool/manager/QuestionPoolServiceTest.java b/src/test/java/org/olat/modules/qpool/manager/QuestionPoolServiceTest.java index bd1876ddb92d99c7acfc78ca4bc4b470fb90cd48..4028907f9ebf896cdf45c2ae50a750ade3b37991 100644 --- a/src/test/java/org/olat/modules/qpool/manager/QuestionPoolServiceTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/QuestionPoolServiceTest.java @@ -19,6 +19,12 @@ */ package org.olat.modules.qpool.manager; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -62,8 +68,8 @@ public class QuestionPoolServiceTest extends OlatTestCase { //create a group to share 2 items Identity id = JunitTestHelper.createAndPersistIdentityAsUser("Share-rm-" + UUID.randomUUID().toString()); BusinessGroup group = businessGroupDao.createAndPersist(id, "gdrm", "gdrm-desc", -1, -1, false, false, false, false, false); - QuestionItem item1 = questionDao.create(id, "Share-item-rm-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); - QuestionItem item2 = questionDao.create(id, "Share-item-rm-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, QuestionType.MC); + QuestionItem item1 = questionDao.create(id, "Share-item-rm-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); + QuestionItem item2 = questionDao.create(id, "Share-item-rm-1", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, QuestionType.MC); dbInstance.commit(); //share them questionDao.share(item1, group.getResource()); @@ -86,9 +92,9 @@ public class QuestionPoolServiceTest extends OlatTestCase { @Test public void createCollection() { //create an user with 2 items - Identity id = JunitTestHelper.createAndPersistIdentityAsUser("Coll-Onwer-3-" + UUID.randomUUID().toString()); - QuestionItem item1 = questionDao.create(id, "NGC 92", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, QuestionType.FIB); - QuestionItem item2 = questionDao.create(id, "NGC 97", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, QuestionType.FIB); + Identity id = JunitTestHelper.createAndPersistIdentityAsUser("Coll-Owner-3-" + UUID.randomUUID().toString()); + QuestionItem item1 = questionDao.create(id, "NGC 92", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, null, QuestionType.FIB); + QuestionItem item2 = questionDao.create(id, "NGC 97", QTIConstants.QTI_12_FORMAT, Locale.GERMAN.getLanguage(), null, null, QuestionType.FIB); dbInstance.commit(); //load the items of the collection @@ -109,4 +115,19 @@ public class QuestionPoolServiceTest extends OlatTestCase { Assert.assertTrue(itemsOfCollection.contains(item1)); Assert.assertTrue(itemsOfCollection.contains(item2)); } + + @Test + public void importItem_qti12xml() throws IOException, URISyntaxException { + Identity owner = JunitTestHelper.createAndPersistIdentityAsUser("Imp-Owner-1-" + UUID.randomUUID().toString()); + dbInstance.commit(); + URL itemUrl = QuestionPoolServiceTest.class.getResource("mchc_i_001.xml"); + assertNotNull(itemUrl); + File itemFile = new File(itemUrl.toURI()); + + + qpoolService.importItem(owner, "mchc_i_001.xml", itemFile); + + + } + } diff --git a/src/test/java/org/olat/modules/qpool/manager/mchc_i_001.xml b/src/test/java/org/olat/modules/qpool/manager/mchc_i_001.xml new file mode 100644 index 0000000000000000000000000000000000000000..e865fa086bd3bc7e159205a3130d5200062fc9cd --- /dev/null +++ b/src/test/java/org/olat/modules/qpool/manager/mchc_i_001.xml @@ -0,0 +1 @@ +<?xml version = "1.0" encoding = "UTF-8" standalone = "no"?> <!-- Author: Colin Smythe --> <!-- Date: 22nd January --> <!-- Version 1.2 Compliant Example: BasicExample002a --> <questestinterop> <qticomment>This is a simple multiple choice example. The rendering is a standard radio button style. No response processing is incorporated.</qticomment> <item title = "Standard Multiple Choice Item" ident = "IMS_V01_I_mchc_i_001"> <presentation label = "BasicExample002a"> <flow> <material> <mattext>Which one of the listed standards committees is responsible for developing the token ring specification ?</mattext> </material> <response_lid ident = "MCa_01" rcardinality = "Single" rtiming = "No"> <render_choice shuffle = "Yes"> <flow_label> <response_label ident = "A"> <material> <mattext>IEEE 802.3</mattext> </material> </response_label> </flow_label> <flow_label> <response_label ident = "B"> <material> <mattext>IEEE 802.5</mattext> </material> </response_label> </flow_label> <flow_label> <response_label ident = "C"> <material> <mattext>IEEE 802.6</mattext> </material> </response_label> </flow_label> <flow_label> <response_label ident = "D"> <material> <mattext>IEEE 802.11</mattext> </material> </response_label> </flow_label> <flow_label> <response_label ident = "E" rshuffle = "No"> <material> <mattext>None of the above.</mattext> </material> </response_label> </flow_label> </render_choice> </response_lid> </flow> </presentation> </item> </questestinterop> \ No newline at end of file diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 5e4647c821663c6ddaa0002a022bba384029d271..465854c03556ad2d1bddaaf3d4d0f89e70c0fefd 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -117,6 +117,7 @@ import org.junit.runners.Suite; org.olat.course.nodes.projectbroker.ProjectBrokerManagerTest.class, org.olat.core.commons.persistence.DBTest.class, org.olat.modules.ims.cp.CPManagerTest.class, + org.olat.modules.ims.qti.fileresource.FileResourceValidatorTest.class, org.olat.modules.webFeed.FeedManagerImplTest.class, org.olat.modules.qpool.manager.QuestionDAOTest.class, org.olat.modules.qpool.manager.CollectionDAOTest.class,