Skip to content
Snippets Groups Projects
Commit 282f6bb1 authored by srosse's avatar srosse
Browse files

Merge remote-tracking branch 'origin/OpenOLAT_13.2'

parents 3eee3c83 bd881d75
No related branches found
No related tags found
No related merge requests found
......@@ -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>
......
/**
* <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;
}
......@@ -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);
}
......
......@@ -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
......
......@@ -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;
......
......@@ -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'>");
......
......@@ -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);
}
......
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment