diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
index dc25734a11c14cff29366843629aa1feb7d38387..2d713ea0357bed1007115e82b0b4eb68e78151ad 100644
--- a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
+++ b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
@@ -461,7 +461,7 @@ public class QTI21AssessmentRunController extends BasicController implements Gen
 					new ResourcesMapper(assessmentObjectUri, submissionDir));
 
 			resultCtrl = new AssessmentResultController(ureq, getWindowControl(), getIdentity(), true, session,
-					fUnzippedDirRoot, mapperUri, getDeliveryOptions().getAssessmentResultsOptions(), false, false);
+					fUnzippedDirRoot, mapperUri, null, getDeliveryOptions().getAssessmentResultsOptions(), false, false);
 			listenTo(resultCtrl);
 			mainVC.put("resultReport", resultCtrl.getInitialComponent());
 			mainVC.contextPut("showResults", Boolean.TRUE);
diff --git a/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java b/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java
index 5ea7081ad67a5e8b35eb384495cd84fd8da5c9d6..2f4f95745467cf5d3a67c44d06e6ae84efa16b5a 100644
--- a/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java
+++ b/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java
@@ -24,18 +24,15 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.TimeZone;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -66,6 +63,7 @@ import org.olat.core.util.FileUtils;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.core.util.WebappHelper;
+import org.olat.core.util.ZipUtil;
 import org.olat.course.nodes.QTICourseNode;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.fileresource.FileResourceManager;
@@ -99,7 +97,8 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 	
 	private RepositoryEntry entry;
 	private UserRequest ureq;
-
+	
+	private final Set<RepositoryEntry> testEntries = new HashSet<>();
 
 	public QTI21ResultsExportMediaResource(CourseEnvironment courseEnv, List<Identity> identities, 
 			QTICourseNode courseNode, QTI21Service qtiService, UserRequest ureq) {
@@ -110,6 +109,7 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 		this.qtiService = qtiService;
 		this.ureq = ureq;		
 		this.entry = courseEnv.getCourseGroupManager().getCourseEntry();
+		translator = Util.createPackageTranslator(QTI21ResultsExportMediaResource.class, ureq.getLocale());
 	}
 	
 	public QTI21ResultsExportMediaResource(CourseEnvironment courseEnv, List<Identity> identities, QTICourseNode courseNode, 
@@ -153,7 +153,6 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 	@Override
 	public void prepare(HttpServletResponse hres) {
 		//init package translator
-		translator = Util.createPackageTranslator(QTI21ResultsExportMediaResource.class, hres.getLocale());
 		exportFolderName = translator.translate("export.folder.name");
 		
 		String label = StringHelper.transformDisplayNameToFileSystemName(title);
@@ -168,27 +167,40 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 		try { 			
 			ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream());
 			zout.setLevel(9);
-			
-			List<AssessedMember> assessedMembers = createAssessedMembersDetail(zout);
-			
-			//convert velocity template to zip entry
-			String membersHTML = createMemberListingHTML(assessedMembers);	
-			convertToZipEntry(zout, exportFolderName + "/index.html", membersHTML);
-			
-			//Copy resource files or file trees to export file tree 
-			File sasstheme = new File(WebappHelper.getContextRealPath("/static/offline/qti"));
-			fsToZip(zout, sasstheme.toPath(), exportFolderName + "/css/offline/qti/");
-			
-			File fontawesome = new File(WebappHelper.getContextRealPath("/static/font-awesome"));
-			fsToZip(zout, fontawesome.toPath(), exportFolderName + "/css/font-awesome/");
-			
+			exportTestResults(zout);
 			zout.close();
-
 		} catch (Exception e) {
 			log.error("Unknown error while assessment result resource export", e);
 		}
 	}
 	
+	/**
+	 * Adds the result export to existing zip stream.
+	 *
+	 * @throws IOException
+	 */
+	public void exportTestResults(ZipOutputStream zout) throws IOException {
+		
+		List<AssessedMember> assessedMembers = createAssessedMembersDetail(zout);
+		
+		//convert velocity template to zip entry
+		String membersHTML = createMemberListingHTML(assessedMembers);	
+		convertToZipEntry(zout, exportFolderName + "/index.html", membersHTML);
+		
+		//Copy resource files or file trees to export file tree 
+		File sasstheme = new File(WebappHelper.getContextRealPath("/static/offline/qti"));
+		ZipUtil.addDirectoryToZip(sasstheme.toPath(), exportFolderName + "/css/offline/qti/", zout);
+		File fontawesome = new File(WebappHelper.getContextRealPath("/static/font-awesome"));
+		ZipUtil.addDirectoryToZip(fontawesome.toPath(), exportFolderName + "/css/font-awesome/", zout);
+		File qtiJs = new File(WebappHelper.getContextRealPath("/static/js/jquery/"));
+		ZipUtil.addDirectoryToZip(qtiJs.toPath(), exportFolderName + "/js/jquery/", zout);
+
+		//materials
+		for(RepositoryEntry testEntry:testEntries) {
+			copyTestMaterials(testEntry, zout);
+		}
+	}
+	
 	private List<ResultDetail> createResultDetail (Identity identity, ZipOutputStream zout, String idDir) throws IOException {
 		List<ResultDetail> assessments = new ArrayList<ResultDetail>();				
 		List<AssessmentTestSession> sessions = qtiService.getAssessmentTestSessions(entry, courseNode.getIdent(), identity);
@@ -208,25 +220,32 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 			WindowControl mockwControl = new WindowControlMocker();
 			
 			FileResourceManager frm = FileResourceManager.getInstance();
-			File fUnzippedDirRoot = frm.unzipFileResource(session.getTestEntry().getOlatResource());
+			RepositoryEntry testEntry = session.getTestEntry();
+			testEntries.add(testEntry);
+			File fUnzippedDirRoot = frm.unzipFileResource(testEntry.getOlatResource());
 		
+			String mapperUri = "../../../test" + testEntry.getKey() + "/";//add test repo key
+			String submissionMapperUri = ".";
 			Controller assessmentResultController = new AssessmentResultController(
 					ureq, mockwControl, identity, false, session,
-					fUnzippedDirRoot, null, QTI21AssessmentResultsOptions.allOptions(), false, true);
+					fUnzippedDirRoot, mapperUri, submissionMapperUri, QTI21AssessmentResultsOptions.allOptions(), false, true);
 
 			Component component = assessmentResultController.getInitialComponent();
 			String componentHTML = createResultHTML(component); 
 			convertToZipEntry(zout, idPath + assessmentID +".html", componentHTML);	
 			
 			File resultXML = qtiService.getAssessmentResultFile(session);
-			convertToZipEntry(zout, idPath + assessmentID +".xml", resultXML);		
+			convertToZipEntry(zout, idPath + assessmentID +".xml", resultXML);	
 			
+			File submissionDir = qtiService.getSubmissionDirectory(session);
+			String baseDir = idPath + "submissions/";
+			ZipUtil.addDirectoryToZip(submissionDir.toPath(), baseDir, zout);
 		}
 		return assessments;
 	}
 	
 	private List<AssessedMember> createAssessedMembersDetail (ZipOutputStream zout) throws IOException {
-		List<AssessedMember> assessedMembers = new ArrayList<AssessedMember>();		
+		List<AssessedMember> assessedMembers = new ArrayList<>();		
 		for (Identity identity : identities) {
 			
 			String idDir = exportFolderName + "/" + DATA + identity.getName();
@@ -258,27 +277,13 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 		}
 		return assessedMembers;
 	}
-		
 	
-	/**
-	 * Adds the result export to existing zip stream.
-	 *
-	 * @throws IOException
-	 */
-	public void exportTestResults (ZipOutputStream zout) throws IOException {
-		
-		List<AssessedMember> assessedMembers = createAssessedMembersDetail(zout);
-		
-		//convert velocity template to zip entry
-		String membersHTML = createMemberListingHTML(assessedMembers);	
-		convertToZipEntry(zout, exportFolderName + "/index.html", membersHTML);
+	private void copyTestMaterials(RepositoryEntry testEntry, ZipOutputStream zout) {
+		FileResourceManager frm = FileResourceManager.getInstance();
+		File fUnzippedDirRoot = frm.unzipFileResource(testEntry.getOlatResource());
+		String baseDir = exportFolderName + "/test" + testEntry.getKey();
+		ZipUtil.addDirectoryToZip(fUnzippedDirRoot.toPath(), baseDir, zout);
 		
-		//Copy resource files or file trees to export file tree 
-		File sasstheme = new File(WebappHelper.getContextRealPath("/static/offline/qti"));
-		fsToZip(zout, sasstheme.toPath(), exportFolderName + "/css/offline/qti/");
-		
-		File fontawesome = new File(WebappHelper.getContextRealPath("/static/font-awesome"));
-		fsToZip(zout, fontawesome.toPath(), exportFolderName + "/css/font-awesome/");
 	}
 	
 	private String createLink(String name, String href, boolean userview) {
@@ -286,7 +291,6 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 		return "<a href='" + href + "' target='" + targetLink + "' class='userLink'>" + name + "</a>";		
 	}
 	
-	
 	private String createPassedIcons(boolean passed) {
 		String icon = passed ? "<i class='o_icon o_passed o_icon_passed text-success'></i>"
 				: "<i class='o_icon o_failed o_icon_failed text-danger'></i>";
@@ -302,25 +306,13 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 		mainVC.contextPut("rootTitle", translator.translate("table.grading"));
 		mainVC.put("results", results);
 		
-		GlobalSettings globalSettings = new GlobalSettings() {
-			public int getFontSize() { return 100;}
-			public AJAXFlags getAjaxFlags() { return new EmptyAJAXFlags();}
-			public boolean isIdDivsForced() { return false; }
-		};
+		
 		//render VelocityContainer to StringOutPut
-		Renderer renderer = Renderer.getInstance(mainVC, translator, ubu, new RenderResult(), globalSettings); 
+		Renderer renderer = Renderer.getInstance(mainVC, translator, ubu, new RenderResult(), new EmptyGlobalSettings()); 
 		renderer.render(sb, mainVC, null);
-		//mainVC.getHTMLRendererSingleton().render(renderer, sb, mainVC, ubu, tlr, new RenderResult(), null);
-		
 		return sb.toString();
 	}
 	
-	private static class EmptyAJAXFlags extends AJAXFlags {
-		public EmptyAJAXFlags() { super(null); }
-		@Override
-		public boolean isIframePostEnabled() { return false; }
-	}
-	
 	private String createResultListingHTML (List<ResultDetail> assessments,AssessedMember assessedMember){
 		// now put values to velocityContext
 		VelocityContext ctx = new VelocityContext();
@@ -356,26 +348,6 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 		return velocityHelper.evaluateVTL(template, ctx);
 	}
 	
-
-	private void fsToZip(ZipOutputStream zout, final Path sourceFolder, final String targetPath) throws IOException {
-		Files.walkFileTree(sourceFolder, new SimpleFileVisitor<Path>() {
-			@Override
-			public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-				zout.putNextEntry(new ZipEntry(targetPath + sourceFolder.relativize(file).toString()));
-				Files.copy(file, zout);
-				zout.closeEntry();
-				return FileVisitResult.CONTINUE;
-			}
-
-			@Override
-			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
-				zout.putNextEntry(new ZipEntry(targetPath + sourceFolder.relativize(dir).toString() + "/"));
-				zout.closeEntry();
-				return FileVisitResult.CONTINUE;
-			}
-		});
-	}
-	
 	private void convertToZipEntry(ZipOutputStream zout, String link, File file) throws IOException {
 		zout.putNextEntry(new ZipEntry(link));
 		try (InputStream in = new FileInputStream(file)) {
@@ -404,10 +376,37 @@ public class QTI21ResultsExportMediaResource implements MediaResource {
 		zout.closeEntry();		
 	} 
 
-
 	@Override
 	public void release() {
-
+		//
 	}
 
+	private static class EmptyAJAXFlags extends AJAXFlags {
+		
+		public EmptyAJAXFlags() {
+			super(null);
+		}
+		
+		@Override
+		public boolean isIframePostEnabled() {
+			return false;
+		}
+	}
+	
+	private static class EmptyGlobalSettings implements GlobalSettings {
+		@Override
+		public int getFontSize() {
+			return 100;
+		}
+		
+		@Override
+		public AJAXFlags getAjaxFlags() {
+			return new EmptyAJAXFlags();
+		}
+		
+		@Override
+		public boolean isIdDivsForced() {
+			return false;
+		}
+	};
 }
diff --git a/src/main/java/org/olat/ims/qti21/resultexport/_content/qti21results.html b/src/main/java/org/olat/ims/qti21/resultexport/_content/qti21results.html
index f5e4a281298bfac2c905695642a6d5b282994e63..90fa8303e96abecc19a1a85d92e86072b0c4cc1f 100644
--- a/src/main/java/org/olat/ims/qti21/resultexport/_content/qti21results.html
+++ b/src/main/java/org/olat/ims/qti21/resultexport/_content/qti21results.html
@@ -3,7 +3,28 @@
 <head>
 	<title>$rootTitle</title>	
 	<link rel='stylesheet' href='../../../css/offline/qti/theme.css' />
+	<script type="text/javascript" src='../../../js/jquery/jquery-2.1.3.min.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/maphilight/jquery.maphilight.min.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.associate.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.gapMatch.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.graphicAssociate.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.graphicGap.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.graphicOrder.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.hotspot.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.match.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.order.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.positionObject.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.qtiTimer.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.selectPoint.js'></script>
+	<script type="text/javascript" src='../../../js/jquery/qti/jquery.slider.js'></script>
 	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
+	<script type="text/javascript">
+/* <![CDATA[ */ 
+function setFlexiFormDirtyByListener(e){
+	//
+}
+/* ]]> */
+	</script>
 </head>
 <body class="o_page_margins">
 	<div id="o_main">$r.render("results")</div>
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentResultController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentResultController.java
index 89e2070159d9fc53e2ecbec35b0f4005ac9038e5..fe3af2bf23f6cada0e841ebe37bc858e44756325 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentResultController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentResultController.java
@@ -102,6 +102,7 @@ public class AssessmentResultController extends FormBasicController {
 	
 	private final String mapperUri;
 	private String signatureMapperUri;
+	private final String submissionMapperUri;
 	private final QTI21AssessmentResultsOptions options;
 	
 	private final boolean anonym;
@@ -125,7 +126,7 @@ public class AssessmentResultController extends FormBasicController {
 	private QTI21Service qtiService;
 	
 	public AssessmentResultController(UserRequest ureq, WindowControl wControl, Identity assessedIdentity, boolean anonym,
-			AssessmentTestSession candidateSession, File fUnzippedDirRoot, String mapperUri,
+			AssessmentTestSession candidateSession, File fUnzippedDirRoot, String mapperUri, String submissionMapperUri,
 			QTI21AssessmentResultsOptions options, boolean withPrint, boolean withTitle) {
 		super(ureq, wControl, "assessment_results");
 		
@@ -137,6 +138,7 @@ public class AssessmentResultController extends FormBasicController {
 		this.assessedIdentity = assessedIdentity;
 		this.candidateSession = candidateSession;
 		this.fUnzippedDirRoot = fUnzippedDirRoot;
+		this.submissionMapperUri = submissionMapperUri;
 
 		ResourceLocator fileResourceLocator = new PathResourceLocator(fUnzippedDirRoot.toPath());
 		inputResourceLocator = 
@@ -336,6 +338,9 @@ public class AssessmentResultController extends FormBasicController {
 		formItem.setResourceLocator(inputResourceLocator);
 		formItem.setAssessmentObjectUri(assessmentObjectUri);
 		formItem.setMapperUri(mapperUri);
+		if(submissionMapperUri != null) {
+			formItem.setSubmissionMapperUri(submissionMapperUri);
+		}
 	}
 	
 	private void extractOutcomeVariable(List<ItemVariable> itemVariables, Results results) {
@@ -391,7 +396,7 @@ public class AssessmentResultController extends FormBasicController {
 			@Override
 			public Controller createController(UserRequest uureq, WindowControl wwControl) {
 				AssessmentResultController printViewCtrl = new AssessmentResultController(uureq, wwControl, assessedIdentity, anonym,
-						candidateSession, fUnzippedDirRoot, mapperUri, options, false, true);
+						candidateSession, fUnzippedDirRoot, mapperUri, submissionMapperUri, options, false, true);
 				printViewCtrl.flc.contextPut("printCommand", Boolean.TRUE);
 				listenTo(printViewCtrl);
 				return printViewCtrl;
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
index 436c46c161aca832ecf1ebf4851381299988c0c8..0dc9cbb14ff209454cb82069141733f2dfd335dc 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
@@ -1819,7 +1819,7 @@ public class AssessmentTestDisplayController extends BasicController implements
 				removeAsListenerAndDispose(resultCtrl);
 				resultCtrl = new AssessmentResultController(ureq, getWindowControl(), assessedIdentity, anonym,
 						AssessmentTestDisplayController.this.getCandidateSession(),
-						fUnzippedDirRoot, mapperUri, deliveryOptions.getAssessmentResultsOptions(),  false, true);
+						fUnzippedDirRoot, mapperUri, null, deliveryOptions.getAssessmentResultsOptions(), false, true);
 				listenTo(resultCtrl);
 				flc.add("qtiResults", resultCtrl.getInitialFormItem());
 				resultsVisible = true;
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
index 5d47f62bb36144a17f9125bad2029434ee5f76be..5ca22b1212272add6231d051351d126f4d04f404 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
@@ -410,7 +410,7 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 				new ResourcesMapper(assessmentObjectUri, submissionDir));
 		
 		resultCtrl = new AssessmentResultController(ureq, getWindowControl(), assessedIdentity, false, session,
-				fUnzippedDirRoot, mapperUri, QTI21AssessmentResultsOptions.allOptions(), true, true);
+				fUnzippedDirRoot, mapperUri, null, QTI21AssessmentResultsOptions.allOptions(), true, true);
 		listenTo(resultCtrl);
 		cmc = new CloseableModalController(getWindowControl(), "close", resultCtrl.getInitialComponent(),
 				true, translate("table.header.results"));
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java
index 82298ac3341479eca17ba1675bb34fbc7d47de7a..017781e1784d45091d619262411d9be94aa1c457 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java
@@ -54,6 +54,7 @@ public abstract class AssessmentObjectComponent extends AbstractComponent implem
 	private Context context;
 	
 	private String mapperUri;
+	private String submissionMapperUri;
 	private URI assessmentObjectUri;
 	private ResourceLocator resourceLocator;
 	private CandidateSessionContext candidateSessionContext;
@@ -71,6 +72,19 @@ public abstract class AssessmentObjectComponent extends AbstractComponent implem
 	public void setMapperUri(String mapperUri) {
 		this.mapperUri = mapperUri;
 	}
+	
+	/**
+	 * Allow to define a specific mapper uri for the uploaded files.
+	 * 
+	 * @return The specific submission mapper uri or the standard one if it was not defined
+	 */
+	public String getSubmissionMapperUri() {
+		return submissionMapperUri == null ? mapperUri : submissionMapperUri;
+	}
+	
+	public void setSubmissionMapperUri(String submissionMapperUri) {
+		this.submissionMapperUri = submissionMapperUri;
+	}
 
 	public URI getAssessmentObjectUri() {
 		return assessmentObjectUri;
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectFormItem.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectFormItem.java
index b23bd7d1c38cd84d74ac910c9b052bc5a9003dd5..40e69b96d3abe17a3d6e452fb8023a7aada3ea1a 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectFormItem.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectFormItem.java
@@ -83,6 +83,14 @@ public abstract class AssessmentObjectFormItem extends FormItemImpl implements F
 		getComponent().setMapperUri(mapperUri);
 	}
 	
+	public String getSubmissionMapperUri() {
+		return getComponent().getSubmissionMapperUri();
+	}
+	
+	public void setSubmissionMapperUri(String submissionMapperUri) {
+		getComponent().setSubmissionMapperUri(submissionMapperUri);
+	}
+	
 	public URI getAssessmentObjectUri() {
 		return getComponent().getAssessmentObjectUri();
 	}
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java
index a7c6d76e9e37cbd542ccbad89a7f2ba273058e55..1be51d263f5c45414e1c50c21b90a7bd49350c32 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectVelocityRenderDecorator.java
@@ -177,6 +177,10 @@ public class AssessmentObjectVelocityRenderDecorator extends VelocityRenderDecor
 		return path.concat(url);
 	}
 	
+	public String convertSubmissionLinkFull(String uri) {
+		return AssessmentRenderFunctions.convertSubmissionLink(avc, resolvedAssessmentItem, uri);
+	}
+	
 	public String getFormDispatchFieldId() {
 		return avc.getQtiItem().getRootForm().getDispatchFieldId();
 	}
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java
index 73f2feb99d88f5f6f604553f347e35ce7bd84f90..03cf91b81a1ab6f74f1dea8643250566537d972d 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentRenderFunctions.java
@@ -617,11 +617,24 @@ public class AssessmentRenderFunctions {
     </xsl:choose>
   </xsl:function>
 	 */
+	
 	public static final String convertLink(AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, String uri) {
 		if(uri != null && uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("mailto:")) {
 			return uri;
 		}
 		
+		String filename = getLinkFilename(uri);
+		String relativePath = component.relativePathTo(resolvedAssessmentItem);
+		return component.getMapperUri() + "/" + filename + "?href=" + relativePath + (uri == null ? "" : uri);
+	}
+	
+	public static final String convertSubmissionLink(AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem, String uri) {
+		String filename = getLinkFilename(uri);
+		String relativePath = component.relativePathTo(resolvedAssessmentItem);
+		return component.getSubmissionMapperUri() + "/submissions/" + filename + "?href=" + relativePath + (uri == null ? "" : uri);
+	}
+	
+	private static final String getLinkFilename(String uri) {
 		String filename = "file";
 		try {
 			if(StringHelper.containsNonWhitespace(uri)) {
@@ -635,9 +648,7 @@ public class AssessmentRenderFunctions {
 		} catch (Exception e) {
 			log.error("", e);
 		}
-
-		String relativePath = component.relativePathTo(resolvedAssessmentItem);
-		return component.getMapperUri() + "/" + filename + "?href=" + relativePath + (uri == null ? "" : uri);
+		return filename;
 	}
 	
 	public static final boolean testFeedbackVisible(TestFeedback testFeedback, TestSessionState testSessionState) {
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/_content/uploadInteraction.html b/src/main/java/org/olat/ims/qti21/ui/components/_content/uploadInteraction.html
index 3a3da86905f629e1c9667512bab1f6f949a78bcb..547e9ed01d976efe097524aee4295c1b2ac7666e 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/_content/uploadInteraction.html
+++ b/src/main/java/org/olat/ims/qti21/ui/components/_content/uploadInteraction.html
@@ -23,7 +23,7 @@
 			</div>
 		</div>	
 		#else
-			<a href="${r.convertLinkFull("submissions/${r.getAssessmentTestSessionKey()}/$responseValue.fileName")}" target="_blank"><i class="o_icon $r.getFiletypeIconCss($responseValue.fileName)"> </i> $responseValue.fileName</a>
+			<a href='${r.convertSubmissionLinkFull("submissions/${r.getAssessmentTestSessionKey()}/$responseValue.fileName")}' target="_blank"><i class="o_icon $r.getFiletypeIconCss($responseValue.fileName)"> </i> $responseValue.fileName</a>
 		#end
 	#else
 		## Nothing uploaded yet