diff --git a/pom.xml b/pom.xml
index 35f7d51e055cd27916e3aea985d9e8006d620451..24228ad7d53332e5f10796fc4a185fef89b3241c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
 		<version.selenium>3.141.59</version.selenium>
 		<version.drone>2.5.1</version.drone>
 		<activemq.version>5.15.9</activemq.version>
-		<qtiworks.version>1.0.16</qtiworks.version>
+		<qtiworks.version>1.0.17</qtiworks.version>
 
 	    <!-- properties for testing and Q&A -->
 	    <!-- by default no tests are executed so far (April 2011). Use appropriate profiles and properties on the command line -->
@@ -1411,7 +1411,7 @@
 								<Implementation-Build>${buildNumber}</Implementation-Build>
 							</manifestEntries>
 						</archive>
-						<warSourceExcludes>**/*.pxm, **/*.psd, **/*.scss, **/*.sh, static/bootstrap/**, **/*.README</warSourceExcludes>
+						<warSourceExcludes>**/*.pxm, **/*.psd, **/*.sh, static/bootstrap/**, **/*.README</warSourceExcludes>
 						<webResources>
 							<resource>
 								<directory>src/main/webapp</directory>
diff --git a/src/main/java/org/olat/core/commons/services/vfs/restapi/VFSDepthException.java b/src/main/java/org/olat/core/commons/services/vfs/restapi/VFSDepthException.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb352be525206be0e81d55caf7dd8c5cb04a9079
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/restapi/VFSDepthException.java
@@ -0,0 +1,37 @@
+/**
+ * <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.core.commons.services.vfs.restapi;
+
+import java.io.IOException;
+
+/**
+ * 
+ * 
+ * Initial date: 6 juin 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class VFSDepthException extends IOException {
+
+	private static final long serialVersionUID = 7550064282144460698L;
+	
+	
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/restapi/VFSWebservice.java b/src/main/java/org/olat/core/commons/services/vfs/restapi/VFSWebservice.java
index 03f3e71be1f67f094677c6c9cf51a7458ca696fd..c6f848a68266324a528c3f43b29a45dde3f3b523 100644
--- a/src/main/java/org/olat/core/commons/services/vfs/restapi/VFSWebservice.java
+++ b/src/main/java/org/olat/core/commons/services/vfs/restapi/VFSWebservice.java
@@ -74,8 +74,9 @@ public class VFSWebservice {
 	private static final String VERSION  = "1.0";
 	
 	private static final Logger log = Tracing.createLoggerFor(VFSWebservice.class);
+	private static final int MAX_FOLDER_DEPTH = 20;
 	
-	public static CacheControl cc = new CacheControl();
+	private static final CacheControl cc = new CacheControl();
 	static {
 		cc.setMaxAge(-1);
 	}
@@ -182,8 +183,13 @@ public class VFSWebservice {
 	public Response postFile64ToRoot(@FormParam("foldername") String foldername, @FormParam("filename") String filename,
 			@FormParam("file") String file, @Context UriInfo uriInfo) {
 		byte[] fileAsBytes = Base64.decodeBase64(file);
-		InputStream in = new ByteArrayInputStream(fileAsBytes);
-		return putFile(foldername, filename, in, uriInfo, Collections.<PathSegment>emptyList());
+		try(InputStream in = new ByteArrayInputStream(fileAsBytes)) {
+			return putFile(foldername, filename, in, uriInfo, Collections.<PathSegment>emptyList());
+		} catch (VFSDepthException e) {
+			return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
+		} catch (IOException e) {
+			return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
+		}
 	}
 	
 	/**
@@ -224,8 +230,13 @@ public class VFSWebservice {
 	public Response postFile64ToFolder(@FormParam("foldername") String foldername, @FormParam("filename") String filename,
 			@FormParam("file") String file, @Context UriInfo uriInfo, @PathParam("path") List<PathSegment> path) {
 		byte[] fileAsBytes = Base64.decodeBase64(file);
-		InputStream in = new ByteArrayInputStream(fileAsBytes);
-		return putFile(foldername, filename, in, uriInfo, path);
+		try(InputStream in = new ByteArrayInputStream(fileAsBytes)) {
+			return putFile(foldername, filename, in, uriInfo, path);
+		} catch (VFSDepthException e) {
+			return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
+		} catch (IOException e) {
+			return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
+		}
 	}
 	
 	/**
@@ -260,8 +271,13 @@ public class VFSWebservice {
 	@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
 	public Response putFile64VOToRoot(File64VO file, @Context UriInfo uriInfo) {
 		byte[] fileAsBytes = Base64.decodeBase64(file.getFile());
-		InputStream in = new ByteArrayInputStream(fileAsBytes);
-		return putFile(null, file.getFilename(), in, uriInfo, Collections.<PathSegment>emptyList());
+		try(InputStream in = new ByteArrayInputStream(fileAsBytes)) {
+			return putFile(null, file.getFilename(), in, uriInfo, Collections.<PathSegment>emptyList());
+		} catch (VFSDepthException e) {
+			return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
+		} catch (IOException e) {
+			return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
+		}
 	}
 	
 	/**
@@ -300,6 +316,9 @@ public class VFSWebservice {
 		} catch (FileNotFoundException e) {
 			log.error("", e);
 			return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
+		}  catch (VFSDepthException e) {
+			log.error("", e);
+			return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
 		} finally {
 			MultipartReader.closeQuietly(partsReader);
 			IOUtils.closeQuietly(in);
@@ -323,8 +342,13 @@ public class VFSWebservice {
 	@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
 	public Response putFile64ToFolder(File64VO file, @Context UriInfo uriInfo, @PathParam("path") List<PathSegment> path) {
 		byte[] fileAsBytes = Base64.decodeBase64(file.getFile());
-		InputStream in = new ByteArrayInputStream(fileAsBytes);
-		return putFile(null, file.getFilename(), in, uriInfo, path);
+		try(InputStream in = new ByteArrayInputStream(fileAsBytes)) {
+			return putFile(null, file.getFilename(), in, uriInfo, path);
+		} catch (VFSDepthException e) {
+			return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
+		} catch(IOException e) {
+			return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
+		}
 	}
 	
 	/**
@@ -368,15 +392,23 @@ public class VFSWebservice {
 		if(container.getLocalSecurityCallback() != null && !container.getLocalSecurityCallback().canWrite()) {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
+		if(path.size() >= MAX_FOLDER_DEPTH) {
+			return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
+		}
 		
-		VFSContainer directory = resolveContainer(path, true);
-		if(directory == null) {
-			return Response.serverError().status(Status.NOT_FOUND).build();
+		try {
+			VFSContainer directory = resolveContainer(path, true);
+			if(directory == null) {
+				return Response.serverError().status(Status.NOT_FOUND).build();
+			}
+			return Response.ok(createFileVO(directory, uriInfo)).build();
+		} catch (VFSDepthException e) {
+			return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
 		}
-		return Response.ok(createFileVO(directory, uriInfo)).build();
 	}
 	
-	protected Response putFile(String foldername, String filename, InputStream file, UriInfo uriInfo, List<PathSegment> path) {
+	private Response putFile(String foldername, String filename, InputStream file, UriInfo uriInfo, List<PathSegment> path)
+	throws VFSDepthException {
 		if(container.getLocalSecurityCallback() != null && !container.getLocalSecurityCallback().canWrite()) {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
@@ -476,15 +508,20 @@ public class VFSWebservice {
 		return Response.serverError().status(Status.BAD_REQUEST).build();
 	}
 	
-	protected VFSContainer resolveContainer(List<PathSegment> path, boolean create) {
+	protected VFSContainer resolveContainer(List<PathSegment> path, boolean create)
+	throws VFSDepthException {
 		VFSContainer directory = container;
 		boolean notFound = false;
 		
 		//remove trailing segment if a trailing / is used
-		if(path.size() > 0 && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) {
+		if(!path.isEmpty() && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) {
 			path = path.subList(0, path.size() -1);
 		}
 		
+		if(create && path.size() >= MAX_FOLDER_DEPTH) {
+			throw new VFSDepthException();
+		}
+		
 		a_a:
 		for(PathSegment seg:path) {
 			String segPath = seg.getPath();
@@ -517,7 +554,7 @@ public class VFSWebservice {
 		boolean notFound = false;
 		
 		//remove trailing segment if a trailing / is used
-		if(path.size() > 0 && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) {
+		if(!path.isEmpty() && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) {
 			path = path.subList(0, path.size() -1);
 		}
 		
diff --git a/src/main/java/org/olat/course/nodes/iq/IQRunController.java b/src/main/java/org/olat/course/nodes/iq/IQRunController.java
index 3e46c174faab3df35dffbcfaad251a0d4bc2523f..f80369dcb3e37644426a0df0ad49576f30a4eb2c 100644
--- a/src/main/java/org/olat/course/nodes/iq/IQRunController.java
+++ b/src/main/java/org/olat/course/nodes/iq/IQRunController.java
@@ -27,7 +27,8 @@ package org.olat.course.nodes.iq;
 
 import java.io.File;
 import java.text.DateFormat;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -222,25 +223,34 @@ public class IQRunController extends BasicController implements GenericEventList
 		Formatter formatter = Formatter.getInstance(ureq.getLocale());
 		ImsRepositoryResolver resolver = new ImsRepositoryResolver(referenceTestEntry);
 		QTIChangeLogMessage[] qtiChangeLog = resolver.getDocumentChangeLog();
+		
 		StringBuilder qtiChangelog = new StringBuilder();
 
 		if(qtiChangeLog.length>0){
+			List<QTIChangeLogMessage> qtiChangeLogList = new ArrayList<>(qtiChangeLog.length);
+			for (int i=qtiChangeLog.length; i-->0 ; ) {
+				if(qtiChangeLog[i] != null) {
+					qtiChangeLogList.add(qtiChangeLog[i]);
+				}
+			}
 			//there are resource changes
-			Arrays.sort(qtiChangeLog);
-			for (int i = qtiChangeLog.length-1; i >= 0 ; i--) {
+			Collections.sort(qtiChangeLogList);
+			
+			for (int i = qtiChangeLogList.size()-1; i >= 0 ; i--) {
+				QTIChangeLogMessage qtiChangeLogEntry = qtiChangeLogList.get(i);
 				//show latest change first
-				if(!showAll && qtiChangeLog[i].isPublic()){
+				if(!showAll && qtiChangeLogEntry.isPublic()){
 					//logged in person is a normal user, hence public messages only
-					Date msgDate = new Date(qtiChangeLog[i].getTimestmp());
+					Date msgDate = new Date(qtiChangeLogEntry.getTimestmp());
 					qtiChangelog.append("\nChange date: ").append(formatter.formatDateAndTime(msgDate)).append("\n");
-					String msg = StringHelper.escapeHtml(qtiChangeLog[i].getLogMessage());
+					String msg = StringHelper.escapeHtml(qtiChangeLogEntry.getLogMessage());
 					qtiChangelog.append(msg);
 					qtiChangelog.append("\n********************************\n");
 				}else if (showAll){
 					//logged in person is an author, olat admin, owner, show all messages
-					Date msgDate = new Date(qtiChangeLog[i].getTimestmp());
+					Date msgDate = new Date(qtiChangeLogEntry.getTimestmp());
 					qtiChangelog.append("\nChange date: ").append(formatter.formatDateAndTime(msgDate)).append("\n");
-					String msg = StringHelper.escapeHtml(qtiChangeLog[i].getLogMessage());
+					String msg = StringHelper.escapeHtml(qtiChangeLogEntry.getLogMessage());
 					qtiChangelog.append(msg);
 					qtiChangelog.append("\n********************************\n");
 				}//else non public messages are not shown to normal user
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java
index 61f1bdc31ecf9591b25f4174ba99a1b941ad8fb2..5dbb56207b0a45e28fd182dc1a312cb01d144863 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponent.java
@@ -35,6 +35,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.ModalFeedback;
 import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction;
 import uk.ac.ed.ph.jqtiplus.node.result.SessionStatus;
 import uk.ac.ed.ph.jqtiplus.node.test.AbstractPart;
+import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef;
 import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection;
 import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest;
 import uk.ac.ed.ph.jqtiplus.node.test.TestPart;
@@ -174,9 +175,16 @@ public class AssessmentTestComponent extends AssessmentObjectComponent  {
 		}
 		
 		try {
-			URI itemSystemId = itemNode.getItemSystemId();
+			AssessmentItemRef itemRef = getResolvedAssessmentTest().getItemRefsByIdentifierMap()
+				.get(itemNode.getKey().getIdentifier());
+			if(itemRef == null) {
+				return false;
+			}
 			ResolvedAssessmentItem resolvedAssessmentItem = getResolvedAssessmentTest()
-					.getResolvedAssessmentItemBySystemIdMap().get(itemSystemId);
+				.getResolvedAssessmentItem(itemRef);
+			if(resolvedAssessmentItem == null) {
+				return false;
+			}
 			AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful();
 			if(assessmentItem.getAdaptive()) {
 				return true;
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
index dc4104cdea4e0577f5c51b3bce594b4ae77b6088..6534f5f817118e862316419526fff3839387f143 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
@@ -25,7 +25,6 @@ import static org.olat.ims.qti21.ui.components.AssessmentRenderFunctions.testFee
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.UnsupportedEncodingException;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -70,6 +69,7 @@ import uk.ac.ed.ph.jqtiplus.node.content.variable.RubricBlock;
 import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
 import uk.ac.ed.ph.jqtiplus.node.item.template.declaration.TemplateDeclaration;
 import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration;
+import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef;
 import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection;
 import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode;
 import uk.ac.ed.ph.jqtiplus.node.test.TestFeedback;
@@ -380,17 +380,23 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe
 	
 	private void renderTestItemBody(AssessmentRenderer renderer, StringOutput sb, AssessmentTestComponent component, TestPlanNode itemNode,
 			URLBuilder ubu, Translator translator, RenderingRequest options) {
-		final ItemSessionState itemSessionState = component.getItemSessionState(itemNode.getKey());
-		
-		URI itemSystemId = itemNode.getItemSystemId();
+
+		AssessmentItemRef itemRef = component.getResolvedAssessmentTest()
+				.getItemRefsByIdentifierMap().get(itemNode.getKey().getIdentifier());
+		if(itemRef == null) {
+			log.error("Missing assessment item ref: " + itemNode.getKey());
+			renderMissingItem(sb, translator);
+			return;
+		}
 		ResolvedAssessmentItem resolvedAssessmentItem = component.getResolvedAssessmentTest()
-				.getResolvedAssessmentItemBySystemIdMap().get(itemSystemId);
+				.getResolvedAssessmentItem(itemRef);
 		if(resolvedAssessmentItem == null) {
-			log.error("Missing assessment item: " + itemSystemId);
+			log.error("Missing assessment item: " + itemNode.getKey());
 			renderMissingItem(sb, translator);
 			return;
 		}
-		
+
+		final ItemSessionState itemSessionState = component.getItemSessionState(itemNode.getKey());
 		final AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful();
 
 		sb.append("<div class='o_assessmentitem_wrapper'>");
diff --git a/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java b/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java
index c2635cebbc6fd1aafb34f32ff96f5bd4c00c5b09..54a689abeeab8f84ed4f1ccb34f27e38a5661be2 100644
--- a/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java
+++ b/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java
@@ -62,6 +62,7 @@ import org.olat.core.gui.components.stack.TooledStackedPanel.Align;
 import org.olat.core.gui.components.tree.GenericTreeModel;
 import org.olat.core.gui.components.tree.GenericTreeNode;
 import org.olat.core.gui.components.tree.MenuTree;
+import org.olat.core.gui.components.tree.TreeEvent;
 import org.olat.core.gui.components.tree.TreeModel;
 import org.olat.core.gui.components.tree.TreeNode;
 import org.olat.core.gui.control.Controller;
@@ -209,9 +210,11 @@ public class UserAdminMainController extends MainLayoutBasicController implement
 	@Override
 	public void event(UserRequest ureq, Component source, Event event) {
 		if (source == menuTree) {
-			if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED)) {
-				TreeNode selTreeNode = menuTree.getSelectedNode();
-				contentCtr = pushController(ureq, selTreeNode);
+			if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED) && event instanceof TreeEvent) {
+				TreeNode selTreeNode = menuTree.getTreeModel().getNodeById(((TreeEvent)event).getNodeId());
+				if(selTreeNode != null) {
+					contentCtr = pushController(ureq, selTreeNode);
+				}
 			} else { // the action was not allowed anymore
 				content.popUpToRootController(ureq);
 			}
diff --git a/src/test/java/org/olat/restapi/CoursesFoldersTest.java b/src/test/java/org/olat/restapi/CoursesFoldersTest.java
index 7a17016f1b894dda53312660b7351b34a1f8425e..53b06f5ad4279eab5ad3adb73a0431a6648a37f1 100644
--- a/src/test/java/org/olat/restapi/CoursesFoldersTest.java
+++ b/src/test/java/org/olat/restapi/CoursesFoldersTest.java
@@ -219,6 +219,20 @@ public class CoursesFoldersTest extends OlatJerseyTestCase {
 		assertTrue(item2 instanceof VFSContainer);
 	}
 	
+	@Test
+	public void testCreateFolders_tooMany() throws IOException, URISyntaxException {
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		URI uri = UriBuilder.fromUri(getNodeURI()).path("files").path("RootFolder")
+				.path("Folder").path("Folder").path("Folder").path("Folder").path("Folder")
+				.path("Folder").path("Folder").path("Folder").path("Folder").path("Folder")
+				.path("Folder").path("Folder").path("Folder").path("Folder").path("Folder")
+				.path("Folder").path("Folder").path("Folder").path("Folder").path("Folder").build();
+		HttpPut method = conn.createPut(uri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		assertEquals(406, response.getStatusLine().getStatusCode());
+	}
+	
 	@Test
 	public void deleteFolder() throws IOException, URISyntaxException {
 		//add some folders