From 19337491f4078c74703c511b4ce5d0b9933c03bb Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Fri, 29 Mar 2019 15:58:37 +0100
Subject: [PATCH] OO-3941: Enable Collabora office in sample solutions

---
 .../ui/AbstractAssignmentEditController.java  |  45 +----
 .../ui/GTASampleSolutionsEditController.java  | 190 +++++++++++-------
 .../nodes/gta/ui/NewSolutionController.java   |  78 ++++++-
 .../olat/course/nodes/gta/ui/SolutionRow.java |   9 +-
 .../nodes/gta/ui/SolutionTableModel.java      |   4 +-
 .../gta/ui/component/ModeCellRenderer.java    |  58 ++++++
 6 files changed, 260 insertions(+), 124 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/gta/ui/component/ModeCellRenderer.java

diff --git a/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java
index 624ed26bd8f..971d6d907d7 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java
@@ -25,7 +25,6 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.olat.core.commons.editor.htmleditor.HTMLEditorController;
 import org.olat.core.commons.services.filetemplate.FileTypes;
 import org.olat.core.commons.services.filetemplate.FileTypes.Builder;
 import org.olat.core.commons.services.notifications.NotificationsManager;
@@ -49,7 +48,6 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
-import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.ChiefController;
 import org.olat.core.gui.control.Controller;
@@ -73,6 +71,7 @@ import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.TaskList;
 import org.olat.course.nodes.gta.model.TaskDefinition;
 import org.olat.course.nodes.gta.ui.TaskDefinitionTableModel.TDCols;
+import org.olat.course.nodes.gta.ui.component.ModeCellRenderer;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.modules.ModuleConfiguration;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -93,7 +92,6 @@ abstract class AbstractAssignmentEditController extends FormBasicController {
 	private CloseableModalController cmc;
 	private NewTaskController newTaskCtrl;
 	private DialogBoxController confirmDeleteCtrl;
-	private HTMLEditorController newTaskEditorCtrl;
 	private EditHTMLController editHtmlCtrl;
 	private EditTaskController addTaskCtrl, editTaskCtrl;
 	private VFSLeafEditorController vfsLeafEditorCtrl;
@@ -237,19 +235,17 @@ abstract class AbstractAssignmentEditController extends FormBasicController {
 				doOpen(ureq, newTask, EDIT);
 				updateModel();
 			} 
-		} else if(newTaskEditorCtrl == source) {
+		} else if(editHtmlCtrl == source) {
 			if(event == Event.DONE_EVENT) {
-				updateModel();
-				//fireEvent(ureq, Event.DONE_EVENT);
 				gtaManager.markNews(courseEnv, gtaNode);
 			}
-			cmc.deactivate();
-			cleanUp();
-		} else if(editHtmlCtrl == source) {
+			updateModel();
 			doCloseFullscreen();
 			cleanUp();
 		} else if (source == vfsLeafEditorCtrl) {
 			if(event == Event.DONE_EVENT) {
+				gtaManager.markNews(courseEnv, gtaNode);
+				updateModel();
 				doCloseFullscreen();
 				cleanUp();
 			}
@@ -456,35 +452,4 @@ abstract class AbstractAssignmentEditController extends FormBasicController {
 			}
 		}
 	}
-	
-
-	public class ModeCellRenderer extends StaticFlexiCellRenderer {
-
-		public ModeCellRenderer(String action) {
-			super("", action);
-		}
-
-		@Override
-		public void render(Renderer renderer, StringOutput target, Object cellValue, int row,
-				FlexiTableComponent source, URLBuilder ubu, Translator translator) {
-			if (cellValue instanceof Mode) {
-				Mode mode = (Mode) cellValue;
-				switch (mode) {
-				case EDIT:
-					setIconLeftCSS("o_icon_edit o_icon-lg");
-					break;
-				case VIEW:
-					setIconLeftCSS("o_icon_preview o_icon-lg");
-					break;
-				default:
-					setIconLeftCSS(null);
-					break;
-				}
-			} else {
-				setIconLeftCSS(null);
-			}
-			super.render(renderer, target, cellValue, row, source, ubu, translator);
-		}
-
-	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java
index 5bbf2e75aad..0119c7b5d47 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java
@@ -19,14 +19,20 @@
  */
 package org.olat.course.nodes.gta.ui;
 
+import static org.olat.core.commons.services.vfs.VFSLeafEditor.Mode.EDIT;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.olat.core.commons.editor.htmleditor.HTMLEditorController;
-import org.olat.core.commons.editor.htmleditor.WysiwygFactory;
+import org.olat.core.commons.services.filetemplate.FileTypes;
+import org.olat.core.commons.services.filetemplate.FileTypes.Builder;
+import org.olat.core.commons.services.vfs.VFSLeafEditor.Mode;
+import org.olat.core.commons.services.vfs.VFSLeafEditorSecurityCallback;
+import org.olat.core.commons.services.vfs.VFSLeafEditorSecurityCallbackBuilder;
 import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.commons.services.vfs.VFSRepositoryService;
+import org.olat.core.commons.services.vfs.ui.editor.VFSLeafEditorController;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -35,26 +41,27 @@ import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
 import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
-import org.olat.core.gui.components.form.flexible.impl.elements.table.BooleanCellRenderer;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
-import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
 import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.control.ChiefController;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.ScreenMode;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.util.FileUtils;
 import org.olat.core.util.vfs.VFSConstants;
 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.VFSManager;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.model.Solution;
 import org.olat.course.nodes.gta.ui.SolutionTableModel.SolCols;
+import org.olat.course.nodes.gta.ui.component.ModeCellRenderer;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -76,14 +83,15 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 	private EditSolutionController addSolutionCtrl;
 	private EditSolutionController editSolutionCtrl;
 	private NewSolutionController newSolutionCtrl;
-	private HTMLEditorController newSolutionEditorCtrl;
-	private HTMLEditorController editSolutionEditorCtrl;
+	private EditHTMLController editHtmlCtrl;
+	private VFSLeafEditorController vfsLeafEditorCtrl;
 	
 	private final File solutionDir;
 	private final boolean readOnly;
 	private final GTACourseNode gtaNode;
 	private final CourseEnvironment courseEnv;
 	private final VFSContainer solutionContainer;
+	private final Long courseRepoKey;
 	
 	private int linkCounter = 0;
 	
@@ -92,7 +100,7 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 	@Autowired
 	private GTAManager gtaManager;
 	@Autowired
-	private VFSRepositoryService vfsRepositoryService;
+	private VFSRepositoryService vfsService;
 	
 	public GTASampleSolutionsEditController(UserRequest ureq, WindowControl wControl, GTACourseNode gtaNode,
 			CourseEnvironment courseEnv, boolean readOnly) {
@@ -100,6 +108,7 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 		this.gtaNode = gtaNode;
 		this.readOnly = readOnly;
 		this.courseEnv = courseEnv;
+		this.courseRepoKey = courseEnv.getCourseGroupManager().getCourseEntry().getKey();
 		solutionDir = gtaManager.getSolutionsDirectory(courseEnv, gtaNode);
 		solutionContainer = gtaManager.getSolutionsContainer(courseEnv, gtaNode);
 		initForm(ureq);
@@ -121,12 +130,12 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(SolCols.title.i18nKey(), SolCols.title.ordinal()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(SolCols.file.i18nKey(), SolCols.file.ordinal()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(SolCols.author.i18nKey(), SolCols.author.ordinal()));
+		
+		String openI18n = readOnly? "table.header.view": "table.header.edit";
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(openI18n, SolCols.mode.ordinal(), "open", new ModeCellRenderer("open")));
 		if(!readOnly) {
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.header.edit", SolCols.edit.ordinal(), "edit",
-					new BooleanCellRenderer(
-							new StaticFlexiCellRenderer(translate("edit"), "edit"),
-							new StaticFlexiCellRenderer(translate("replace"), "edit"))));
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.header.edit", translate("delete"), "delete"));
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.header.metadata", translate("table.header.metadata"), "metadata"));
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.header.delete", translate("table.header.delete"), "delete"));
 		}
 
 		solutionModel = new SolutionTableModel(columnsModel);
@@ -141,6 +150,7 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 		for(Solution solution:solutionList) {
 			String filename = solution.getFilename();
 			String author = null;
+			Mode openMode = null;
 			
 			VFSItem item = solutionContainer.resolve(filename);
 			if(item.canMeta() == VFSConstants.YES) {
@@ -152,16 +162,33 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 			
 			DownloadLink downloadLink = null;
 			if(item instanceof VFSLeaf) {
+				VFSLeaf vfsLeaf = (VFSLeaf)item;
 				downloadLink = uifactory
-					.addDownloadLink("file_" + (++linkCounter), filename, null, (VFSLeaf)item, solutionTable);
+					.addDownloadLink("file_" + (++linkCounter), filename, null, vfsLeaf, solutionTable);
+				openMode = getOpenMode(vfsLeaf);
 			}
 
-			rows.add(new SolutionRow(solution, author, downloadLink));
+			rows.add(new SolutionRow(solution, author, downloadLink, openMode));
 		}
 		solutionModel.setObjects(rows);
 		solutionTable.reset();
 	}
 	
+	private Mode getOpenMode(VFSLeaf vfsLeaf) {
+		if (FileUtils.getFileSuffix(vfsLeaf.getName()).equals("html")) {
+			if (!readOnly) {
+				return Mode.EDIT;
+			}
+			return Mode.VIEW;
+		}
+		if (!readOnly && vfsService.hasEditor(vfsLeaf, Mode.EDIT)) {
+			return Mode.EDIT;
+		} else if (vfsService.hasEditor(vfsLeaf, Mode.VIEW)) {
+			return Mode.VIEW;
+		}
+		return null;
+	}
+	
 	@Override
 	protected void doDispose() {
 		//
@@ -195,23 +222,24 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 			
 			if(event == Event.DONE_EVENT) {
 				gtaManager.addSolution(newSolution, courseEnv, gtaNode);
-				doCreateSolutionEditor(ureq, newSolution);
+				doOpen(ureq, newSolution, Mode.EDIT);
 				updateModel();
 				gtaManager.markNews(courseEnv, gtaNode);
 			}
-		} else if(newSolutionEditorCtrl == source) {
+		} else if(editHtmlCtrl == source) {
 			if(event == Event.DONE_EVENT) {
-				updateModel();
-				fireEvent(ureq, Event.DONE_EVENT);
 				gtaManager.markNews(courseEnv, gtaNode);
 			}
-			cmc.deactivate();
-			cleanUp();
-		} else if(editSolutionEditorCtrl == source) {
-			// edit solution cannot update the title or the description
-			gtaManager.markNews(courseEnv, gtaNode);
-			cmc.deactivate();
+			updateModel();
+			doCloseFullscreen();
 			cleanUp();
+		} else if (source == vfsLeafEditorCtrl) {
+			if(event == Event.DONE_EVENT) {
+				gtaManager.markNews(courseEnv, gtaNode);
+				updateModel();
+				doCloseFullscreen();
+				cleanUp();
+			}
 		} else if(cmc == source) {
 			cleanUp();
 		}
@@ -219,13 +247,24 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 	}
 	
 	private void cleanUp() {
+		removeAsListenerAndDispose(vfsLeafEditorCtrl);
 		removeAsListenerAndDispose(editSolutionCtrl);
 		removeAsListenerAndDispose(addSolutionCtrl);
+		removeAsListenerAndDispose(editHtmlCtrl);
 		removeAsListenerAndDispose(cmc);
+		vfsLeafEditorCtrl = null;
 		editSolutionCtrl = null;
 		addSolutionCtrl = null;
+		editHtmlCtrl = null;
 		cmc = null;
 	}
+	
+	private void doCloseFullscreen() {
+		getWindowControl().pop();
+		String businessPath = getWindowControl().getBusinessControl().getAsString();
+		getWindowControl().getWindowBackOffice().getChiefController().getScreenMode().setMode(ScreenMode.Mode.standard, businessPath);
+		cleanUp();
+	}
 
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
@@ -237,8 +276,10 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 			if(event instanceof SelectionEvent) {
 				SelectionEvent se = (SelectionEvent)event;
 				SolutionRow row = solutionModel.getObject(se.getIndex());
-				if("edit".equals(se.getCommand())) {
-					doEdit(ureq, row.getSolution());
+				if("open".equals(se.getCommand())) {
+					doOpen(ureq, row.getSolution(), row.getMode());
+				} else if("metadata".equals(se.getCommand())) {
+					doEditmetadata(ureq, row.getSolution());
 				} else if("delete".equals(se.getCommand())) {
 					doDelete(ureq, row);
 				}
@@ -252,13 +293,48 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 		//
 	}
 	
-	private void doEdit(UserRequest ureq, Solution solution) {
+	private void doOpen(UserRequest ureq, Solution solution, Mode mode) {
 		if(solution.getFilename().endsWith(".html")) {
-			doEditSolutionEditor(ureq, solution);
+			doEditHtml(ureq, solution);
+		} else {
+			doEditVfsEditor(ureq, solution, mode);
+		}	
+	}
+	
+	@SuppressWarnings("deprecation")
+	private void doEditHtml(UserRequest ureq, Solution solution) {
+		VFSItem htmlDocument = solutionContainer.resolve(solution.getFilename());
+		if(htmlDocument == null || !(htmlDocument instanceof VFSLeaf)) {
+			showError("error.missing.file");
 		} else {
-			doReplace(ureq, solution);
+			editHtmlCtrl = new EditHTMLController(ureq, getWindowControl(), solutionContainer,
+					(VFSLeaf) htmlDocument, courseRepoKey, readOnly);
+			listenTo(editHtmlCtrl);
+			
+			ChiefController cc = getWindowControl().getWindowBackOffice().getChiefController();
+			String businessPath = editHtmlCtrl.getWindowControlForDebug().getBusinessControl().getAsString();
+			cc.getScreenMode().setMode(ScreenMode.Mode.full, businessPath);
+			getWindowControl().pushToMainArea(editHtmlCtrl.getInitialComponent());
+		}
+	}
+
+	@SuppressWarnings("deprecation")
+	private void doEditVfsEditor(UserRequest ureq, Solution solution, Mode mode) {
+		VFSItem vfsItem = solutionContainer.resolve(solution.getFilename());
+		if(vfsItem == null || !(vfsItem instanceof VFSLeaf)) {
+			showError("error.missing.file");
+		} else {
+			VFSLeafEditorSecurityCallback secCallback = VFSLeafEditorSecurityCallbackBuilder.builder()
+					.withMode(mode)
+					.build();
+			vfsLeafEditorCtrl = new VFSLeafEditorController(ureq, getWindowControl(), (VFSLeaf)vfsItem, null, secCallback);
+			listenTo(vfsLeafEditorCtrl);
+			
+			ChiefController cc = getWindowControl().getWindowBackOffice().getChiefController();
+			String businessPath = vfsLeafEditorCtrl.getWindowControlForDebug().getBusinessControl().getAsString();
+			cc.getScreenMode().setMode(ScreenMode.Mode.full, businessPath);
+			getWindowControl().pushToMainArea(vfsLeafEditorCtrl.getInitialComponent());
 		}
-		
 	}
 
 	private void doAddSolution(UserRequest ureq) {
@@ -271,7 +347,7 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 		cmc.activate();
 	}
 	
-	private void doReplace(UserRequest ureq, Solution solution) {
+	private void doEditmetadata(UserRequest ureq, Solution solution) {
 		editSolutionCtrl = new EditSolutionController(ureq, getWindowControl(), solution, solutionDir, solutionContainer);
 		listenTo(editSolutionCtrl);
 
@@ -282,7 +358,7 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 	}
 	
 	private void doCreateSolution(UserRequest ureq) {
-		newSolutionCtrl = new NewSolutionController(ureq, getWindowControl(), solutionContainer);
+		newSolutionCtrl = new NewSolutionController(ureq, getWindowControl(), solutionContainer, htmlOffice());
 		listenTo(newSolutionCtrl);
 		
 		cmc = new CloseableModalController(getWindowControl(), "close", newSolutionCtrl.getInitialComponent());
@@ -290,46 +366,18 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 		cmc.activate();
 	}
 	
-	private void doCreateSolutionEditor(UserRequest ureq, Solution solution) {
-		String documentName = solution.getFilename();
-		VFSItem item = solutionContainer.resolve(documentName);
-		if(item == null) {
-			item = solutionContainer.createChildLeaf(documentName);
-		} else {
-			documentName = VFSManager.rename(solutionContainer, documentName);
-			item = solutionContainer.createChildLeaf(documentName);
+	private FileTypes htmlOffice() {
+		Builder builder = FileTypes.builder(getLocale());
+		if (vfsService.hasEditor("html", EDIT)) {
+			builder.addHtml();
 		}
-		if(item.canMeta() == VFSConstants.YES) {
-			VFSMetadata metaInfo = item.getMetaInfo();
-			metaInfo.setAuthor(getIdentity());
-			vfsRepositoryService.updateMetadata(metaInfo);
+		if (vfsService.hasEditor("docx", EDIT)) {
+			builder.addDocx();
 		}
-
-		newSolutionEditorCtrl = WysiwygFactory.createWysiwygController(ureq, getWindowControl(),
-				solutionContainer, documentName, "media", true, true);
-		newSolutionEditorCtrl.getRichTextConfiguration().disableMedia();
-		newSolutionEditorCtrl.getRichTextConfiguration().setAllowCustomMediaFactory(false);
-		newSolutionEditorCtrl.setNewFile(true);
-		newSolutionEditorCtrl.setUserObject(solution);
-		listenTo(newSolutionEditorCtrl);
-		
-		cmc = new CloseableModalController(getWindowControl(), "close", newSolutionEditorCtrl.getInitialComponent());
-		listenTo(cmc);
-		cmc.activate();
-	}
-	
-	private void doEditSolutionEditor(UserRequest ureq, Solution solution) {
-		String documentName = solution.getFilename();
-
-		editSolutionEditorCtrl = WysiwygFactory.createWysiwygController(ureq, getWindowControl(),
-				solutionContainer, documentName, "media", true, true);
-		editSolutionEditorCtrl.getRichTextConfiguration().disableMedia();
-		editSolutionEditorCtrl.getRichTextConfiguration().setAllowCustomMediaFactory(false);
-		listenTo(editSolutionEditorCtrl);
-		
-		cmc = new CloseableModalController(getWindowControl(), "close", editSolutionEditorCtrl.getInitialComponent());
-		listenTo(cmc);
-		cmc.activate();
+		if (vfsService.hasEditor("xlsx", EDIT)) {
+			builder.addXlsx();
+		}
+		return builder.build();
 	}
 	
 	private void doDelete(UserRequest ureq, SolutionRow solution) {
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/NewSolutionController.java b/src/main/java/org/olat/course/nodes/gta/ui/NewSolutionController.java
index c56951e1eed..b846564d53f 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/NewSolutionController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/NewSolutionController.java
@@ -19,8 +19,15 @@
  */
 package org.olat.course.nodes.gta.ui;
 
+import java.util.List;
+
+import org.olat.core.commons.services.filetemplate.FileType;
+import org.olat.core.commons.services.filetemplate.FileTypes;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
@@ -29,8 +36,13 @@ import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.util.FileUtils;
 import org.olat.core.util.StringHelper;
+import org.olat.core.util.vfs.VFSConstants;
 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.VFSManager;
 import org.olat.course.nodes.gta.model.Solution;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
@@ -41,11 +53,17 @@ import org.olat.course.nodes.gta.model.Solution;
 public class NewSolutionController extends FormBasicController {
 	
 	private TextElement filenameEl, titleEl;
+	private SingleSelection fileTypeEl;
 	private final VFSContainer documentContainer;
+	private final List<FileType> fileTypes;
+	
+	@Autowired
+	private VFSRepositoryService vfsService;
 	
-	public NewSolutionController(UserRequest ureq, WindowControl wControl, VFSContainer documentContainer) {
+	public NewSolutionController(UserRequest ureq, WindowControl wControl, VFSContainer documentContainer, FileTypes fileTypes) {
 		super(ureq, wControl);
 		this.documentContainer = documentContainer;
+		this.fileTypes = fileTypes.getFileTypes();
 		initForm(ureq);
 	}
 
@@ -56,6 +74,23 @@ public class NewSolutionController extends FormBasicController {
 		titleEl = uifactory.addTextElement("title", "task.title", 128, "", formLayout);
 		titleEl.setElementCssClass("o_sel_course_gta_upload_task_title");
 		titleEl.setMandatory(true);
+		
+		String[] fileTypeKeys = new String[fileTypes.size()];
+		String[] fileTypeValues = new String[fileTypes.size()];
+		String[] fileTypeSuffix = new String[fileTypes.size()];
+		for (int i = 0; i < fileTypes.size(); i++) {
+			FileType fileType = fileTypes.get(i);
+			String name = fileType.getName() + " (." + fileType.getSuffix() + ")";
+			fileTypeKeys[i] = String.valueOf(i);
+			fileTypeValues[i] = name;
+			fileTypeSuffix[i] = fileType.getSuffix();
+		}
+		fileTypeEl = uifactory.addDropdownSingleselect("file.type", formLayout, fileTypeKeys, fileTypeValues, fileTypeSuffix);
+		fileTypeEl.setElementCssClass("o_sel_course_gta_doc_filetype");
+		fileTypeEl.setMandatory(true);
+		if (fileTypes.size() == 1) {
+			fileTypeEl.setVisible(false);
+		}
 
 		filenameEl = uifactory.addTextElement("fileName", "file.name", -1, "", formLayout);
 		filenameEl.setElementCssClass("o_sel_course_gta_doc_filename");
@@ -71,6 +106,8 @@ public class NewSolutionController extends FormBasicController {
 		String jsPage = velocity_root + "/new_task_js.html";
 		FormLayoutContainer jsCont = FormLayoutContainer.createCustomFormLayout("js", getTranslator(), jsPage);
 		jsCont.contextPut("titleId", titleEl.getFormDispatchId());
+		jsCont.contextPut("filetypeId", fileTypeEl.getFormDispatchId());
+		jsCont.contextPut("filetypeDefaultSuffix", fileTypes.get(0).getSuffix());
 		jsCont.contextPut("filenameId", filenameEl.getFormDispatchId());
 		formLayout.add(jsCont);
 	}
@@ -80,15 +117,18 @@ public class NewSolutionController extends FormBasicController {
 		//
 	}
 	
-	public String getFilename() {
-		String value = filenameEl.getValue();
-		String lowerCased = value.toLowerCase();
-		if(!lowerCased.endsWith(".xhtm")
-				&& !lowerCased.endsWith(".html")
-				&& !lowerCased.endsWith(".htm")) {
-			value += ".html";
-		}
-		return value;
+	private String getFilename() {
+		String fileName = filenameEl.getValue().toLowerCase();
+		FileType fileType = getSelectedFileType();
+		String suffix = fileType != null? fileType.getSuffix(): "";
+		return fileName.endsWith("." + suffix)
+				? fileName
+				: fileName + "." + suffix;
+	}
+
+	private FileType getSelectedFileType() {
+		int index = fileTypeEl.getSelected();
+		return index >= 0? fileTypes.get(index): fileTypes.get(0);
 	}
 	
 	public Solution getSolution() {
@@ -129,6 +169,24 @@ public class NewSolutionController extends FormBasicController {
 
 	@Override
 	protected void formOK(UserRequest ureq) {
+		String documentName = getFilename();
+		VFSItem item = documentContainer.resolve(documentName);
+		VFSLeaf vfsLeaf = null;
+		if(item == null) {
+			vfsLeaf = documentContainer.createChildLeaf(documentName);
+		} else {
+			documentName = VFSManager.rename(documentContainer, documentName);
+			vfsLeaf = documentContainer.createChildLeaf(documentName);
+		}
+		FileType fileType = getSelectedFileType();
+		if (fileType != null) {
+			VFSManager.copyContent(fileType.getContentProvider().getContent(), vfsLeaf);
+		}
+		if(vfsLeaf.canMeta() == VFSConstants.YES) {
+			VFSMetadata metaInfo = vfsLeaf.getMetaInfo();
+			metaInfo.setAuthor(getIdentity());
+			vfsService.updateMetadata(metaInfo);
+		}
 		fireEvent(ureq, Event.DONE_EVENT);
 	}
 
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SolutionRow.java b/src/main/java/org/olat/course/nodes/gta/ui/SolutionRow.java
index ae9f860b38c..068aacdb9cb 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/SolutionRow.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/SolutionRow.java
@@ -19,6 +19,7 @@
  */
 package org.olat.course.nodes.gta.ui;
 
+import org.olat.core.commons.services.vfs.VFSLeafEditor.Mode;
 import org.olat.core.gui.components.form.flexible.elements.DownloadLink;
 import org.olat.course.nodes.gta.model.Solution;
 
@@ -33,11 +34,13 @@ public class SolutionRow {
 	private final Solution solution;
 	private final String author;
 	private final DownloadLink downloadLink;
+	private final Mode mode;
 	
-	public SolutionRow(Solution solution, String author, DownloadLink downloadLink) {
+	public SolutionRow(Solution solution, String author, DownloadLink downloadLink, Mode mode) {
 		this.solution = solution;
 		this.author = author;
 		this.downloadLink = downloadLink;
+		this.mode = mode;
 	}
 
 	public Solution getSolution() {
@@ -51,4 +54,8 @@ public class SolutionRow {
 	public DownloadLink getDownloadLink() {
 		return downloadLink;
 	}
+
+	public Mode getMode() {
+		return mode;
+	}
 }
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SolutionTableModel.java b/src/main/java/org/olat/course/nodes/gta/ui/SolutionTableModel.java
index b2c4168ebdd..d707844be98 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/SolutionTableModel.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/SolutionTableModel.java
@@ -47,7 +47,7 @@ public class SolutionTableModel extends DefaultFlexiTableDataModel<SolutionRow>
 			case file: return solutionRow.getDownloadLink() == null
 					? solutionRow.getSolution().getFilename() : solutionRow.getDownloadLink();
 			case author: return solutionRow.getAuthor();
-			case edit: return solutionRow.getSolution().getFilename().endsWith(".html");
+			case mode: return solutionRow.getMode();
 			default: return "ERROR";
 		}
 	}
@@ -56,7 +56,7 @@ public class SolutionTableModel extends DefaultFlexiTableDataModel<SolutionRow>
 		title("task.title"),
 		file("task.file"),
 		author("table.header.author"),
-		edit("table.header.edit");
+		mode("table.header.edit");
 		
 		private final String i18nKey;
 	
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/component/ModeCellRenderer.java b/src/main/java/org/olat/course/nodes/gta/ui/component/ModeCellRenderer.java
new file mode 100644
index 00000000000..cef42b462e4
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/component/ModeCellRenderer.java
@@ -0,0 +1,58 @@
+/**
+ * <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.course.nodes.gta.ui.component;
+
+import org.olat.core.commons.services.vfs.VFSLeafEditor.Mode;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
+import org.olat.core.gui.render.Renderer;
+import org.olat.core.gui.render.StringOutput;
+import org.olat.core.gui.render.URLBuilder;
+import org.olat.core.gui.translator.Translator;
+
+public class ModeCellRenderer extends StaticFlexiCellRenderer {
+
+	public ModeCellRenderer(String action) {
+		super("", action);
+	}
+
+	@Override
+	public void render(Renderer renderer, StringOutput target, Object cellValue, int row,
+			FlexiTableComponent source, URLBuilder ubu, Translator translator) {
+		if (cellValue instanceof Mode) {
+			Mode mode = (Mode) cellValue;
+			switch (mode) {
+			case EDIT:
+				setIconLeftCSS("o_icon_edit o_icon-lg");
+				break;
+			case VIEW:
+				setIconLeftCSS("o_icon_preview o_icon-lg");
+				break;
+			default:
+				setIconLeftCSS(null);
+				break;
+			}
+		} else {
+			setIconLeftCSS(null);
+		}
+		super.render(renderer, target, cellValue, row, source, ubu, translator);
+	}
+
+}
\ No newline at end of file
-- 
GitLab