From 82ac2dbb0609c3430c987e347ee57567f6dff365 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 16 Jul 2014 11:15:47 +0200
Subject: [PATCH] OO-666: edit tags in map view (if allowed)

---
 .../olat/portfolio/EPSecurityCallback.java    |   2 +
 .../portfolio/EPSecurityCallbackFactory.java  |  14 +-
 .../portfolio/EPSecurityCallbackImpl.java     |  10 +-
 .../portfolio/EPSecurityCallbackOwner.java    |   7 +-
 .../java/org/olat/portfolio/EPUIFactory.java  |  16 ---
 .../collect/EPCollectStepForm01.java          |  56 ++++----
 .../collect/_content/step01tagging.html       |   6 +
 .../edit/EPReflexionWrapperController.java    |   5 +-
 .../ui/artefacts/edit/EPTagsController.java   | 128 ++++++++++++++++++
 .../EPArtefactViewOptionsLinkController.java  |  30 ++--
 .../EPArtefactViewReadOnlyController.java     |  29 ++--
 .../EPMultipleArtefactsAsTableController.java |   4 +-
 .../artefacts/view/EPTagViewController.java   |  79 +++++++++++
 .../view/_content/artefactOptions.html        |   1 +
 .../view/_i18n/LocalStrings_de.properties     |   2 +
 .../view/_i18n/LocalStrings_en.properties     |   2 +
 .../view/_i18n/LocalStrings_fr.properties     |   2 +
 17 files changed, 321 insertions(+), 72 deletions(-)
 create mode 100644 src/main/java/org/olat/portfolio/ui/artefacts/edit/EPTagsController.java
 create mode 100644 src/main/java/org/olat/portfolio/ui/artefacts/view/EPTagViewController.java

diff --git a/src/main/java/org/olat/portfolio/EPSecurityCallback.java b/src/main/java/org/olat/portfolio/EPSecurityCallback.java
index 1de04d4562a..3c32af6d6a6 100644
--- a/src/main/java/org/olat/portfolio/EPSecurityCallback.java
+++ b/src/main/java/org/olat/portfolio/EPSecurityCallback.java
@@ -39,6 +39,8 @@ public interface EPSecurityCallback {
 	
 	public boolean canEditReflexion();
 	
+	public boolean canEditTags();
+	
 	public boolean canShareMap();
 	
 	public boolean canAddArtefact();
diff --git a/src/main/java/org/olat/portfolio/EPSecurityCallbackFactory.java b/src/main/java/org/olat/portfolio/EPSecurityCallbackFactory.java
index 889ce46bfe7..bf3c8a317c9 100644
--- a/src/main/java/org/olat/portfolio/EPSecurityCallbackFactory.java
+++ b/src/main/java/org/olat/portfolio/EPSecurityCallbackFactory.java
@@ -63,6 +63,7 @@ public class EPSecurityCallbackFactory {
 	public static EPSecurityCallback updateAfterFailedLock(EPSecurityCallback secCallback) {
 		boolean canEditStructure = false;
 		boolean canEditReflexion = false;
+		boolean canEditTags = false;
 		boolean canShare = secCallback.canShareMap();
 		boolean canAddArtefact = false;
 		boolean canRemoveArtefactFromStruct = false;
@@ -74,7 +75,7 @@ public class EPSecurityCallbackFactory {
 		boolean restrictionsEnabled = secCallback.isRestrictionsEnabled();
 		boolean isOwner = secCallback.isOwner();
 
-		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
+		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canEditTags, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
 				canView, canCommentAndRate, canSubmitAssess, restrictionsEnabled, isOwner);
 	}
 	
@@ -91,6 +92,7 @@ public class EPSecurityCallbackFactory {
 		
 		boolean canEditStructure = isOwner;
 		boolean canEditReflexion = isOwner;
+		boolean canEditTags = isOwner;
 		boolean canShare = isOwner;
 		boolean canAddArtefact = isOwner;
 		boolean canRemoveArtefactFromStruct = isOwner;
@@ -101,7 +103,7 @@ public class EPSecurityCallbackFactory {
 		boolean canSubmitAssess = false;
 		boolean restrictionsEnabled = false;
 		
-		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
+		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canEditTags, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
 				canView, canCommentAndRate, canSubmitAssess, restrictionsEnabled, isOwner);
 	}
 	
@@ -120,6 +122,7 @@ public class EPSecurityCallbackFactory {
 		
 		boolean canEditStructure = false;
 		boolean canEditReflexion = isOwner && open;
+		boolean canEditTags = isOwner && open;
 		boolean canShare = (isOwner || isCoach);
 		boolean canAddArtefact = isOwner && open;
 		boolean canRemoveArtefactFromStruct = isOwner && open;
@@ -130,7 +133,7 @@ public class EPSecurityCallbackFactory {
 		boolean canSubmitAssess = isOwner;
 		boolean restrictionsEnabled = true;
 		
-		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
+		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canEditTags, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
 				canView, canCommentAndRate, canSubmitAssess, restrictionsEnabled, isOwner);
 	}
 	
@@ -154,7 +157,8 @@ public class EPSecurityCallbackFactory {
 		boolean open = !StructureStatusEnum.CLOSED.equals(map.getStatus());
 		
 		boolean canEditStructure = (isOwner || isAdmin) && open;
-		boolean canEditReflexion = (isOwner) && open;
+		boolean canEditReflexion = isOwner && open;
+		boolean canEditTags = isOwner && open;
 		boolean canShare = false;
 		boolean canAddArtefact = false; // (isOwner || isAdmin) && open;
 		boolean canRemoveArtefactFromStruct = (isOwner || isAdmin) && open;
@@ -165,7 +169,7 @@ public class EPSecurityCallbackFactory {
 		boolean canSubmitAssess = false;
 		boolean restrictionsEnabled = true;//for author
 
-		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
+		return new EPSecurityCallbackImpl(canEditStructure, canEditReflexion, canEditTags, canShare, canAddArtefact, canRemoveArtefactFromStruct, canAddStructure, canAddPage,
 				canView, canCommentAndRate, canSubmitAssess, restrictionsEnabled, isOwner);
 	}
 }
diff --git a/src/main/java/org/olat/portfolio/EPSecurityCallbackImpl.java b/src/main/java/org/olat/portfolio/EPSecurityCallbackImpl.java
index a082358d145..8702308b208 100644
--- a/src/main/java/org/olat/portfolio/EPSecurityCallbackImpl.java
+++ b/src/main/java/org/olat/portfolio/EPSecurityCallbackImpl.java
@@ -33,6 +33,7 @@ public class EPSecurityCallbackImpl implements EPSecurityCallback {
 	
 	private final boolean canEditStructure;
 	private final boolean canEditReflexion;
+	private final boolean canEditTags;
 	private final boolean canShare;
 	private final boolean canAddArtefact;
 	private final boolean canRemoveArtefactFromStruct;
@@ -47,6 +48,7 @@ public class EPSecurityCallbackImpl implements EPSecurityCallback {
 	public EPSecurityCallbackImpl(boolean canEdit, boolean canView) {
 		this.canEditStructure = canEdit;
 		this.canEditReflexion = canEdit;
+		this.canEditTags = canEdit;
 		this.canShare = canEdit;
 		this.canAddArtefact = canEdit;
 		this.canRemoveArtefactFromStruct = canEdit;
@@ -59,10 +61,11 @@ public class EPSecurityCallbackImpl implements EPSecurityCallback {
 		this.isOwner = false;//TODO
 	}
 	
-	protected EPSecurityCallbackImpl(boolean canEditStructure, boolean canEditReflexion, boolean canShare, boolean canAddArtefact, boolean canRemoveArtefactFromStruct, boolean canAddStructure, boolean canAddPage,
+	protected EPSecurityCallbackImpl(boolean canEditStructure, boolean canEditReflexion, boolean canEditTags, boolean canShare, boolean canAddArtefact, boolean canRemoveArtefactFromStruct, boolean canAddStructure, boolean canAddPage,
 			boolean canView, boolean canCommentAndRate, boolean canSubmitAssess, boolean restrictionsEnabled, boolean isOwner) {
 		this.canEditStructure = canEditStructure;
 		this.canEditReflexion = canEditReflexion;
+		this.canEditTags = canEditTags;
 		this.canShare = canShare;
 		this.canAddArtefact = canAddArtefact;
 		this.canRemoveArtefactFromStruct = canRemoveArtefactFromStruct;
@@ -96,6 +99,11 @@ public class EPSecurityCallbackImpl implements EPSecurityCallback {
 	public boolean canEditReflexion() {
 		return canEditReflexion;
 	}
+	
+	@Override
+	public boolean canEditTags() {
+		return canEditTags;
+	}
 
 	@Override
 	public boolean canShareMap() {
diff --git a/src/main/java/org/olat/portfolio/EPSecurityCallbackOwner.java b/src/main/java/org/olat/portfolio/EPSecurityCallbackOwner.java
index c04eef21a7a..f9eeb075c2a 100644
--- a/src/main/java/org/olat/portfolio/EPSecurityCallbackOwner.java
+++ b/src/main/java/org/olat/portfolio/EPSecurityCallbackOwner.java
@@ -62,7 +62,12 @@ public class EPSecurityCallbackOwner implements EPSecurityCallback {
 	
 	@Override
 	public boolean canEditReflexion() {
-		return canEdit();
+		return owner;
+	}
+
+	@Override
+	public boolean canEditTags() {
+		return owner;
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/portfolio/EPUIFactory.java b/src/main/java/org/olat/portfolio/EPUIFactory.java
index 6640f06e757..3792a5d768f 100755
--- a/src/main/java/org/olat/portfolio/EPUIFactory.java
+++ b/src/main/java/org/olat/portfolio/EPUIFactory.java
@@ -36,7 +36,6 @@ import org.olat.portfolio.ui.EPMapRunController;
 import org.olat.portfolio.ui.EPMapRunViewOption;
 import org.olat.portfolio.ui.PortfolioAdminController;
 import org.olat.portfolio.ui.artefacts.collect.ArtefactWizzardStepsController;
-import org.olat.portfolio.ui.artefacts.edit.EPReflexionWrapperController;
 import org.olat.portfolio.ui.artefacts.view.EPArtefactViewController;
 import org.olat.portfolio.ui.artefacts.view.EPMultiArtefactsController;
 import org.olat.portfolio.ui.artefacts.view.EPMultipleArtefactSmallReadOnlyPreviewController;
@@ -161,19 +160,4 @@ public class EPUIFactory {
 		}
 		return null;
 	}
-	
-	/**
-	 * get the correct reflexion popup depending on permissions and mode as also context
-	 * @param ureq
-	 * @param wControl
-	 * @param secCallback
-	 * @param artefact
-	 * @param struct - might be null -> means not linked to a structure
-	 * @return
-	 */
-	public static Controller getReflexionPopup(UserRequest ureq, WindowControl wControl, EPSecurityCallback secCallback, AbstractArtefact artefact, PortfolioStructure struct){
-		return new EPReflexionWrapperController(ureq, wControl, secCallback, artefact, struct);
-	}
-
-
 }
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStepForm01.java b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStepForm01.java
index 381b10ec269..71865c88e1b 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStepForm01.java
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStepForm01.java
@@ -27,7 +27,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -42,11 +41,10 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
 import org.olat.core.gui.control.generic.wizard.StepsEvent;
 import org.olat.core.gui.control.generic.wizard.StepsRunContext;
-import org.olat.core.logging.OLog;
-import org.olat.core.logging.Tracing;
 import org.olat.core.util.StringHelper;
 import org.olat.portfolio.manager.EPFrontendManager;
 import org.olat.portfolio.model.artefacts.AbstractArtefact;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * Description:<br>
@@ -61,19 +59,22 @@ import org.olat.portfolio.model.artefacts.AbstractArtefact;
 public class EPCollectStepForm01 extends StepFormBasicController {
 
 	private AbstractArtefact artefact;
-	private EPFrontendManager ePFMgr;
 	private TextBoxListElement tagListElement;
+	@Autowired
+	private EPFrontendManager ePFMgr;
 
 	private static final String RUNCTX_TAGLIST_KEY = "artefactTagsList"; 
 	
-	private static OLog logger = Tracing.createLoggerFor(EPCollectStepForm01.class);
-
+	public EPCollectStepForm01(UserRequest ureq, WindowControl wControl, AbstractArtefact artefact) {
+		super(ureq, wControl, "step01tagging");
+		this.artefact = artefact;
+		initForm(ureq);
+	}
+	
 	public EPCollectStepForm01(UserRequest ureq, WindowControl wControl, Form rootForm, StepsRunContext runContext, AbstractArtefact artefact) {
 		super(ureq, wControl, rootForm, runContext, FormBasicController.LAYOUT_CUSTOM, "step01tagging");
-		ePFMgr = CoreSpringFactory.getImpl(EPFrontendManager.class);
-
 		this.artefact = artefact;
-		initForm(this.flc, this, ureq);
+		initForm(ureq);
 	}
 
 	/**
@@ -100,7 +101,12 @@ public class EPCollectStepForm01 extends StepFormBasicController {
 			userTagLinks.add(tagLink);
 			i++;
 		}
-		this.flc.contextPut("userTagLinks", userTagLinks);
+		flc.contextPut("userTagLinks", userTagLinks);
+		
+		if (!isUsedInStepWizzard()) {
+			// add form buttons
+			uifactory.addFormSubmitButton("stepform.submit", formLayout);
+		}
 	}
 
 	/**
@@ -121,7 +127,7 @@ public class EPCollectStepForm01 extends StepFormBasicController {
 		Collection<String> preSetArtefactTags = ePFMgr.getArtefactTags(artefact);
 
 		@SuppressWarnings("unchecked")
-		Collection<String> runContextTags = (List<String>) getFromRunContext(RUNCTX_TAGLIST_KEY);
+		Collection<String> runContextTags = isUsedInStepWizzard() ? (List<String>) getFromRunContext(RUNCTX_TAGLIST_KEY) : null;
 		if (runContextTags != null) {
 			// there are already tags in runContext, use those
 			tagCollection = runContextTags;
@@ -136,9 +142,6 @@ public class EPCollectStepForm01 extends StepFormBasicController {
 			}
 		}
 
-		if (logger.isDebug())
-			logger.debug("initForm -->  adding TextBoxListElement with tags: " + tagMap);
-
 		return tagMap;
 	}
 
@@ -152,8 +155,6 @@ public class EPCollectStepForm01 extends StepFormBasicController {
 		if (source == tagListElement) {
 			// nothing to do here, update dataModel on FormOK
 		} else if (source instanceof FormLink) {
-			if (logger.isDebug())
-				logger.debug("formInnerEvent -->  source is FormLink --> updating UI with newly added Tag from List");
 			
 			// user clicked on a tag in the "50 most used tags"-list
 			FormLink link = (FormLink) source;
@@ -163,7 +164,9 @@ public class EPCollectStepForm01 extends StepFormBasicController {
 				newTagFromLink = StringHelper.escapeHtml(newTagFromLink);
 				newTagFromLink = StringHelper.escapeJavaScript(newTagFromLink);
 				currentTagsInComponent.add(newTagFromLink);
-				addToRunContext(RUNCTX_TAGLIST_KEY, currentTagsInComponent);
+				if(isUsedInStepWizzard()) {
+					addToRunContext(RUNCTX_TAGLIST_KEY, currentTagsInComponent);
+				}
 				// refresh gui
 				flc.setDirty(true);
 				initForm(ureq);
@@ -176,14 +179,19 @@ public class EPCollectStepForm01 extends StepFormBasicController {
 	 */
 	@Override
 	protected void formOK(UserRequest ureq) {
-		List<String> actualTagList = tagListElement.getValueList();
-		if (actualTagList.size() != 0) {
-			addToRunContext(RUNCTX_TAGLIST_KEY, actualTagList);
+		if(isUsedInStepWizzard()) {
+			List<String> actualTagList = tagListElement.getValueList();
+			if (actualTagList.size() != 0) {
+				addToRunContext(RUNCTX_TAGLIST_KEY, actualTagList);
+			}	
+			// force repaint when navigating back and forth
+			flc.setDirty(true);
+			fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
+		} else {
+			List<String> tags = tagListElement.getValueList();
+			ePFMgr.setArtefactTags(ureq.getIdentity(), artefact, tags);
+			fireEvent(ureq, StepsEvent.DONE_EVENT);
 		}
-			
-		// force repaint when navigating back and forth
-		this.flc.setDirty(true);
-		fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/_content/step01tagging.html b/src/main/java/org/olat/portfolio/ui/artefacts/collect/_content/step01tagging.html
index 38e59a1462e..3c60f773bcc 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/_content/step01tagging.html
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/_content/step01tagging.html
@@ -20,4 +20,10 @@ $r.render("artefact.tags")
 	#foreach($tagLink in $propTagLinks)
 		$r.render($tagLink.getName())
 	#end
+#end
+
+#if($r.available("stepform.submit"))
+<div class="o_button_group">
+	$r.render("stepform.submit")
+</div>
 #end
\ No newline at end of file
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/edit/EPReflexionWrapperController.java b/src/main/java/org/olat/portfolio/ui/artefacts/edit/EPReflexionWrapperController.java
index 2fcffd2ab49..19b5b649325 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/edit/EPReflexionWrapperController.java
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/edit/EPReflexionWrapperController.java
@@ -61,8 +61,8 @@ public class EPReflexionWrapperController extends BasicController {
 	private PortfolioStructure struct;
 	private CloseableModalController reflexionBox;
 
-	public EPReflexionWrapperController(UserRequest ureq, WindowControl wControl, EPSecurityCallback secCallback, AbstractArtefact artefact,
-			PortfolioStructure struct) {
+	public EPReflexionWrapperController(UserRequest ureq, WindowControl wControl, EPSecurityCallback secCallback,
+			AbstractArtefact artefact, PortfolioStructure struct) {
 		super(ureq, wControl);
 		if (struct != null && struct.getRoot() instanceof PortfolioStructureMap) {
 			mapClosed = StructureStatusEnum.CLOSED.equals(((PortfolioStructureMap) struct.getRoot()).getStatus());
@@ -107,7 +107,6 @@ public class EPReflexionWrapperController extends BasicController {
 		removeAsListenerAndDispose(reflexionBox);
 		reflexionBox = new CloseableModalController(getWindowControl(), title, reflexionCtrl.getInitialComponent());
 		listenTo(reflexionBox);
-		//reflexionBox.setInitialWindowSize(550, 600);
 		reflexionBox.activate();
 	}
 
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/edit/EPTagsController.java b/src/main/java/org/olat/portfolio/ui/artefacts/edit/EPTagsController.java
new file mode 100644
index 00000000000..e3113a0c7c1
--- /dev/null
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/edit/EPTagsController.java
@@ -0,0 +1,128 @@
+/**
+ * <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.portfolio.ui.artefacts.edit;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
+import org.olat.core.util.Util;
+import org.olat.portfolio.EPSecurityCallback;
+import org.olat.portfolio.manager.EPFrontendManager;
+import org.olat.portfolio.model.artefacts.AbstractArtefact;
+import org.olat.portfolio.model.structel.PortfolioStructure;
+import org.olat.portfolio.model.structel.PortfolioStructureMap;
+import org.olat.portfolio.model.structel.StructureStatusEnum;
+import org.olat.portfolio.ui.artefacts.collect.EPCollectStepForm01;
+import org.olat.portfolio.ui.artefacts.view.EPArtefactViewController;
+import org.olat.portfolio.ui.artefacts.view.EPTagViewController;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 16.07.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EPTagsController extends StepFormBasicController {
+
+	private boolean mapClosed;
+	private EPSecurityCallback secCallback;
+	private PortfolioStructure struct;
+	private AbstractArtefact artefact;
+	
+	private Controller tagsCtrl;
+	private CloseableModalController tagsModalCtrl;
+	
+	@Autowired
+	private EPFrontendManager ePFMgr;
+
+	public EPTagsController(UserRequest ureq, WindowControl wControl, EPSecurityCallback secCallback,
+			AbstractArtefact artefact, PortfolioStructure struct) {
+		super(ureq, wControl);
+		this.artefact = artefact;
+		
+		if (struct != null && struct.getRoot() instanceof PortfolioStructureMap) {
+			mapClosed = StructureStatusEnum.CLOSED.equals(((PortfolioStructureMap) struct.getRoot()).getStatus());
+		} else {
+			mapClosed = false;
+		}
+		this.secCallback = secCallback;
+		this.artefact = artefact;
+		this.struct = struct;
+		setTranslator(Util.createPackageTranslator(EPArtefactViewController.class, ureq.getLocale(), getTranslator()));
+
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		removeAsListenerAndDispose(tagsCtrl);
+		removeAsListenerAndDispose(tagsModalCtrl);
+		
+		boolean artClosed = ePFMgr.isArtefactClosed(artefact);
+		if ( mapClosed || !secCallback.canEditTags() || (artClosed && struct == null)) {
+			// reflexion cannot be edited, view only!
+			tagsCtrl = new EPTagViewController(ureq, getWindowControl(), artefact);
+		} else {
+			tagsCtrl = new EPCollectStepForm01(ureq, getWindowControl(), artefact);
+		}
+		listenTo(tagsCtrl);
+
+		String title = translate("artefact.tags.title");
+		tagsModalCtrl = new CloseableModalController(getWindowControl(), title, tagsCtrl.getInitialComponent(), true, title);
+		listenTo(tagsModalCtrl);
+		tagsModalCtrl.activate();
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(tagsModalCtrl == source) {
+			cleanUp();
+		} else if(tagsCtrl == source) {
+			tagsModalCtrl.deactivate();
+			cleanUp();
+			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				fireEvent(ureq, event);
+			}
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(tagsCtrl);
+		removeAsListenerAndDispose(tagsModalCtrl);
+		tagsCtrl = null;
+		tagsModalCtrl = null;
+	}
+
+	@Override
+	protected void doDispose() {
+		// nothing
+	}
+}
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewOptionsLinkController.java b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewOptionsLinkController.java
index 7af264a341d..feb415f8c18 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewOptionsLinkController.java
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewOptionsLinkController.java
@@ -36,6 +36,7 @@ import org.olat.portfolio.model.artefacts.AbstractArtefact;
 import org.olat.portfolio.model.structel.PortfolioStructure;
 import org.olat.portfolio.ui.artefacts.collect.EPCollectStepForm04;
 import org.olat.portfolio.ui.artefacts.edit.EPReflexionWrapperController;
+import org.olat.portfolio.ui.artefacts.edit.EPTagsController;
 import org.olat.portfolio.ui.structel.EPStructureChangeEvent;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -49,7 +50,6 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class EPArtefactViewOptionsLinkController extends BasicController {
 
-	
 	private final AbstractArtefact artefact;
 	private PortfolioStructure struct;
 	private final EPSecurityCallback secCallback;
@@ -60,11 +60,10 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 	//controllers
 	private EPCollectStepForm04 moveTreeCtrl;
 	private CloseableModalController moveTreeBox;
-	private Controller reflexionCtrl;
+	private Controller tagsCtrl;
+	private EPReflexionWrapperController reflexionCtrl;
 	private CloseableCalloutWindowController artefactOptionCalloutCtrl;
 	
-	
-
 	// the link that triggers the callout
 	private Link optionLink;
 
@@ -72,7 +71,7 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 	private Link unlinkLink;
 	private Link moveLink;
 	private Link reflexionLink;
-	
+	private Link tagsLink;
 	
 	public EPArtefactViewOptionsLinkController(final UserRequest ureq, final WindowControl wControl, final AbstractArtefact artefact,
 			final EPSecurityCallback secCallback, final PortfolioStructure struct){
@@ -90,7 +89,6 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 		putInitialPanel(optionLink);
 	}
 	
-	
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
 		if (source == optionLink){
@@ -106,6 +104,10 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 			closeArtefactOptionsCallout();
 			reflexionCtrl = new EPReflexionWrapperController(ureq, getWindowControl(), secCallback, artefact, struct);
 			listenTo(reflexionCtrl);
+		} else if (source == tagsLink) {
+			closeArtefactOptionsCallout();
+			tagsCtrl = new EPTagsController(ureq, getWindowControl(), secCallback, artefact, struct);
+			listenTo(tagsCtrl);
 		}
 	}
 	
@@ -122,6 +124,11 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 		} else if (source == artefactOptionCalloutCtrl) {
 			removeAsListenerAndDispose(artefactOptionCalloutCtrl);
 			artefactOptionCalloutCtrl = null;
+		} else if (source == tagsCtrl) {
+			if(event == Event.DONE_EVENT) {
+				fireEvent(ureq, Event.CHANGED_EVENT);
+			}
+			removeAsListenerAndDispose(tagsCtrl);
 		}
 		fireEvent(ureq, event);
 	}
@@ -140,7 +147,6 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 		moveTreeBox.activate();
 	}
 	
-	
 	/**
 	 * closes the callout
 	 */
@@ -161,10 +167,14 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 		if (secCallback.canRemoveArtefactFromStruct()){
 			unlinkLink = LinkFactory.createCustomLink("unlink.link", "remove", "remove.from.map", Link.LINK, artOptVC, this);
 		}		
-		if (secCallback.canAddArtefact() && secCallback.canRemoveArtefactFromStruct() && secCallback.isOwner()){ // isOwner: don't show move in group maps!
+		if (secCallback.canAddArtefact() && secCallback.canRemoveArtefactFromStruct() && secCallback.isOwner()) { // isOwner: don't show move in group maps!
 			moveLink = LinkFactory.createCustomLink("move.link", "move", "artefact.options.move", Link.LINK, artOptVC, this);
 		}
 		reflexionLink = LinkFactory.createCustomLink("reflexion.link", "reflexion", "table.header.reflexion", Link.LINK, artOptVC, this);
+		if(secCallback.canEditTags()) {
+			tagsLink = LinkFactory.createCustomLink("tags.link", "tags", "artefact.tags", Link.LINK, artOptVC, this);
+		}
+		
 		String title = translate("option.link");
 		removeAsListenerAndDispose(artefactOptionCalloutCtrl);
 		artefactOptionCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), artOptVC, optionLink, title, true, null);
@@ -172,10 +182,8 @@ public class EPArtefactViewOptionsLinkController extends BasicController {
 		artefactOptionCalloutCtrl.activate();
 	}
 	
-
 	@Override
 	protected void doDispose() {
 		closeArtefactOptionsCallout();
 	}
-
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewReadOnlyController.java b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewReadOnlyController.java
index 4c5e788ff38..ebfc40afbb1 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewReadOnlyController.java
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewReadOnlyController.java
@@ -53,14 +53,16 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com
  */
 public class EPArtefactViewReadOnlyController extends BasicController {
-
-	private VelocityContainer vC;
-	@Autowired
-	private EPFrontendManager ePFMgr;
+	
 	private Link detailsLink;
+	private VelocityContainer vC;
+	private EPArtefactViewOptionsLinkController optionsLinkCtrl;
+
 	private AbstractArtefact artefact;
 	private EPSecurityCallback secCallback;
-
+	@Autowired
+	private EPFrontendManager ePFMgr;
+	
 	protected EPArtefactViewReadOnlyController(UserRequest ureq, WindowControl wControl, AbstractArtefact artefact,
 			PortfolioStructure struct, EPSecurityCallback secCallback, boolean options) {
 		super(ureq, wControl);
@@ -83,21 +85,23 @@ public class EPArtefactViewReadOnlyController extends BasicController {
 		
 		if(options) {
 			//add the optionsLink to the artefact
-			EPArtefactViewOptionsLinkController optionsLinkCtrl
-				= new EPArtefactViewOptionsLinkController(ureq, getWindowControl(), artefact, secCallback, struct);
+			optionsLinkCtrl = new EPArtefactViewOptionsLinkController(ureq, getWindowControl(), artefact, secCallback, struct);
 			vC.put("option.link" , optionsLinkCtrl.getInitialComponent());
 			listenTo(optionsLinkCtrl);
-			
 		}
 		
+		updateTags();
+		putInitialPanel(vC);	
+	}
+	
+	private void updateTags() {
 		List<String> tags = ePFMgr.getArtefactTags(artefact);
 		List<String> escapedTags = new ArrayList<String>(tags.size());
 		for(String tag:tags) {
 			escapedTags.add(StringHelper.escapeHtml(tag));
 		}
 		vC.contextPut("tags", StringHelper.formatAsCSVString(escapedTags));
-		
-		putInitialPanel(vC);	
+		vC.setDirty(true);
 	}
 
 	/**
@@ -114,6 +118,11 @@ public class EPArtefactViewReadOnlyController extends BasicController {
 
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(optionsLinkCtrl == source) {
+			if(event == Event.CHANGED_EVENT) {
+				updateTags();
+			}
+		}
 		super.event(ureq, source, event);
 		fireEvent(ureq, event);
 	}
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPMultipleArtefactsAsTableController.java b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPMultipleArtefactsAsTableController.java
index 7366ddb4033..78682df78b6 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPMultipleArtefactsAsTableController.java
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPMultipleArtefactsAsTableController.java
@@ -55,6 +55,7 @@ import org.olat.portfolio.model.structel.PortfolioStructure;
 import org.olat.portfolio.model.structel.PortfolioStructureMap;
 import org.olat.portfolio.model.structel.StructureStatusEnum;
 import org.olat.portfolio.ui.artefacts.collect.EPCollectStepForm04;
+import org.olat.portfolio.ui.artefacts.edit.EPReflexionWrapperController;
 import org.olat.portfolio.ui.filter.PortfolioFilterController;
 import org.olat.portfolio.ui.structel.EPStructureChangeEvent;
 
@@ -227,7 +228,8 @@ public class EPMultipleArtefactsAsTableController extends BasicController implem
 				if(CMD_TITLE.equals(action)) {
 					popupArtefact(artefact, ureq);
 				} else if (CMD_REFLEXION.equals(action)){
-					EPUIFactory.getReflexionPopup(ureq, getWindowControl(), secCallback, artefact, struct);
+					EPReflexionWrapperController reflexionCtrl = new EPReflexionWrapperController(ureq, getWindowControl(), secCallback, artefact, struct);
+					listenTo(reflexionCtrl);
 				} else if (CMD_CHOOSE.equals(action)){
 					fireEvent(ureq, new EPArtefactChoosenEvent(artefact));
 				} else if (CMD_UNLINK.equals(action)){
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPTagViewController.java b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPTagViewController.java
new file mode 100644
index 00000000000..6d366682954
--- /dev/null
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPTagViewController.java
@@ -0,0 +1,79 @@
+/**
+ * <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.portfolio.ui.artefacts.view;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.TextBoxListElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.portfolio.manager.EPFrontendManager;
+import org.olat.portfolio.model.artefacts.AbstractArtefact;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Show the tags read-only
+ * 
+ * 
+ * Initial date: 16.07.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EPTagViewController extends FormBasicController {
+	
+	private List<String> tags;
+	@Autowired
+	private EPFrontendManager ePFMgr;
+
+	public EPTagViewController(UserRequest ureq, WindowControl wControl, AbstractArtefact artefact) {
+		super(ureq, wControl, FormBasicController.LAYOUT_VERTICAL);
+		tags = ePFMgr.getArtefactTags(artefact);
+
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormDescription("tags.view.header");
+		
+		Map<String,String> tagsMap = new HashMap<>();
+		for(String tag:tags) {
+			tagsMap.put(tag, tag);
+		}
+		
+		TextBoxListElement tagListElement = uifactory.addTextBoxListElement("artefact.tags", null, "tag.input.hint", tagsMap, formLayout, getTranslator());
+		tagListElement.setEnabled(false);
+	}
+
+	@Override
+	protected void doDispose() {
+		//nothing
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//do nothing
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/artefactOptions.html b/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/artefactOptions.html
index 19b98547dc5..9456702d862 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/artefactOptions.html
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/artefactOptions.html
@@ -3,4 +3,5 @@
 	#if ($r.available("unlink.link")) <li>$r.render("unlink.link")</li> #end
 	#if ($r.available("move.link")) <li>$r.render("move.link")</li> #end
 	<li>$r.render("reflexion.link")</li>
+	#if ($r.available("tags.link")) <li>$r.render("tags.link")</li> #end
 </ul>
\ No newline at end of file
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_de.properties
index 2d86257a013..00741e6e746 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_de.properties
@@ -11,6 +11,7 @@ artefact.author=Autor
 artefact.date=Datum
 artefact.source=Sammelort
 artefact.tags=Tags
+artefact.tags.title=Tags zu diesem Artefakt
 artefact.used.in.maps=Verwendet in
 artefact.reflexion=Reflexion
 artefact.reflexion.original=Die originale Reflexion
@@ -52,6 +53,7 @@ info.no.reflexion.yet=Sie haben noch keine Reflexion zur Wahl dieses Artefakts i
 
 browse.root=Tags
 tag.browser.intro=Durchsuchen Sie Ihre Artefakte nach Tags.
+tags.view.header=$\:tag.browser.intro
 option.link=Weitere Optionen...
 artefact.options.title=Wählen Sie die gewünschte Operation für dieses Artefakt
 artefact.options.move=Verschieben
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties
index 42083fcff70..3e52642a24e 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties
@@ -17,6 +17,7 @@ artefact.reflexion.view.descr=Here you can see the original reflection provided
 artefact.source=Source of collection
 artefact.sourcelink=Source
 artefact.tags=Tags
+artefact.tags.title=Tags of artefact
 artefact.title=Title
 artefact.title.not.empty=The $\:artefact.title is mandatory.
 artefact.title.too.long=The $\:artefact.title must not exceed {0} characters.
@@ -50,6 +51,7 @@ table.header.view=View
 table.row.reflexion=Edit
 tag.browser.intro=Search your artefacts for tags.
 tag.textboxlist.hint=Click here to add more tags.
+tags.view.header=$\:tag.browser.intro
 title.reflexion.artefact=Reflection on this artefact
 title.reflexion.link=Edit reflection on selecting this artefact in a binder
 view.artefact.header=Detailed view of artefact
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_fr.properties
index fbd7e30a0f1..c91cb7ee1e8 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_fr.properties
@@ -17,6 +17,7 @@ artefact.reflexion.view.descr=Vous voyez ici la r\u00E9flexion originale qui a \
 artefact.source=Lieu de r\u00E9colte
 artefact.sourcelink=Source
 artefact.tags=Tags
+artefact.tags.title=Tags de l'artefact
 artefact.title=Titre
 artefact.title.not.empty=Le $\:artefact.title doit \u00EAtre indiqu\u00E9.
 artefact.title.too.long=Le $\:artefact.title ne peut exc\u00E9der la limite de {0} signes.
@@ -50,6 +51,7 @@ table.header.view=Voir
 table.row.reflexion=\u00C9diter
 tag.browser.intro=Cherchez des tags dans vos artefacts.
 tag.textboxlist.hint=Cliquez ici pour plus de tags.
+tags.view.header=$\:tag.browser.intro
 title.reflexion.artefact=R\u00E9flexion sur cet artefact
 title.reflexion.link=\u00C9diter la r\u00E9flexion sur le choix de cet artefact pour ce classeur
 view.artefact.header=Aper\u00E7u d\u00E9taill\u00E9 de l'artefact
-- 
GitLab