diff --git a/src/main/java/org/olat/admin/user/course/CourseOverviewController.java b/src/main/java/org/olat/admin/user/course/CourseOverviewController.java
index 417b41071bf6aaf625d972157a530c317404eb0b..d734808b9b690190de09e3baa45f8f0c79f38f6e 100644
--- a/src/main/java/org/olat/admin/user/course/CourseOverviewController.java
+++ b/src/main/java/org/olat/admin/user/course/CourseOverviewController.java
@@ -202,10 +202,22 @@ public class CourseOverviewController extends BasicController  {
 			}
 			
 			memberView.setFirstTime(membership.getCreationDate());
-			memberView.setLastTime(membership.getLastModified());
-			memberView.getMembership().setRepoOwner(membership.isOwner());
-			memberView.getMembership().setRepoTutor(membership.isCoach());
-			memberView.getMembership().setRepoParticipant(membership.isParticipant());
+			if(memberView.getLastTime() == null ||
+					(memberView.getLastTime() != null && membership.getLastModified() != null
+					&& membership.getLastModified().after(memberView.getLastTime()))) {
+				memberView.setLastTime(membership.getLastModified());
+			}
+			
+			//add the roles
+			if(!memberView.getMembership().isRepoOwner()) {
+				memberView.getMembership().setRepoOwner(membership.isOwner());
+			}
+			if(!memberView.getMembership().isRepoTutor()) {
+				memberView.getMembership().setRepoTutor(membership.isCoach());
+			}
+			if(!memberView.getMembership().isRepoParticipant()) {
+				memberView.getMembership().setRepoParticipant(membership.isParticipant());
+			}
 		}
 
 		List<BusinessGroupShort> groups = businessGroupService.loadShortBusinessGroups(groupKeys);
@@ -236,7 +248,11 @@ public class CourseOverviewController extends BasicController  {
 			}
 			memberView.addGroup(group);
 			memberView.setFirstTime(membership.getCreationDate());
-			memberView.setLastTime(membership.getLastModified());
+			if(memberView.getLastTime() == null || (
+					memberView.getLastTime() != null && membership.getLastModified() != null
+					&& membership.getLastModified().after(memberView.getLastTime()))) {
+				memberView.setLastTime(membership.getLastModified());
+			}
 			switch(membership.getMembership()) {
 				case owner: memberView.getMembership().setGroupTutor(true); break;
 				case participant: memberView.getMembership().setGroupParticipant(true); break;
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java
index 23fb984fc82cda7451dc715ea865862fca8bf5c0..7ce770e7885dd518a73fba03d3e5424ca00136f5 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java
@@ -315,16 +315,16 @@ public abstract class AbstractFlexiTableRenderer extends DefaultComponentRendere
 
 			sb.append("<div class='o_table_footer'><div class='o_table_checkall input-sm'>");
 
-			sb.append("<label class='checkbox-inline'><a id=\"")
-			  .append(dispatchId).append("\" href=\"javascript:o_table_toggleCheck('").append(formName).append("', true);")
+			sb.append("<label class='checkbox-inline'><a id='")
+			  .append(dispatchId).append("_sa' href=\"javascript:o_table_toggleCheck('").append(formName).append("', true);")
 			  .append(FormJSHelper.getXHRFnCallFor(ftE.getRootForm(), dispatchId, 1, new NameValuePair("select", "checkall")))
-			  .append("\"><input type='checkbox' checked='checked' disabled='disabled' /><span>").append(translator.translate("form.checkall"))
+			  .append("\"><i class='o_icon o_icon-lg o_icon_check_on'> </i> <span>").append(translator.translate("form.checkall"))
 			  .append("</span></a></label>");
 
-			sb.append("<label class='checkbox-inline'><a id=\"")
-			  .append(dispatchId).append("\" href=\"javascript:o_table_toggleCheck('").append(formName).append("', false);")
+			sb.append("<label class='checkbox-inline'><a id='")
+			  .append(dispatchId).append("_dsa' href=\"javascript:o_table_toggleCheck('").append(formName).append("', false);")
 			  .append(FormJSHelper.getXHRFnCallFor(ftE.getRootForm(), dispatchId, 1, new NameValuePair("select", "uncheckall")))
-			  .append("\"><input type='checkbox' disabled='disabled' /><span>").append(translator.translate("form.uncheckall"))
+			  .append("\"><i class='o_icon o_icon-lg o_icon_check_off'> </i> <span>").append(translator.translate("form.uncheckall"))
 			  .append("</span></a></label>");
 
 			sb.append("</div></div>");
diff --git a/src/main/java/org/olat/core/gui/components/table/TableRenderer.java b/src/main/java/org/olat/core/gui/components/table/TableRenderer.java
index 89b681d823708d37cc2ca203e3ad245d7632594a..5a5e659c3b31949310974b50f621ed0ef1aeaef5 100644
--- a/src/main/java/org/olat/core/gui/components/table/TableRenderer.java
+++ b/src/main/java/org/olat/core/gui/components/table/TableRenderer.java
@@ -220,17 +220,17 @@ public class TableRenderer extends DefaultComponentRenderer {
 
 	private void appendSelectDeselectAllButtons(final StringOutput target, final Translator translator, Table table, String formName, int rows, int resultsPerPage) {
 		if (table.isMultiSelect()) {
-			target.append("<div class='o_table_checkall input-sm'>");
-			target.append("<label class='checkbox-inline'>");
-			target.append("<a href='#' onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', true)\">");
-			target.append("<input type='checkbox' checked='checked' disabled='disabled' />");
-			target.append(translator.translate("checkall"));
-			target.append("</a></label>");
-			target.append("<label class='checkbox-inline'><a href=\"#\" onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', false)\">");
-			target.append("<input type='checkbox' disabled='disabled' />");
-			target.append(translator.translate("uncheckall"));
-			target.append("</a></label>");
-			target.append("</div>");
+			target.append("<div class='o_table_checkall input-sm'>")
+			  .append("<label class='checkbox-inline'>")
+			  .append("<a href='#' onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', true)\">")
+			  .append("<i class='o_icon o_icon-lg o_icon_check_on'> </i> ")
+			  .append(translator.translate("checkall"))
+			  .append("</a></label>");
+			target.append("<label class='checkbox-inline'><a href=\"#\" onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', false)\">")
+			  .append("<i class='o_icon o_icon-lg o_icon_check_off'> </i> ")
+			  .append(translator.translate("uncheckall"))
+			  .append("</a></label>")
+			  .append("</div>");
 		}
 
 		if (table.isShowAllSelected() && (rows > resultsPerPage)) {
diff --git a/src/main/java/org/olat/core/id/context/HistoryManager.java b/src/main/java/org/olat/core/id/context/HistoryManager.java
index 0e40b5c05f5160aff6651b9e6dcd7af8fc026df7..2c7b0f9ede48225a98cd12bb94796b6da9e04e4c 100644
--- a/src/main/java/org/olat/core/id/context/HistoryManager.java
+++ b/src/main/java/org/olat/core/id/context/HistoryManager.java
@@ -35,6 +35,7 @@ import org.olat.group.BusinessGroupImpl;
 import org.olat.repository.RepositoryEntry;
 
 import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.ConversionException;
 
 /**
  * 
@@ -105,6 +106,9 @@ public class HistoryManager extends BasicManager {
 			String pathHomePage = FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomePage(identity.getName());
 			File resumeXml = new File(pathHomePage, "resume.xml");
 			return readHistory(resumeXml);
+		} catch(ConversionException e) {
+			logWarn("Cannot read resume file: ", e);
+			return null;
 		} catch (Exception e) {
 			logError("Cannot read resume file: ", e);
 			return null;
diff --git a/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java b/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java
index 6c3adc1c73e31256da2b1079ca8d3a1388f85897..f63fcd4d776d41734b42f0774eb40706d06ba644 100644
--- a/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java
+++ b/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java
@@ -146,7 +146,7 @@ public class BulkAssessmentOverviewController extends FormBasicController {
 		}
 		taskModel.setObjects(taskDatas);
 		taskListEl.reset();
-		this.flc.contextPut("hasScheduledTasks", Boolean.valueOf(taskDatas.size()>0));
+		flc.contextPut("hasScheduledTasks", Boolean.valueOf(taskDatas.size()>0));
 	}
 	
 	@Override
diff --git a/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java b/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java
index 2f03c1571ae689910e8e88b2fb4ea390f7d4bd89..8279b5a9194bf2f61880a49e0456b2d189b666d8 100644
--- a/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java
+++ b/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java
@@ -81,13 +81,19 @@ import org.olat.course.assessment.model.BulkAssessmentRow;
 import org.olat.course.assessment.model.BulkAssessmentSettings;
 import org.olat.course.nodes.AssessableCourseNode;
 import org.olat.course.nodes.CourseNode;
+import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.MSCourseNode;
 import org.olat.course.nodes.ProjectBrokerCourseNode;
 import org.olat.course.nodes.TACourseNode;
+import org.olat.course.nodes.gta.GTAManager;
+import org.olat.course.nodes.gta.TaskList;
+import org.olat.course.nodes.gta.TaskProcess;
 import org.olat.course.nodes.ta.ReturnboxController;
+import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.course.run.userview.UserCourseEnvironmentImpl;
+import org.olat.repository.RepositoryEntry;
 import org.olat.user.UserManager;
 import org.olat.util.logging.activity.LoggingResourceable;
 
@@ -310,7 +316,7 @@ public class BulkAssessmentTask implements LongRunnable, TaskAwareRunnable, Sequ
 		final boolean hasScore = courseNode.hasScoreConfigured();
 		final boolean hasPassed = courseNode.hasPassedConfigured();
 		final boolean hasReturnFiles = (StringHelper.containsNonWhitespace(datas.getReturnFiles())
-				&& courseNode instanceof TACourseNode);
+				&& (courseNode instanceof TACourseNode || courseNode instanceof GTACourseNode));
 		
 		if(hasReturnFiles) {
 			try {
@@ -396,49 +402,108 @@ public class BulkAssessmentTask implements LongRunnable, TaskAwareRunnable, Sequ
 				uce.getScoreAccounting().scoreInfoChanged(courseNode, se);
 			}
 			
+			boolean identityHasReturnFile = false;
 			if(hasReturnFiles && row.getReturnFiles() != null && row.getReturnFiles().size() > 0) {
 				String assessedId = row.getAssessedId();
 				File assessedFolder = new File(unzipped, assessedId);
-				if(assessedFolder.exists()) {
-					VFSContainer returnBox = getReturnBox(uce, courseNode, identity);
-					if(returnBox != null) {
-						for(String returnFilename:row.getReturnFiles()) {
-							File returnFile = new File(assessedFolder, returnFilename);
-							VFSItem currentReturnLeaf = returnBox.resolve(returnFilename);
-							if(currentReturnLeaf != null) {
-								//remove the current file (delete make a version is enabled)
-								currentReturnLeaf.delete();
-							}
-
-							VFSLeaf returnLeaf = returnBox.createChildLeaf(returnFilename);
-							if(returnFile.exists()) {
-								try {
-									InputStream inStream = new FileInputStream(returnFile);
-									VFSManager.copyContent(inStream, returnLeaf);
-								} catch (FileNotFoundException e) {
-									log.error("Cannot copy return file " + returnFilename + " from " + assessedId, e);
-								}
-							}
-						}
-					}
+				identityHasReturnFile = assessedFolder.exists();
+				if(identityHasReturnFile) {
+					processReturnFile(courseNode, row, uce, assessedFolder);
+				}
+			}
+			
+			if(courseNode instanceof GTACourseNode) {
+				//push the state further
+				GTACourseNode gtaNode = (GTACourseNode)courseNode;
+				if((hasScore && score != null) || (hasPassed && passed != null)) {
+					//pushed to graded
+					updateTasksState(gtaNode, uce, TaskProcess.grading);
+				} else if(hasReturnFiles) {
+					//push to revised
+					updateTasksState(gtaNode, uce, TaskProcess.correction);
 				}
 			}
 			
 			if(count++ % 5 == 0) {
 				dbInstance.commitAndCloseSession();
+			} else {
+				dbInstance.commit();
 			}
 		}
 	}
 	
+	private void updateTasksState(GTACourseNode courseNode, UserCourseEnvironment uce, TaskProcess status) {
+		final GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
+		Identity identity = uce.getIdentityEnvironment().getIdentity();
+		RepositoryEntry entry = uce.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		
+		org.olat.course.nodes.gta.Task task;
+		TaskList taskList = gtaManager.getTaskList(entry, courseNode);
+		if(taskList == null) {
+			taskList = gtaManager.createIfNotExists(entry, courseNode);
+			task = gtaManager.createTask(null, taskList, status, null, identity, courseNode);
+		} else {
+			task = gtaManager.getTask(identity, taskList);
+			if(task == null) {
+				gtaManager.createTask(null, taskList, status, null, identity, courseNode);
+			}
+		}
+		
+		gtaManager.nextStep(status, courseNode);
+	}
+	
+	
+	private void processReturnFile(AssessableCourseNode courseNode, BulkAssessmentRow row, UserCourseEnvironment uce, File assessedFolder) {
+		String assessedId = row.getAssessedId();
+		Identity identity = uce.getIdentityEnvironment().getIdentity();
+		VFSContainer returnBox = getReturnBox(uce, courseNode, identity);
+		if(returnBox != null) {
+			for(String returnFilename:row.getReturnFiles()) {
+				File returnFile = new File(assessedFolder, returnFilename);
+				VFSItem currentReturnLeaf = returnBox.resolve(returnFilename);
+				if(currentReturnLeaf != null) {
+					//remove the current file (delete make a version if it is enabled)
+					currentReturnLeaf.delete();
+				}
+
+				VFSLeaf returnLeaf = returnBox.createChildLeaf(returnFilename);
+				if(returnFile.exists()) {
+					try {
+						InputStream inStream = new FileInputStream(returnFile);
+						VFSManager.copyContent(inStream, returnLeaf);
+					} catch (FileNotFoundException e) {
+						log.error("Cannot copy return file " + returnFilename + " from " + assessedId, e);
+					}
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Return the target folder of the assessed identity. This is a factory method which take care
+	 * of the type of the course node.
+	 * 
+	 * @param uce
+	 * @param courseNode
+	 * @param identity
+	 * @return
+	 */
 	private VFSContainer getReturnBox(UserCourseEnvironment uce, CourseNode courseNode, Identity identity) {
-		String returnPath = ReturnboxController.getReturnboxPathRelToFolderRoot(uce.getCourseEnvironment(), courseNode);
-		OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(returnPath, null);
-		VFSItem assessedItem = rootFolder.resolve(identity.getName());
-		if(assessedItem == null) {
-			return rootFolder.createChildContainer(identity.getName());
-		} else if(assessedItem instanceof VFSContainer) {
-			return (VFSContainer)assessedItem;
+		VFSContainer returnContainer = null;
+		if(courseNode instanceof GTACourseNode) {
+			final GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
+			CourseEnvironment courseEnv = uce.getCourseEnvironment();
+			returnContainer = gtaManager.getCorrectionContainer(courseEnv, (GTACourseNode)courseNode, identity);
+		} else {
+			String returnPath = ReturnboxController.getReturnboxPathRelToFolderRoot(uce.getCourseEnvironment(), courseNode);
+			OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(returnPath, null);
+			VFSItem assessedItem = rootFolder.resolve(identity.getName());
+			if(assessedItem == null) {
+				returnContainer = rootFolder.createChildContainer(identity.getName());
+			} else if(assessedItem instanceof VFSContainer) {
+				returnContainer = (VFSContainer)assessedItem;
+			}
 		}
-		return null;
+		return returnContainer;
 	}
 }
diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java
index e45eccaf8a4a2ba1f16f30845373114b55f5c3f4..d66a926ffeea30b9d68dd0a430544f75ea25baab 100644
--- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java
+++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java
@@ -51,8 +51,22 @@ public interface UserCourseInformationsManager {
 
 	public Map<Long,Date> getRecentLaunchDates(Long courseResourceId, List<Identity> identities);
 	
+	/**
+	 * Return the initial launch dates of a list of users.
+	 * @param courseResourceId
+	 * @param identities
+	 * @return
+	 */
 	public Map<Long,Date> getInitialLaunchDates(Long courseResourceId, List<Identity> identities);
 	
+	/**
+	 * Return all initial launch dates of a course.
+	 * 
+	 * @param courseResourceId
+	 * @return
+	 */
+	public Map<Long,Date> getInitialLaunchDates(Long courseResourceId);
+	
 	public int deleteUserCourseInformations(RepositoryEntry entry);
 
 }
diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java
index 55252b5f5edc29e24d9343aaa5112fddce53d6f1..0cdb16e5cbbcdab18e7c95ada7d3a352a1af4836 100644
--- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java
+++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java
@@ -338,7 +338,40 @@ public class UserCourseInformationsManagerImpl implements UserCourseInformations
 			return Collections.emptyMap();
 		}
 	}
+	
+	/**
+	 * Return a map of identity keys to initial launch date.
+	 * 
+	 * @param courseResourceId The course resourceable id
+	 * @return
+	 */
+	@Override
+	public Map<Long,Date> getInitialLaunchDates(Long courseResourceId) {
+		try {
+
+			StringBuilder sb = new StringBuilder();
+			sb.append("select infos.identity.key, infos.initialLaunch from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ")
+			  .append(" inner join infos.resource as resource")
+			  .append(" where resource.resId=:resId and resource.resName='CourseModule'");
+
+			TypedQuery<Object[]> query = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Object[].class)
+					.setParameter("resId", courseResourceId);
 
+			List<Object[]> infoList = query.getResultList();
+			Map<Long,Date> dateMap = new HashMap<Long,Date>();
+			for(Object[] infos:infoList) {
+				Long identityKey = (Long)infos[0];
+				Date initialLaunch = (Date)infos[1];
+				if(identityKey != null && initialLaunch != null) {
+					dateMap.put(identityKey, initialLaunch);
+				}
+			}
+			return dateMap;
+		} catch (Exception e) {
+			log.error("Cannot retrieve course informations for: " + courseResourceId, e);
+			return Collections.emptyMap();
+		}
+	}
 
 	/**
 	 * Return a map of identity keys to initial launch date.
diff --git a/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java b/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java
index b0267ca5590f67f91b4036baa4518273aa037508..fae0d16ade05d801df6a89c455c010f6e1a6d33b 100644
--- a/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java
+++ b/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java
@@ -22,8 +22,11 @@ package org.olat.course.assessment.model;
 import java.io.Serializable;
 
 import org.olat.course.nodes.AssessableCourseNode;
+import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.ProjectBrokerCourseNode;
 import org.olat.course.nodes.TACourseNode;
+import org.olat.course.nodes.gta.GTAType;
+import org.olat.modules.ModuleConfiguration;
 
 /**
  * 
@@ -47,11 +50,15 @@ public class BulkAssessmentSettings implements Serializable {
 		hasScore = courseNode.hasScoreConfigured();
 		hasPassed = courseNode.hasPassedConfigured();
 		
+		ModuleConfiguration config = courseNode.getModuleConfiguration();
 		if (courseNode instanceof TACourseNode) {
-			Boolean hasReturnBox = (Boolean)courseNode.getModuleConfiguration().get(TACourseNode.CONF_RETURNBOX_ENABLED);
+			Boolean hasReturnBox = (Boolean)config.get(TACourseNode.CONF_RETURNBOX_ENABLED);
 			hasReturnFiles = hasReturnBox == null ? false : hasReturnBox.booleanValue();				
+		} else if (courseNode instanceof GTACourseNode) {
+			hasReturnFiles = GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))
+					&& config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION);				
 		} else if (courseNode instanceof ProjectBrokerCourseNode) {
-			Boolean hasReturnBox = (Boolean)courseNode.getModuleConfiguration().get(ProjectBrokerCourseNode.CONF_RETURNBOX_ENABLED);
+			Boolean hasReturnBox = (Boolean)config.get(ProjectBrokerCourseNode.CONF_RETURNBOX_ENABLED);
 			hasReturnFiles = hasReturnBox == null ? false : hasReturnBox.booleanValue();				
 		} else {
 			hasReturnFiles = false;			
diff --git a/src/main/java/org/olat/course/config/CourseConfig.java b/src/main/java/org/olat/course/config/CourseConfig.java
index 3f901a1b7509e53b8f7212a67a5508925c7afd06..5e95df63c05f791604be4029aa2327b8e328e78f 100644
--- a/src/main/java/org/olat/course/config/CourseConfig.java
+++ b/src/main/java/org/olat/course/config/CourseConfig.java
@@ -386,7 +386,11 @@ public class CourseConfig implements Serializable, Cloneable {
 	 * @return true if the efficency statement is enabled
 	 */
 	public Long getCertificateTemplate() {
-		Long templateId = (Long)configuration.get(CERTIFICATE_TEMPLATE);
+		Object templateIdObj = configuration.get(CERTIFICATE_TEMPLATE);
+		Long templateId = null;
+		if(templateIdObj instanceof Long) {
+			templateId = (Long)templateIdObj;
+		}
 		return templateId;
 	}
 	
diff --git a/src/main/java/org/olat/course/editor/EditorStatusController.java b/src/main/java/org/olat/course/editor/EditorStatusController.java
index 0b15a0570a83cfa88573275f469d411ad8e8d9c4..9710b08654839bda214f3a1229f219c9cecd3fdf 100644
--- a/src/main/java/org/olat/course/editor/EditorStatusController.java
+++ b/src/main/java/org/olat/course/editor/EditorStatusController.java
@@ -78,7 +78,7 @@ public class EditorStatusController extends BasicController {
 		
 		long lpTimeStamp = cetm.getLatestPublishTimestamp();
 		if (lpTimeStamp == -1) {				
-			main.contextPut("publishInfos", "published.never.yet");
+			main.contextPut("publishInfos", translate("published.never.yet"));
 		} else { // course has been published before
 			Date d = new Date(lpTimeStamp);
 			main.contextPut("publishInfos", translate("published.latest", Formatter.getInstance(getLocale()).formatDateAndTime(d)));
diff --git a/src/main/java/org/olat/course/nodes/GTACourseNode.java b/src/main/java/org/olat/course/nodes/GTACourseNode.java
index 4486129d868eae7bb8d46e4d0f7d7a4635ce6d0f..ecd475adf1c86915fa7cade2d7b194c9047a8eab 100644
--- a/src/main/java/org/olat/course/nodes/GTACourseNode.java
+++ b/src/main/java/org/olat/course/nodes/GTACourseNode.java
@@ -60,6 +60,7 @@ import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.archiver.ScoreAccountingHelper;
 import org.olat.course.assessment.AssessmentManager;
+import org.olat.course.assessment.bulk.BulkAssessmentToolController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
@@ -70,6 +71,7 @@ import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskHelper;
 import org.olat.course.nodes.gta.TaskList;
+import org.olat.course.nodes.gta.ui.BulkDownloadToolController;
 import org.olat.course.nodes.gta.ui.GTAAssessmentDetailsController;
 import org.olat.course.nodes.gta.ui.GTAEditController;
 import org.olat.course.nodes.gta.ui.GTAGroupAssessmentToolController;
@@ -106,16 +108,24 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 	public static final String GTASK_AREAS = "grouptask.areas";
 	public static final String GTASK_ASSIGNMENT = "grouptask.assignement";
 	public static final String GTASK_ASSIGNMENT_DEADLINE = "grouptask.assignment.deadline";
+	public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE = "grouptask.assignment.deadline.relative";
+	public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO = "grouptask.assignment.deadline.relative.to";
 	public static final String GTASK_SUBMIT = "grouptask.submit";
 	public static final String GTASK_SUBMIT_DEADLINE = "grouptask.submit.deadline";
+	public static final String GTASK_SUBMIT_DEADLINE_RELATIVE = "grouptask.submit.deadline.relative";
+	public static final String GTASK_SUBMIT_DEADLINE_RELATIVE_TO = "grouptask.submit.deadline.relative.to";
 	public static final String GTASK_REVIEW_AND_CORRECTION = "grouptask.review.and.correction";
 	public static final String GTASK_REVISION_PERIOD = "grouptask.revision.period";
 	public static final String GTASK_SAMPLE_SOLUTION = "grouptask.solution";
 	public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER = "grouptask.solution.visible.after";
+	public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE = "grouptask.solution.visible.after.relative";
+	public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO = "grouptask.solution.visible.after.relative.to";
 	public static final String GTASK_GRADING = "grouptask.grading";
 	
 	public static final String GTASK_TASKS = "grouptask.tasks";
 	
+	public static final String GTASK_RELATIVE_DATES = "grouptask.rel.dates";
+	
 	public static final String GTASK_ASSIGNEMENT_TYPE = "grouptask.assignement.type";
 	public static final String GTASK_ASSIGNEMENT_TYPE_AUTO = "auto";
 	public static final String GTASK_ASSIGNEMENT_TYPE_MANUAL = "manual";
@@ -137,10 +147,16 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 	public static final String GTASK_SOLUTIONS = "grouptask.solutions";
 	
 
-	private static final String TYPE = "gta";
+	public static final String TYPE_GROUP = "gta";
+	public static final String TYPE_INDIVIDUAL = "ita";
 
 	public GTACourseNode() {
-		super(TYPE);
+		super(TYPE_GROUP);
+        updateModuleConfigDefaults(true);
+	}
+	
+	public GTACourseNode(String type) {
+		super(type);
         updateModuleConfigDefaults(true);
 	}
 
@@ -160,7 +176,12 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 			//setup default configuration
 			ModuleConfiguration config = getModuleConfiguration();
 			//group task
-			config.setStringValue(GTASK_TYPE, GTAType.group.name());
+			if(getType().equals(TYPE_INDIVIDUAL)) {
+				config.setStringValue(GTASK_TYPE, GTAType.individual.name());
+			} else {
+				config.setStringValue(GTASK_TYPE, GTAType.group.name());
+			}
+
 			//manual choice
 			config.setStringValue(GTASK_ASSIGNEMENT_TYPE, GTASK_ASSIGNEMENT_TYPE_MANUAL);
 			//all steps
@@ -219,7 +240,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 		boolean hasScoring = config.getBooleanSafe(GTASK_GRADING);
 		if (hasScoring) {
 			if(!config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD)
-					&& !config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD)) {
+					&& !config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD)) {
 
 				addStatusErrorDescription("error.missing.score.config", GTAEditController.PANE_TAB_GRADING, sdList);
 			}
@@ -380,14 +401,14 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 		if(taskList != null) {
 			if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) {
 				List<BusinessGroup> selectedGroups;
-				if(options.getGroup() != null) {
+				if(options != null && options.getGroup() != null) {
 					selectedGroups = Collections.singletonList(options.getGroup());
 				} else {
 					selectedGroups = gtaManager.getBusinessGroups(this);
 				}
 				
 				for(BusinessGroup businessGroup:selectedGroups) {
-					archiveNodeData(locale, course, businessGroup, taskList, dirName, exportStream);
+					archiveNodeData(course, businessGroup, taskList, dirName, exportStream);
 				}
 			} else {
 				if(users == null) {
@@ -396,7 +417,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 				
 				Set<Identity> uniqueUsers = new HashSet<>(users);
 				for(Identity user: uniqueUsers) {
-					archiveNodeData(locale, course, user, taskList, dirName, exportStream);
+					archiveNodeData(course, user, taskList, dirName, exportStream);
 				}
 			}
 		}
@@ -415,7 +436,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 		return true;
 	}
 	
-	private void archiveNodeData(Locale locale, ICourse course, Identity assessedIdentity, TaskList taskList, String dirName, ZipOutputStream exportStream) {
+	private void archiveNodeData(ICourse course, Identity assessedIdentity, TaskList taskList, String dirName, ZipOutputStream exportStream) {
 		ModuleConfiguration config = getModuleConfiguration();
 		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
 		
@@ -460,7 +481,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 		}
 	}
 	
-	private void archiveNodeData(Locale locale, ICourse course, BusinessGroup businessGroup, TaskList taskList, String dirName, ZipOutputStream exportStream) {
+	private void archiveNodeData(ICourse course, BusinessGroup businessGroup, TaskList taskList, String dirName, ZipOutputStream exportStream) {
 		ModuleConfiguration config = getModuleConfiguration();
 		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
 		
@@ -511,11 +532,14 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 		//tasks
 		File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
 		FileUtils.deleteDirsAndFiles(taskDirectory, true, true);
-		//TODO the rest
 		
 		//solutions
 		File solutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this);
 		FileUtils.deleteDirsAndFiles(solutionsDirectory, true, true);
+		
+		//clean up database
+		RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		gtaManager.deleteTaskList(entry, this);
 	}
 	
 	@Override
@@ -676,15 +700,22 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 			TooledStackedPanel stackPanel, CourseEnvironment courseEnv, AssessmentToolOptions options) {
 		
 		ModuleConfiguration config =  getModuleConfiguration();
-		List<Controller> tools = new ArrayList<>(1);
-		if(options.getGroup() != null && GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))
+		List<Controller> tools = new ArrayList<>(2);
+		if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))
 			&& (config.getBooleanSafe(GTASK_ASSIGNMENT)
 				|| config.getBooleanSafe(GTASK_SUBMIT)
 				|| config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION)
 				|| config.getBooleanSafe(GTASK_REVISION_PERIOD))) {
 			
-			Controller tool = new GTAGroupAssessmentToolController(ureq, wControl, courseEnv, options.getGroup(), this);
-			tools.add(tool);		
+			if(options.getGroup() != null) {
+				tools.add(new GTAGroupAssessmentToolController(ureq, wControl, courseEnv, options.getGroup(), this));
+			}
+			tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this));
+		} else if(GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))
+				&& (config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION)
+						|| config.getBooleanSafe(GTASK_GRADING))) {
+			tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, this));
+			tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this));
 		}
 		return tools;
 	}
diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties
index c19dba0d67c3fc88e35f45661101c480932374a0..c020cc1c29988bf615d20907f491259ba51f4d24 100644
--- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties
@@ -15,6 +15,7 @@ title_dialog=Dateidiskussion
 title_en=Einschreibung
 title_fo=Forum
 title_gta=Gruppenaufgabe
+title_ita=Aufgabe
 title_iqself=Selbsttest
 title_iqsurv=Fragebogen
 title_iqtest=Test
@@ -22,7 +23,7 @@ title_ms=Bewertung
 title_scorm=SCORM-Lerninhalt
 title_sp=Einzelne Seite
 title_st=Struktur
-title_ta=Aufgabe
+title_ta=<s>Aufgabe (deprecated)</s>
 title_tu=Externe Seite
 title_wiki=Wiki
 title_ll=Linkliste
diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties
index 081542872708fc930fda19ca078a879d8ccb86af..50c0dde4c6090394f8a7e3b61f2454cb4ebf4a45 100644
--- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties
@@ -16,6 +16,7 @@ title_dialog=File dialog
 title_en=Enrolment
 title_fo=Forum
 title_gta=Grouptask
+title_ita=Task
 title_iqself=Self-test
 title_iqsurv=Questionnaire
 title_iqtest=Test
@@ -26,7 +27,7 @@ title_projectbroker=Topic assignment
 title_scorm=SCORM learning content
 title_sp=Single page
 title_st=Structure
-title_ta=Task
+title_ta=<s>Task (deprecated)</s>
 title_tu=External page
 title_wiki=Wiki
 personal.title=Performance summary
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties
index 95d0b68017eac9df504f762f8dfbed4b1b461c13..d613022ed5a9e57a3ea30d4bc7c390844fcace38 100644
--- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties
@@ -16,6 +16,8 @@ title_den=Attribution de rendez-vous
 title_dialog=Fichier discut\u00E9
 title_en=Inscription
 title_fo=Forum
+title_gta=Devoir de groupe
+title_ita=Devoir
 title_iqself=Auto-test
 title_iqsurv=Questionnaire
 title_iqtest=Test
@@ -26,6 +28,6 @@ title_projectbroker=Affectation sujets
 title_scorm=Contenu did. SCORM
 title_sp=Page simple
 title_st=Structure
-title_ta=Devoir
+title_ta=<s>Devoir</s>
 title_tu=Page externe
 title_wiki=Page Wiki
diff --git a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml
index 0ec7c18fc0666c2779ac3a5f3b28f5247c17983c..39c1a2ccf35dd38cb09f793ab0272f756627998f 100644
--- a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml
+++ b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml
@@ -79,14 +79,20 @@
 		<property name="order" value="130" />
 		<property name="alternativeCourseNodes">
 			<list>
-				<value>gta</value>
+				<value>igta</value>
 			</list>
 		</property>
 	</bean>
 	
-	<bean id="gta" class="org.olat.course.nodes.gta.GTACourseNodeConfiguration" scope="prototype">
+	<bean id="ita" class="org.olat.course.nodes.gta.GTACourseNodeConfiguration" scope="prototype">
+		<constructor-arg index="0" value="true" />
 		<property name="order" value="131" />
 	</bean>
+	
+	<bean id="gta" class="org.olat.course.nodes.gta.GTACourseNodeConfiguration" scope="prototype">
+		<constructor-arg index="0" value="false" />
+		<property name="order" value="132" />
+	</bean>
 			
 	<bean id="projectbroker" class="org.olat.course.nodes.projectbroker.ProjectBrokerNodeConfiguration" scope="prototype">
 		<property name="order" value="181" />
diff --git a/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java b/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java
index 67217c099781f06c428a607dc0394e5939fed4e5..c2db876c3673043511dacc17e17dd7db490a7b5a 100644
--- a/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java
+++ b/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java
@@ -37,20 +37,23 @@ import org.olat.course.nodes.GTACourseNode;
  */
 public class GTACourseNodeConfiguration extends AbstractCourseNodeConfiguration {
 
-	private GTACourseNodeConfiguration() {
+	private final boolean individual;
+	
+	private GTACourseNodeConfiguration(boolean individual) {
 		super();
+		this.individual = individual;
 	}
 
 	@Override
 	public CourseNode getInstance() {
-		return new GTACourseNode();
+		return new GTACourseNode(getAlias());
 	}
 
 	@Override
 	public String getLinkText(Locale locale) {
 		Translator fallback = Util.createPackageTranslator(CourseNodeConfiguration.class, locale);
 		Translator translator = Util.createPackageTranslator(this.getClass(), locale, fallback);
-		return translator.translate("title_gta");
+		return individual ? translator.translate("title_ita") : translator.translate("title_gta");
 	}
 
 	@Override
@@ -60,7 +63,7 @@ public class GTACourseNodeConfiguration extends AbstractCourseNodeConfiguration
 
 	@Override
 	public String getAlias() {
-		return "gta";
+		return  individual ? "ita" : "gta";
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/gta/GTAManager.java b/src/main/java/org/olat/course/nodes/gta/GTAManager.java
index 5859a22a63e38fdaa63a9aacedf327f6e78cf7a7..474811c833a7edfa3812b4eed25749ddbcd2550c 100644
--- a/src/main/java/org/olat/course/nodes/gta/GTAManager.java
+++ b/src/main/java/org/olat/course/nodes/gta/GTAManager.java
@@ -145,6 +145,8 @@ public interface GTAManager {
 	
 	public TaskList getTaskList(RepositoryEntryRef entry, GTACourseNode cNode);
 	
+	public int deleteTaskList(RepositoryEntryRef entry, GTACourseNode cNode);
+	
 	
 	public Membership getMembership(IdentityRef identity, RepositoryEntryRef entry, GTACourseNode cNode);
 	
@@ -181,7 +183,10 @@ public interface GTAManager {
 	public AssignmentResponse assignTaskAutomatically(TaskList taskList, BusinessGroup assessedGroup, CourseEnvironment courseEnv, GTACourseNode cNode);
 
 	public AssignmentResponse assignTaskAutomatically(TaskList taskList, Identity assessedIdentity, CourseEnvironment courseEnv, GTACourseNode cNode);
-	
+
+	public TaskProcess firstStep(GTACourseNode cNode);
+
+	public TaskProcess previousStep(TaskProcess currentStep, GTACourseNode cNode);
 	
 	public TaskProcess nextStep(TaskProcess currentStep, GTACourseNode cNode);
 	
diff --git a/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java
new file mode 100644
index 0000000000000000000000000000000000000000..4faf419755f4686d191b0976c8f9b98439438345
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java
@@ -0,0 +1,34 @@
+/**
+ * <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;
+
+/**
+ * 
+ * Initial date: 08.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum GTARelativeToDates {
+	
+	courseStart,// relative to course start defined by a life-cycle
+	courseLaunch,// relative to the course launch by a user
+	enrollment//relative to the enrollment date
+
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java
index 746a050be9df66ffb81a24b701fa8e77e3fc47c1..595b0d213756b8218f4e5cdf1796a842c3965cd8 100644
--- a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java
+++ b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java
@@ -442,6 +442,24 @@ public class GTAManagerImpl implements GTAManager {
 
 		return tasks.isEmpty() ? null : tasks.get(0);
 	}
+	
+	@Override
+	public int deleteTaskList(RepositoryEntryRef entry, GTACourseNode cNode) {
+		TaskList taskList = getTaskList(entry, cNode);
+		
+		int numOfDeletedObjects;
+		if(taskList != null) {
+			String deleteTasks = "delete from gtatask as task where task.taskList.key=:taskListKey";
+			int numOfTasks = dbInstance.getCurrentEntityManager().createQuery(deleteTasks)
+				.setParameter("taskListKey", taskList.getKey())
+				.executeUpdate();
+			dbInstance.getCurrentEntityManager().remove(taskList);
+			numOfDeletedObjects = numOfTasks + 1;
+		} else {
+			numOfDeletedObjects = 0;
+		}
+		return numOfDeletedObjects;
+	}
 
 	@Override
 	public List<Task> getTasks(TaskList taskList) {
@@ -692,6 +710,83 @@ public class GTAManagerImpl implements GTAManager {
 		dbInstance.commit();//make the thing definitiv
 		return mergedtask;
 	}
+	
+	
+	
+	@Override
+	public TaskProcess firstStep(GTACourseNode cNode) {
+		TaskProcess firstStep = null;
+		
+		if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)) {
+			firstStep = TaskProcess.assignment;
+		} else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SUBMIT)) {
+			firstStep = TaskProcess.submit;
+		} else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) {
+			firstStep = TaskProcess.review;
+		} else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) {
+			firstStep = TaskProcess.revision;
+		} else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) {
+			firstStep = TaskProcess.correction;
+		} else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) {
+			firstStep = TaskProcess.solution;
+		} else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_GRADING)) {
+			firstStep = TaskProcess.grading;
+		}
+		
+		return firstStep;
+	}
+
+	@Override
+	public TaskProcess previousStep(TaskProcess currentStep, GTACourseNode cNode) {
+		TaskProcess previousStep = null;
+		switch(currentStep) {
+			case graded:
+			case grading: {
+				if(currentStep != TaskProcess.grading && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_GRADING)) {
+					previousStep = TaskProcess.grading;
+					break;
+				}
+			}
+			case solution: {
+				if(currentStep != TaskProcess.solution && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) {
+					previousStep = TaskProcess.solution;
+					break;
+				}
+			}
+			case correction: {
+				if(currentStep != TaskProcess.correction && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) {
+					previousStep = TaskProcess.correction;
+					break;
+				}
+			}
+			case revision: {
+				if(currentStep != TaskProcess.revision && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) {
+					previousStep = TaskProcess.revision;
+					break;
+				}
+			}
+			case review: {
+				if(currentStep != TaskProcess.review && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) {
+					previousStep = TaskProcess.review;
+					break;
+				}
+			}
+			case submit: {
+				if(currentStep != TaskProcess.submit && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SUBMIT)) {
+					previousStep = TaskProcess.submit;
+					break;
+				}
+			}
+			case assignment: {
+				if(currentStep != TaskProcess.assignment && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)) {
+					previousStep = TaskProcess.assignment;
+					break;
+				}
+			}
+		}
+
+		return previousStep;
+	}
 
 	@Override
 	public TaskProcess nextStep(TaskProcess currentStep, GTACourseNode cNode) {
diff --git a/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java b/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java
index 71fbaccc78ca30863d85c03c3c04a85bacd4b376..ab761b65000cdaf1542ebcf172266d1d6eef938d 100644
--- a/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java
+++ b/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java
@@ -83,7 +83,7 @@ public class TaskImpl implements Task, CreateInfo, Persistable, ModifiedInfo {
 	@Column(name="g_rev_loop", nullable=false, insertable=true, updatable=true)
 	private int revisionLoop;
 	
-	@Column(name="g_taskname", nullable=false, insertable=true, updatable=true)
+	@Column(name="g_taskname", nullable=true, insertable=true, updatable=true)
 	private String taskName;
 
 	@ManyToOne(targetEntity=TaskListImpl.class,fetch=FetchType.LAZY,optional=false)
diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java
index 868517dbdc2759013df742280930d318b870a16b..364de565118c75f16b2b508ca3eae448f7cca5e4 100644
--- a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java
+++ b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java
@@ -19,20 +19,28 @@
  */
 package org.olat.course.nodes.gta.rule;
 
+import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.olat.basesecurity.GroupRoles;
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
 import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
+import org.olat.course.assessment.manager.UserCourseInformationsManager;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskList;
@@ -45,7 +53,9 @@ import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.modules.reminder.rule.LaunchUnit;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRelationType;
+import org.olat.repository.RepositoryService;
 import org.olat.repository.manager.RepositoryEntryRelationDAO;
+import org.olat.repository.model.RepositoryEntryLifecycle;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -79,20 +89,21 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 		return identities == null ? Collections.<Identity>emptyList() : identities;
 	}
 	
-	protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r) {
+	protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) {
 		List<Identity> identities = null;
-		Date dueDate = getDueDate(gtaNode);
-		if(dueDate != null) {
-			int value = Integer.parseInt(r.getRightOperand());
-			String unit = r.getRightUnit();
-			Date now = new Date();
-			if(near(dueDate, now, value, LaunchUnit.valueOf(unit))) {
+		if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES)) {
+			identities = evaluateRelativeDateRule(entry, gtaNode, rule);
+		} else {
+			Date dueDate = getDueDate(gtaNode);
+			if(dueDate != null && isNear(dueDate, now(), rule)) {
 				identities = getPeopleToRemind(entry, gtaNode);
 			}
 		}
 		return identities == null ? Collections.<Identity>emptyList() : identities;
 	}
 	
+	protected abstract List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r);
+	
 	protected abstract Date getDueDate(GTACourseNode gtaNode);
 	
 	protected List<Identity> getPeopleToRemind(RepositoryEntry entry, GTACourseNode gtaNode) {
@@ -105,6 +116,95 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 		}
 	}
 	
+	protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode,
+			int numOfDays, String relativeTo, ReminderRuleImpl rule) {
+		List<Identity> identities = null;
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo);
+			switch(rel) {
+				case courseStart: {
+					RepositoryEntryLifecycle lifecycle = entry.getLifecycle();
+					if(lifecycle != null && lifecycle.getValidFrom() != null) {
+						Date referenceDate = getDate(lifecycle.getValidFrom(),  numOfDays);
+						if(isNear(referenceDate, now(), rule)) {
+							identities = getPeopleToRemind(entry, gtaNode);
+						}
+					}
+					break;
+				}
+	
+				case courseLaunch: {
+					UserCourseInformationsManager userCourseInformationsManager = CoreSpringFactory.getImpl(UserCourseInformationsManager.class);
+					Map<Long,Date> initialLaunchDates = userCourseInformationsManager.getInitialLaunchDates(entry.getOlatResource().getResourceableId());
+					Map<Long,Date> dueDates = getDueDates(initialLaunchDates, numOfDays);
+					identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule);
+					break;
+				}
+				case enrollment: {
+					RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class);
+					Map<Long,Date> enrollmentDates = repositoryService.getEnrollmentDates(entry);
+					Map<Long,Date> dueDates = getDueDates(enrollmentDates, numOfDays);
+					identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule);
+					break;
+				}
+			}	
+		}
+		return identities;
+	}
+	
+	protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode,
+			Map<Long,Date> dates, ReminderRuleImpl rule) {
+		
+		Date now = now();
+		Set<Long> potentialidentityKeys = new HashSet<>();
+		for(Map.Entry<Long, Date> entryDate:dates.entrySet()) {
+			Long identityKey = entryDate.getKey();
+			Date date = entryDate.getValue();
+			if(isNear(date, now, rule)) {
+				potentialidentityKeys.add(identityKey);
+			}	
+		}
+
+		List<Identity> identities = null;
+		if(potentialidentityKeys.size() > 0) {
+			List<Identity> allIdentities = getPeopleToRemind(entry, gtaNode);
+			identities = new ArrayList<>();
+			for(Identity identity:allIdentities) {
+				if(potentialidentityKeys.contains(identity.getKey())) {
+					identities.add(identity);
+				}
+			}
+		}
+		return identities;
+	}
+	
+	private Map<Long,Date> getDueDates(Map<Long,Date> referenceDates, int numOfDays) {
+		Map<Long, Date> dueDates = new HashMap<>();
+		if(referenceDates != null && referenceDates.size() > 0) {
+			Calendar cal = Calendar.getInstance();
+			for(Map.Entry<Long, Date> referenceEntry:referenceDates.entrySet()) {
+				Long identityKey = referenceEntry.getKey();
+				cal.setTime(referenceEntry.getValue());
+				cal.add(Calendar.DATE, numOfDays);
+				dueDates.put(identityKey, cal.getTime());
+			}
+		}
+		return dueDates;
+	}
+	
+	private Date getDate(Date referenceDate, int numOfDays) {
+		Date date = null;
+		if(referenceDate != null) {
+			Calendar cal = Calendar.getInstance();
+			cal.setTime(referenceDate);
+			cal.add(Calendar.DATE, numOfDays);
+			cal.set(Calendar.SECOND, 0);
+			cal.set(Calendar.MILLISECOND, 0);
+			date = cal.getTime();
+		}
+		return date;
+	}
+	
 	protected List<Identity> getGroupsToRemind(TaskList taskList, GTACourseNode gtaNode) {
 		List<Task> tasks = gtaManager.getTasks(taskList);
 		Set<BusinessGroup> doneTasks = new HashSet<BusinessGroup>();
@@ -143,6 +243,18 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 		return identities;
 	}
 	
+	protected Date now() {
+		Calendar cal = Calendar.getInstance();
+		cal.set(Calendar.SECOND, 0);
+		cal.set(Calendar.MILLISECOND, 0);
+		return cal.getTime();
+	}
+	
+	protected boolean isNear(Date dueDate, Date now, ReminderRuleImpl r) {
+		int value = Integer.parseInt(r.getRightOperand());
+		String unit = r.getRightUnit();
+		return near(dueDate, now, value, LaunchUnit.valueOf(unit));
+	}
 	
 	private boolean near(Date date, Date now, int distance, LaunchUnit unit) {
 		double between = -1;
@@ -160,7 +272,8 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 				between = yearsBetween(now, date);
 				break;
 		}
-		return  between <= distance || between < 0.0;
+		// 0.1 to let +- 2 hours to match
+		return  between <= distance || between - 0.1 <= distance || between < 0.0;
 	}
 	
 	private double daysBetween(Date d1, Date d2) {
diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java
index d5ac5d5595776589fb81c92b3840b0433e8f85df..6d8feebbe2720f62d3ef3a14ab120dba887b9d6b 100644
--- a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java
+++ b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java
@@ -20,12 +20,16 @@
 package org.olat.course.nodes.gta.rule;
 
 import java.util.Date;
+import java.util.List;
 
+import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.reminder.ReminderRule;
 import org.olat.modules.reminder.RuleEditorFragment;
+import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.stereotype.Service;
 
@@ -63,4 +67,15 @@ public class AssignTaskRuleSPI extends AbstractDueDateTaskRuleSPI {
 		}
 		return dueDate;
 	}
+
+	@Override
+	protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) {
+		List<Identity> identities = null;
+		int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1);
+		String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule);
+		}
+		return identities;
+	}
 }
diff --git a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java
index a22e6eae58692835b78043362bc2a98d3b1de155..326c05d16b11bf6e5ab381fc550396ab354dcaa5 100644
--- a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java
+++ b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java
@@ -20,7 +20,10 @@
 package org.olat.course.nodes.gta.rule;
 
 import java.util.Date;
+import java.util.List;
 
+import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor;
@@ -28,6 +31,7 @@ import org.olat.group.BusinessGroupService;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.reminder.ReminderRule;
 import org.olat.modules.reminder.RuleEditorFragment;
+import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.manager.RepositoryEntryRelationDAO;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -74,4 +78,15 @@ public class SubmissionTaskRuleSPI extends AbstractDueDateTaskRuleSPI {
 		}
 		return dueDate;
 	}
+	
+	@Override
+	protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) {
+		List<Identity> identities = null;
+		int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1);
+		String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule);
+		}
+		return identities;
+	}
 }
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java b/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9aec7c6a22be0339b83c3c3a03150f64ac81f0fb
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java
@@ -0,0 +1,81 @@
+/**
+ * <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;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.link.LinkFactory;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.controller.BasicController;
+import org.olat.course.archiver.ArchiveResource;
+import org.olat.course.nodes.ArchiveOptions;
+import org.olat.course.nodes.AssessmentToolOptions;
+import org.olat.course.nodes.GTACourseNode;
+import org.olat.course.run.environment.CourseEnvironment;
+import org.olat.resource.OLATResource;
+
+/**
+ * 
+ * Initial date: 07.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BulkDownloadToolController extends BasicController {
+	
+	private final Link downloadButton;
+
+	private final ArchiveOptions options;
+	private final OLATResource courseOres;
+	private final GTACourseNode courseNode;
+	
+	public BulkDownloadToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
+			AssessmentToolOptions asOptions, GTACourseNode courseNode) {
+		super(ureq, wControl);
+		this.options = new ArchiveOptions();
+		this.options.setGroup(asOptions.getGroup());
+		this.options.setIdentities(asOptions.getIdentities());
+		this.courseNode = courseNode;
+		courseOres = courseEnv.getCourseGroupManager().getCourseResource();
+		
+		downloadButton = LinkFactory.createButton("bulk.download.title", null, this);
+		downloadButton.setTranslator(getTranslator());
+		putInitialPanel(downloadButton);
+		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if(downloadButton == source) {
+			doDownload(ureq);
+		}
+	}
+	
+	private void doDownload(UserRequest ureq) {
+		ArchiveResource resource = new ArchiveResource(courseNode, courseOres, options, getLocale());
+		ureq.getDispatchResult().setResultingMediaResource(resource);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/DescriptionWithTooltipCellRenderer.java b/src/main/java/org/olat/course/nodes/gta/ui/DescriptionWithTooltipCellRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..449b82bae4cfd68aa7bb10eebd91d230a0713f35
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/DescriptionWithTooltipCellRenderer.java
@@ -0,0 +1,81 @@
+/**
+ * <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;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
+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;
+import org.olat.core.util.Formatter;
+import org.olat.core.util.filter.FilterFactory;
+
+/**
+ * 
+ * Initial date: 06.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class DescriptionWithTooltipCellRenderer implements FlexiCellRenderer {
+	
+	private final AtomicInteger positionCounter = new AtomicInteger(1);
+
+	@Override
+	public void render(Renderer renderer, StringOutput sb, Object cellValue,
+			int row, FlexiTableComponent source, URLBuilder ubu, Translator translator) {
+		if(cellValue instanceof String) {
+			String desc = (String)cellValue;
+			if(desc.length() > 50) {
+				String truncatedDesc = FilterFactory.getHtmlTagsFilter().filter(desc);
+				truncatedDesc = Formatter.truncate(truncatedDesc, 50, "");
+
+				String pos = Integer.toString(positionCounter.incrementAndGet());
+				sb.append("<span id='o_sel_desc_").append(pos).append("' href='javascript:void(0); return false;'>")
+				  .append(truncatedDesc)
+				  .append("\u2026</span>");
+				
+				sb.append("<div id='o_sel_desc_tooltip_").append(pos).append("' style='display:none;'>")
+				  .append(desc)
+				  .append("</div>");
+				
+				sb.append("<script type='text/javascript'>")
+			      .append("/* <![CDATA[ */")
+				  .append("jQuery(function() {\n")
+				  .append("  jQuery('#o_sel_desc_").append(pos).append("').tooltip({\n")
+				  .append("	   html: true,\n")
+				  .append("	   container: 'body',\n")
+				  .append("	   placement: 'bottom',\n")
+				  .append("    title: function(){ return jQuery('#o_sel_desc_tooltip_").append(pos).append("').html(); }\n")
+				  .append("  });\n")
+				  .append("  jQuery('#o_sel_desc_").append(pos).append("').on('click', function(){\n")
+				  .append("	   jQuery('#o_sel_desc_").append(pos).append("').tooltip('hide');\n")
+				  .append("  });\n")
+				  .append("});")
+				  .append("/* ]]> */")
+				  .append("</script>");
+			} else {
+				sb.append(desc);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java
index 23b75d63a569173d8b8272ad23a56bd89dc62e15..5e4350d42fd5a568c6e383f951a9dfd68b52e5b9 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java
@@ -19,20 +19,25 @@
  */
 package org.olat.course.nodes.gta.ui;
 
+import java.util.Calendar;
 import java.util.Date;
 
 import org.olat.core.commons.services.notifications.PublisherData;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
 import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController;
 import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.id.Identity;
 import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.course.assessment.manager.UserCourseInformationsManager;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskList;
@@ -42,6 +47,8 @@ import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.olat.repository.model.RepositoryEntryLifecycle;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -72,10 +79,20 @@ public abstract class GTAAbstractController extends BasicController {
 	
 	protected final boolean businessGroupTask;
 	
+	protected GTAStepPreferences stepPreferences;
+	
 	private ContextualSubscriptionController contextualSubscriptionCtr;
 	
+	private Date assignmentDueDate;
+	private Date submissionDueDate;
+	private Date solutionDueDate;
+	
 	@Autowired
 	protected GTAManager gtaManager;
+	@Autowired
+	protected RepositoryService repositoryService;
+	@Autowired
+	protected UserCourseInformationsManager userCourseInformationsManager;
 	
 	public GTAAbstractController(UserRequest ureq, WindowControl wControl,
 			GTACourseNode gtaNode, CourseEnvironment courseEnv, boolean withTitle, boolean withGrading) {
@@ -111,6 +128,13 @@ public abstract class GTAAbstractController extends BasicController {
 		publisherData = gtaManager.getPublisherData(courseEnv, gtaNode);
 		subsContext = gtaManager.getSubscriptionContext(courseEnv, gtaNode);
 		
+		stepPreferences = (GTAStepPreferences)ureq.getUserSession()
+				.getGuiPreferences()
+				.get(GTAStepPreferences.class, taskList.getKey().toString());
+		if(stepPreferences == null) {
+			stepPreferences = new GTAStepPreferences();
+		}
+		
 		initContainer(ureq);
 		process(ureq);
 	}
@@ -140,6 +164,9 @@ public abstract class GTAAbstractController extends BasicController {
 		mainVC.contextPut("assignmentEnabled", assignment);
 		if(assignment) {
 			task = stepAssignment(ureq, task);
+		} else if(task == null) {
+			TaskProcess firstStep = gtaManager.firstStep(gtaNode);
+			task = gtaManager.createTask(null, taskList, firstStep, assessedGroup, assessedIdentity, gtaNode);
 		}
 		
 		boolean submit = config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT);
@@ -171,10 +198,46 @@ public abstract class GTAAbstractController extends BasicController {
 		if(grading) {
 			stepGrading(ureq, task);
 		}
+		
+		collapsedContents(task);
+	}
+	
+	protected final void collapsedContents(Task currentTask) {
+		TaskProcess status = null;
+		TaskProcess previousStatus = null;
+		if(currentTask != null) {
+			status = currentTask.getTaskStatus();
+			previousStatus = gtaManager.previousStep(status, gtaNode);
+		}
+		
+		boolean assignment = Boolean.TRUE.equals(stepPreferences.getAssignement())
+				|| TaskProcess.assignment.equals(status) || TaskProcess.assignment.equals(previousStatus);
+		mainVC.contextPut("collapse_assignement", new Boolean(assignment));
+		
+		boolean submit = Boolean.TRUE.equals(stepPreferences.getSubmit())
+				|| TaskProcess.submit.equals(status) || TaskProcess.submit.equals(previousStatus);
+		mainVC.contextPut("collapse_submit", new Boolean(submit));
+		
+		boolean reviewAndCorrection = Boolean.TRUE.equals(stepPreferences.getReviewAndCorrection())
+				|| TaskProcess.review.equals(status) || TaskProcess.review.equals(previousStatus);
+		mainVC.contextPut("collapse_reviewAndCorrection", new Boolean(reviewAndCorrection));
+		
+		boolean revision = Boolean.TRUE.equals(stepPreferences.getRevision())
+				|| TaskProcess.revision.equals(status) || TaskProcess.revision.equals(previousStatus)
+				|| TaskProcess.correction.equals(status) || TaskProcess.correction.equals(previousStatus);
+		mainVC.contextPut("collapse_revision", new Boolean(revision));
+		
+		boolean solution = Boolean.TRUE.equals(stepPreferences.getSolution())
+				|| TaskProcess.solution.equals(status) || TaskProcess.solution.equals(previousStatus);
+		mainVC.contextPut("collapse_solution", new Boolean(solution));
+		
+		boolean grading = Boolean.TRUE.equals(stepPreferences.getGrading())
+				|| TaskProcess.grading.equals(status) || TaskProcess.grading.equals(previousStatus);
+		mainVC.contextPut("collapse_grading", new Boolean(grading));
 	}
 	
 	protected Task stepAssignment(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) {
-		Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
+		Date dueDate = getAssignementDueDate();
 		if(dueDate != null) {
 			String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate);
 			mainVC.contextPut("assignmentDueDate", date);
@@ -188,8 +251,66 @@ public abstract class GTAAbstractController extends BasicController {
 		return assignedTask;
 	}
 	
+	protected void resetDueDates() {
+		assignmentDueDate = null;
+		submissionDueDate = null;
+		solutionDueDate = null;
+	}
+	
+	protected Date getAssignementDueDate() {
+		if(assignmentDueDate == null) {
+			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
+			boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+			if(relativeDate) {
+				int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1);
+				String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+				if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+					assignmentDueDate = getReferenceDate(numOfDays, relativeTo);
+				}
+			} else if(dueDate != null) {
+				assignmentDueDate = dueDate;
+			}
+		}
+		return assignmentDueDate;
+	}
+	
+	protected Date getReferenceDate(int numOfDays, String relativeTo) {
+		Date dueDate = null;
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo);
+			Date referenceDate = null;
+			switch(rel) {
+				case courseStart: {
+					RepositoryEntryLifecycle lifecycle = courseEntry.getLifecycle();
+					if(lifecycle != null && lifecycle.getValidFrom() != null) {
+						referenceDate = lifecycle.getValidFrom();
+					}
+					break;
+				}
+				case courseLaunch: {
+					referenceDate = userCourseInformationsManager
+							.getInitialLaunchDate(courseEnv.getCourseResourceableId(), assessedIdentity);
+					break;
+				}
+				case enrollment: {
+					referenceDate = repositoryService
+							.getEnrollmentDate(courseEntry, assessedIdentity);
+					break;
+				}
+			}
+			
+			if(referenceDate != null) {
+				Calendar cal = Calendar.getInstance();
+				cal.setTime(referenceDate);
+				cal.add(Calendar.DATE, numOfDays);
+				dueDate = cal.getTime();
+			}
+		}
+		return dueDate;
+	}
+	
 	protected Task stepSubmit(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
-		Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE);
+		Date dueDate = getSubmissionDueDate();
 		if(dueDate != null) {
 			String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate);
 			mainVC.contextPut("submitDueDate", date);
@@ -204,6 +325,23 @@ public abstract class GTAAbstractController extends BasicController {
 		return assignedTask;
 	}
 	
+	protected Date getSubmissionDueDate() {
+		if(submissionDueDate == null) {
+			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE);
+			boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+			if(relativeDate) {
+				int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1);
+				String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+				if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+					submissionDueDate = getReferenceDate(numOfDays, relativeTo);
+				}
+			} else if(dueDate != null) {
+				submissionDueDate = dueDate;
+			}
+		}
+		return submissionDueDate;
+	}
+	
 	protected Task stepReviewAndCorrection(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
 		return assignedTask;
 	}
@@ -213,7 +351,7 @@ public abstract class GTAAbstractController extends BasicController {
 	}
 	
 	protected Task stepSolution(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
-		Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
+		Date availableDate = getSolutionDueDate();
 		if(availableDate != null) {
 			String date = Formatter.getInstance(getLocale()).formatDateAndTime(availableDate);
 			mainVC.contextPut("solutionAvailableDate", date);
@@ -221,6 +359,23 @@ public abstract class GTAAbstractController extends BasicController {
 		return assignedTask;
 	}
 	
+	protected Date getSolutionDueDate() {
+		if(solutionDueDate == null) {
+			boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
+			if(relativeDate) {
+				int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1);
+				String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO);
+				if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+					solutionDueDate = getReferenceDate(numOfDays, relativeTo);
+				}
+			} else if(dueDate != null) {
+				solutionDueDate = dueDate;
+			}
+		}
+		return solutionDueDate;
+	}
+	
 	protected Task stepGrading(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) {
 		if(businessGroupTask) {
 			String groupLog = courseEnv.getAuditManager().getUserNodeLog(gtaNode, assessedGroup);
@@ -239,4 +394,39 @@ public abstract class GTAAbstractController extends BasicController {
 		}
 		return assignedTask;
 	}
+	
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if("show".equals(event.getCommand())) {
+			doShow(ureq);
+		} else if("hide".equals(event.getCommand())) {
+			doHide(ureq);
+		}
+	}
+	
+	private void doShow(UserRequest ureq) {
+		String step = ureq.getParameter("step");
+		doSaveStepPreferences(ureq, step, Boolean.TRUE);
+	}
+	
+	private void doHide(UserRequest ureq) {
+		String step = ureq.getParameter("step");
+		doSaveStepPreferences(ureq, step, Boolean.FALSE);
+	}
+
+	private void doSaveStepPreferences(UserRequest ureq, String step, Boolean showHide) {
+		if(step == null) return;
+		switch(step) {
+			case "assignment": stepPreferences.setAssignement(showHide); break;
+			case "submit": stepPreferences.setSubmit(showHide); break;
+			case "reviewAndCorrection": stepPreferences.setReviewAndCorrection(showHide); break;
+			case "revision": stepPreferences.setRevision(showHide); break;
+			case "solution": stepPreferences.setSolution(showHide); break;
+			case "grading": stepPreferences.setGrading(showHide); break;
+			default: {};
+		}
+		
+		ureq.getUserSession().getGuiPreferences()
+			.putAndSave(GTAStepPreferences.class, taskList.getKey().toString(), stepPreferences);
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java
index 7e8c49f697c2a8f8fec1f5dbc74198c43d73b214..4b881ff69e882b5c4df0ec44c68aedc13072b69c 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java
@@ -106,9 +106,9 @@ public class GTAAvailableTaskController extends FormBasicController {
 		this.gtaNode = gtaNode;
 		this.taskDefs = taskDefs;
 		this.taskList = taskList;
-		this.courseEnv = courseEnv;
 		this.assessedGroup = assessedGroup;
 		this.assessedIdentity = assessedIdentity;
+		this.courseEnv = courseEnv;
 		businessGroupTask = GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE));
 		initForm(ureq);
 	}
@@ -117,7 +117,8 @@ public class GTAAvailableTaskController extends FormBasicController {
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ATDCols.title.i18nKey(), ATDCols.title.ordinal()));
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ATDCols.description.i18nKey(), ATDCols.description.ordinal()));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ATDCols.description.i18nKey(), ATDCols.description.ordinal(),
+				new DescriptionWithTooltipCellRenderer()));
 		
 		boolean preview = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_PREVIEW);
 		if(preview) {
@@ -158,7 +159,6 @@ public class GTAAvailableTaskController extends FormBasicController {
 				descriptionLink.setIconLeftCSS("o_icon o_icon_description");
 			}
 			
-			
 			File taskFile = new File(taskFolder, filename);
 			DownloadLink download = uifactory.addDownloadLink("prev-" + CodeHelper.getRAMUniqueID(), filename, null, taskFile, tableEl);
 
@@ -321,7 +321,7 @@ public class GTAAvailableTaskController extends FormBasicController {
 			AvailableTask task = getObject(row);
 			switch(ATDCols.values()[col]) {
 				case title: return task.getTaskDef().getTitle();
-				case description: return task.getDescriptionLink();
+				case description: return task.getTaskDef().getDescription();
 				case preview: return task.getDownloadLink();
 				default: return "ERROR";
 			}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java
index 8bddf3b2c83b66c0d3e6aec9fc2d46880a7bf3e5..6f9a142ddeba307c76af651b401f6544048437db 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java
@@ -348,7 +348,8 @@ public class GTACoachController extends GTAAbstractController {
 		} else if(needRevisionsButton == source) {
 			Task assignedTask = submitCorrectionsCtrl.getAssignedTask();
 			doRevisions(ureq, assignedTask);
-		}		
+		}
+		super.event(ureq, source, event);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java
index b40569b303771a3fbd7e30be178c5e78c7d9c295..a73c6007bfeb15e9d98140bfc50ce82ced185ea6 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java
@@ -89,7 +89,7 @@ public class GTAEditController extends ActivateableTabbableDefaultController {
 				accessCondition, AssessmentHelper.getAssessableNodes(editorModel, gtaNode), euce);		
 		listenTo(accessibilityCondCtrl);
 		//workflow
-		workflowCtrl = new GTAWorkflowEditController(ureq, getWindowControl(), config, euce.getCourseEditorEnv());
+		workflowCtrl = new GTAWorkflowEditController(ureq, getWindowControl(), gtaNode, euce.getCourseEditorEnv());
 		listenTo(workflowCtrl);
 		//assignment
 		assignmentCtrl = new GTAAssignmentEditController(ureq, getWindowControl(), config, tasksDir);
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java
index 3ae3c7cb9a493b8e2c1ff1dd20bd8473734b4828..42d13b40627b5ad4d3676c69e9d092bbefbcf676 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java
@@ -36,6 +36,8 @@ 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.CloseableCalloutWindowController;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.core.id.Identity;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.mail.MailBundle;
@@ -45,7 +47,12 @@ import org.olat.core.util.mail.MailManager;
 import org.olat.core.util.mail.MailTemplate;
 import org.olat.core.util.mail.MailerResult;
 import org.olat.core.util.vfs.VFSContainer;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.course.assessment.AssessmentManager;
 import org.olat.course.nodes.GTACourseNode;
+import org.olat.course.nodes.MSCourseNode;
 import org.olat.course.nodes.gta.AssignmentResponse;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.Task;
@@ -72,6 +79,7 @@ public class GTAParticipantController extends GTAAbstractController {
 
 	private MSCourseNodeRunController gradingCtrl;
 	private SubmitDocumentsController submitDocCtrl;
+	private DialogBoxController confirmSubmitDialog;
 	private GTAAssignedTaskController assignedTaskCtrl;
 	private GTAAvailableTaskController availableTaskCtrl;
 	private CloseableCalloutWindowController chooserCalloutCtrl;
@@ -97,6 +105,7 @@ public class GTAParticipantController extends GTAAbstractController {
 	protected void initContainer(UserRequest ureq) {
 		mainVC = createVelocityContainer("run");
 		putInitialPanel(mainVC);
+		
 		initFlow() ;
 	}
 
@@ -135,7 +144,7 @@ public class GTAParticipantController extends GTAAbstractController {
 			mainVC.contextPut("assignmentCssClass", "o_active");
 			
 			//assignment open?
-			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
+			Date dueDate = getAssignementDueDate();
 			if(dueDate != null && dueDate.compareTo(new Date()) < 0) {
 				//assignment is closed
 				mainVC.contextPut("assignmentClosed", Boolean.TRUE);
@@ -240,7 +249,7 @@ public class GTAParticipantController extends GTAAbstractController {
 		
 		submitButton  = LinkFactory.createCustomLink("run.submit.button", "submit", "run.submit.button", Link.BUTTON, mainVC, this);
 		submitButton.setCustomEnabledLinkCSS("btn btn-primary");
-		submitButton.setIconLeftCSS("o_icon o_icon o_icon_submit");
+		submitButton.setIconLeftCSS("o_icon o_icon_submit");
 	}
 	
 	private void setSubmittedDocumentsController(UserRequest ureq) {
@@ -256,6 +265,18 @@ public class GTAParticipantController extends GTAAbstractController {
 		mainVC.put("submittedDocs", submittedDocCtrl.getInitialComponent());
 	}
 	
+	private void doConfirmSubmit(UserRequest ureq, Task task) {
+		String title = translate("run.submit.button");
+		String text;
+		if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) {
+			text = translate("run.submit.confirm.group", new String[]{ assessedGroup.getName() });
+		} else {
+			text = translate("run.submit.confirm");
+		}
+		confirmSubmitDialog = activateOkCancelDialog(ureq, title, text, confirmSubmitDialog);
+		confirmSubmitDialog.setUserObject(task);
+	}
+	
 	private void doSubmitDocuments(UserRequest ureq, Task task) {
 		task = gtaManager.updateTask(task, TaskProcess.review);
 		showInfo("run.documents.successfully.submitted");
@@ -264,6 +285,7 @@ public class GTAParticipantController extends GTAAbstractController {
 		
 		cleanUpProcess();
 		process(ureq);
+		doUpdateAttempts();
 
 		//do send e-mail
 		if(config.getBooleanSafe(GTACourseNode.GTASK_SUBMISSION_MAIL_CONFIRMATION)) {
@@ -271,6 +293,22 @@ public class GTAParticipantController extends GTAAbstractController {
 		}
 	}
 	
+	private void doUpdateAttempts() {
+		if(businessGroupTask) {
+			List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name());
+			AssessmentManager assessmentManager = courseEnv.getAssessmentManager();
+			assessmentManager.preloadCache(identities);
+			ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
+
+			for(Identity identity:identities) {
+				UserCourseEnvironment userCourseEnv = AssessmentHelper.createAndInitUserCourseEnvironment(identity, course);
+				gtaNode.incrementUserAttempts(userCourseEnv);
+			}
+		} else {
+			gtaNode.incrementUserAttempts(userCourseEnv);
+		}
+	}
+	
 	private void doSubmissionEmail() {
 		String body = config.getStringValue(GTACourseNode.GTASK_SUBMISSION_TEXT);
 		if(StringHelper.containsNonWhitespace(body)) {
@@ -373,7 +411,8 @@ public class GTAParticipantController extends GTAAbstractController {
 	
 	private void setRevisionsAndCorrections(UserRequest ureq, Task task) {
 		if(task.getRevisionLoop() > 0) {
-			revisionDocumentsCtrl = new GTAParticipantRevisionAndCorrectionsController(ureq, getWindowControl(), courseEnv, task, gtaNode, assessedGroup);
+			revisionDocumentsCtrl = new GTAParticipantRevisionAndCorrectionsController(ureq, getWindowControl(), 
+					userCourseEnv, task, gtaNode, assessedGroup);
 			listenTo(revisionDocumentsCtrl);
 			mainVC.put("revisionDocs", revisionDocumentsCtrl.getInitialComponent());
 		}
@@ -411,7 +450,7 @@ public class GTAParticipantController extends GTAAbstractController {
 	}
 	
 	private void setSolutions(UserRequest ureq) {
-		Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
+		Date availableDate = getSolutionDueDate();
 		boolean visible = availableDate == null || availableDate.compareTo(new Date()) <= 0;
 		if(visible) {
 			File documentsDir = gtaManager.getSolutionsDirectory(courseEnv, gtaNode);
@@ -436,6 +475,11 @@ public class GTAParticipantController extends GTAAbstractController {
 			}
 		}
 		
+		String infoTextUser = config.getStringValue(MSCourseNode.CONFIG_KEY_INFOTEXT_USER);
+	    if(StringHelper.containsNonWhitespace(infoTextUser)) {
+	    	mainVC.contextPut("gradingInfoTextUser", StringHelper.xssScan(infoTextUser));
+	    }
+		
 		if(config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)
 				|| config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT)
 				|| config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)
@@ -486,6 +530,7 @@ public class GTAParticipantController extends GTAAbstractController {
 	private void setGroupHeaders(BusinessGroup group) {
 		mainVC.contextPut("groupName", group.getName());
 		openGroupButton = LinkFactory.createButton("open.group", mainVC, this);
+		openGroupButton.setIconLeftCSS("o_icon o_icon_group");
 	}
 	
 	private void setMultiGroupsSelection() {
@@ -505,8 +550,9 @@ public class GTAParticipantController extends GTAAbstractController {
 			doChangeBusinessGroup(ureq);
 		} else if(submitButton == source) {
 			Task assignedTask = submitDocCtrl.getAssignedTask();
-			doSubmitDocuments(ureq, assignedTask);
+			doConfirmSubmit(ureq, assignedTask);
 		}
+		super.event(ureq, source, event);
 	}
 	
 	@Override
@@ -524,6 +570,7 @@ public class GTAParticipantController extends GTAAbstractController {
 		} else if(businessGroupChooserCtrl == source) {
 			if(event == Event.DONE_EVENT && businessGroupChooserCtrl.getSelectGroup() != null) {
 				cleanUpProcess();
+				resetDueDates();
 				assessedGroup = businessGroupChooserCtrl.getSelectGroup();
 				process(ureq);
 			}
@@ -531,10 +578,17 @@ public class GTAParticipantController extends GTAAbstractController {
 			cleanUpPopups();
 		} else if(chooserCalloutCtrl == source) {
 			cleanUpPopups();
+		} else if(confirmSubmitDialog == source) {
+			if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) {
+				Task task = (Task)confirmSubmitDialog.getUserObject();
+				doSubmitDocuments(ureq, task);
+			}
+			cleanUpPopups();
 		} else if(submitDocCtrl == source) {
 			if(event instanceof SubmitEvent) {
 				Task assignedTask = submitDocCtrl.getAssignedTask();
 				gtaManager.log("Submit", (SubmitEvent)event, assignedTask, getIdentity(), assessedIdentity, assessedGroup, courseEnv, gtaNode);
+				doUpdateAttempts();
 			}
 		}
 		super.event(ureq, source, event);
@@ -578,8 +632,10 @@ public class GTAParticipantController extends GTAAbstractController {
 
 	private void cleanUpPopups() {
 		removeAsListenerAndDispose(businessGroupChooserCtrl);
+		removeAsListenerAndDispose(confirmSubmitDialog);
 		removeAsListenerAndDispose(chooserCalloutCtrl);
 		businessGroupChooserCtrl = null;
+		confirmSubmitDialog = null;
 		chooserCalloutCtrl = null;
 	}
 
@@ -587,8 +643,7 @@ public class GTAParticipantController extends GTAAbstractController {
 		String businessPath = "[BusinessGroup:" + assessedGroup.getKey() + "]";
 		NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
 	}
-	
-	
+
 	private void doChangeBusinessGroup(UserRequest ureq) {
 		removeAsListenerAndDispose(businessGroupChooserCtrl);
 		removeAsListenerAndDispose(chooserCalloutCtrl);
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java
index e32dbc7dea424c54cf72262e28a39b62839b2586..0b523023e3df91440b4c3358d1f18b585e787b53 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java
@@ -23,6 +23,7 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.olat.basesecurity.GroupRoles;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.link.Link;
@@ -32,7 +33,12 @@ 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.controller.BasicController;
+import org.olat.core.id.Identity;
 import org.olat.core.util.vfs.VFSContainer;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.course.assessment.AssessmentManager;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.GTAType;
@@ -40,7 +46,9 @@ import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskHelper;
 import org.olat.course.nodes.gta.TaskProcess;
 import org.olat.course.run.environment.CourseEnvironment;
+import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupService;
 import org.olat.modules.ModuleConfiguration;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -63,15 +71,20 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl
 	private final GTACourseNode gtaNode;
 	private final BusinessGroup assessedGroup;
 	private final CourseEnvironment courseEnv;
+	private final UserCourseEnvironment assessedUserCourseEnv;
 	
 	@Autowired
 	private GTAManager gtaManager;
+	@Autowired
+	private BusinessGroupService businessGroupService;
 	
-	public GTAParticipantRevisionAndCorrectionsController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
-			Task assignedTask, GTACourseNode gtaNode, BusinessGroup assessedGroup) {
+	public GTAParticipantRevisionAndCorrectionsController(UserRequest ureq, WindowControl wControl,
+			UserCourseEnvironment assessedUserCourseEnv,Task assignedTask,
+			GTACourseNode gtaNode, BusinessGroup assessedGroup) {
 		super(ureq, wControl);
 		this.gtaNode = gtaNode;
-		this.courseEnv = courseEnv;
+		courseEnv = assessedUserCourseEnv.getCourseEnvironment();
+		this.assessedUserCourseEnv = assessedUserCourseEnv;
 		this.assignedTask = assignedTask;
 		this.assessedGroup = assessedGroup;
 		this.businessGroupTask = GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE));
@@ -204,6 +217,19 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl
 	private void doSubmitRevisions() {
 		assignedTask = gtaManager.updateTask(assignedTask, TaskProcess.correction);
 		gtaManager.log("Revision", "revision submitted", assignedTask, getIdentity(), getIdentity(), assessedGroup, courseEnv, gtaNode);
-		
+
+		if(businessGroupTask) {
+			List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name());
+			AssessmentManager assessmentManager = courseEnv.getAssessmentManager();
+			assessmentManager.preloadCache(identities);
+			ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
+
+			for(Identity identity:identities) {
+				UserCourseEnvironment userCourseEnv = AssessmentHelper.createAndInitUserCourseEnvironment(identity, course);
+				gtaNode.incrementUserAttempts(userCourseEnv);
+			}
+		} else {
+			gtaNode.incrementUserAttempts(assessedUserCourseEnv);
+		}
 	}
 }
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAStepPreferences.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAStepPreferences.java
new file mode 100644
index 0000000000000000000000000000000000000000..27385796a1a2a947d80c1caaba956103bbffc81a
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAStepPreferences.java
@@ -0,0 +1,84 @@
+/**
+ * <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;
+
+/**
+ * 
+ * Initial date: 07.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class GTAStepPreferences {
+	
+	private Boolean assignement;
+	private Boolean submit;
+	private Boolean reviewAndCorrection;
+	private Boolean revision;
+	private Boolean solution;
+	private Boolean grading;
+	
+	public Boolean getAssignement() {
+		return assignement;
+	}
+	
+	public void setAssignement(Boolean assignement) {
+		this.assignement = assignement;
+	}
+	
+	public Boolean getSubmit() {
+		return submit;
+	}
+	
+	public void setSubmit(Boolean submit) {
+		this.submit = submit;
+	}
+	
+	public Boolean getReviewAndCorrection() {
+		return reviewAndCorrection;
+	}
+	
+	public void setReviewAndCorrection(Boolean reviewAndCorrection) {
+		this.reviewAndCorrection = reviewAndCorrection;
+	}
+	
+	public Boolean getRevision() {
+		return revision;
+	}
+	
+	public void setRevision(Boolean revision) {
+		this.revision = revision;
+	}
+	
+	public Boolean getSolution() {
+		return solution;
+	}
+	
+	public void setSolution(Boolean solution) {
+		this.solution = solution;
+	}
+	
+	public Boolean getGrading() {
+		return grading;
+	}
+	
+	public void setGrading(Boolean grading) {
+		this.grading = grading;
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java
index 32bd56624e0b1922ee3365f44ebbbdab85591067..c0c79a17687270728a1855db663c4018381aa9b3 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java
@@ -31,6 +31,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
 import org.olat.core.gui.components.form.flexible.elements.StaticTextElement;
+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.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
@@ -44,6 +45,7 @@ import org.olat.course.condition.AreaSelectionController;
 import org.olat.course.condition.GroupSelectionController;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.nodes.GTACourseNode;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.group.BusinessGroupService;
 import org.olat.group.BusinessGroupShort;
@@ -62,6 +64,11 @@ public class GTAWorkflowEditController extends FormBasicController {
 	
 	private static final String[] keys = new String[]{ "on" };
 	private static final String[] executionKeys = new String[]{ GTAType.group.name(), GTAType.individual.name() };
+	private static final String[] relativeDatesKeys = new String[] {
+		GTARelativeToDates.courseStart.name(), GTARelativeToDates.courseLaunch.name(),
+		GTARelativeToDates.enrollment.name()
+	};
+	private final String[] relativeDatesValues;
 	
 	private CloseableModalController cmc;
 	private AreaSelectionController areaSelectionCtrl;
@@ -71,9 +78,12 @@ public class GTAWorkflowEditController extends FormBasicController {
 	private FormLink chooseGroupButton, chooseAreaButton;
 	private StaticTextElement groupListEl, areaListEl;
 	private DateChooser assignmentDeadlineEl, submissionDeadlineEl, solutionVisibleAfterEl;
-	private MultipleSelectionElement taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl;
-	private FormLayoutContainer stepsCont;
+	private MultipleSelectionElement relativeDatesEl, taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl;
+	private FormLayoutContainer stepsCont, assignmentRelDeadlineCont, submissionRelDeadlineCont, solutionVisibleRelCont;
+	private TextElement assignementDeadlineDaysEl, submissionDeadlineDaysEl, solutionVisibleRelDaysEl;
+	private SingleSelection assignementDeadlineRelToEl, submissionDeadlineRelToEl, solutionVisibleRelToEl;
 	
+	private final GTACourseNode gtaNode;
 	private final ModuleConfiguration config;
 	private final CourseEditorEnv courseEditorEnv;
 	private List<Long> areaKeys;
@@ -84,16 +94,25 @@ public class GTAWorkflowEditController extends FormBasicController {
 	@Autowired
 	private BusinessGroupService businessGroupService;
 	
-	public GTAWorkflowEditController(UserRequest ureq, WindowControl wControl, ModuleConfiguration config, CourseEditorEnv courseEditorEnv) {
+	public GTAWorkflowEditController(UserRequest ureq, WindowControl wControl, GTACourseNode gtaNode, CourseEditorEnv courseEditorEnv) {
 		super(ureq, wControl, LAYOUT_BAREBONE);
-		this.config = config;
+		this.gtaNode = gtaNode;
+		this.config = gtaNode.getModuleConfiguration();
 		this.courseEditorEnv = courseEditorEnv;
 		
+		relativeDatesValues = new String[] {
+			translate("relative.to.course.start"),
+			translate("relative.to.course.launch"),
+			translate("relative.to.enrollment")
+		};
+		
 		initForm(ureq);
 	}
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String type = config.getStringValue(GTACourseNode.GTASK_TYPE);
+		
 		FormLayoutContainer typeCont = FormLayoutContainer.createDefaultFormLayout("type", getTranslator());
 		typeCont.setFormTitle(translate("task.type.title"));
 		typeCont.setFormDescription(translate("task.type.description"));
@@ -104,9 +123,10 @@ public class GTAWorkflowEditController extends FormBasicController {
 				translate("task.execution.group"),
 				translate("task.execution.individual")
 		};
+
 		typeEl = uifactory.addDropdownSingleselect("execution", "task.execution", typeCont, executionKeys, executionValues, null);
 		typeEl.addActionListener(FormEvent.ONCHANGE);
-		String type = config.getStringValue(GTACourseNode.GTASK_TYPE);
+		typeEl.setEnabled(false);
 		if(StringHelper.containsNonWhitespace(type)) {
 			for(String executionKey:executionKeys) {
 				if(executionKey.equals(type)) {
@@ -145,14 +165,35 @@ public class GTAWorkflowEditController extends FormBasicController {
 		areaListEl = uifactory.addStaticTextElement("areas.list", null, areaList, typeCont);
 		areaListEl.setElementCssClass("text-muted");
 		areaListEl.setLabel(null, null);
-		updateTaskType();
 		
+		boolean mismatch = ((GTAType.group.name().equals(type) && !gtaNode.getType().equals(GTACourseNode.TYPE_GROUP))
+				|| (GTAType.individual.name().equals(type) && !gtaNode.getType().equals(GTACourseNode.TYPE_INDIVIDUAL)));
+		
+		if(GTAType.group.name().equals(type)) {
+			typeEl.setVisible(mismatch);
+		} else if(GTAType.individual.name().equals(type)) {
+			if(mismatch) {
+				typeEl.setVisible(true);
+				chooseGroupButton.setVisible(false);
+				groupListEl.setVisible(false);
+				chooseAreaButton.setVisible(false);
+				areaListEl.setVisible(false);
+			} else {
+				typeCont.setVisible(false);
+			}
+		}
+
 		//Steps
 		stepsCont = FormLayoutContainer.createDefaultFormLayout("steps", getTranslator());
 		stepsCont.setFormTitle(translate("task.steps.title"));
 		stepsCont.setFormDescription(translate("task.steps.description"));
 		stepsCont.setRootForm(mainForm);
 		formLayout.add(stepsCont);
+
+		relativeDatesEl = uifactory.addCheckboxesHorizontal("relative.dates", "relative.dates", stepsCont, keys, new String[]{ "" });
+		relativeDatesEl.addActionListener(FormEvent.ONCHANGE);
+		boolean useRelativeDates = config.getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+		relativeDatesEl.select(keys[0], useRelativeDates);
 		
 		//assignment
 		String[] assignmentValues = new String[] { translate("enabled") };
@@ -164,7 +205,37 @@ public class GTAWorkflowEditController extends FormBasicController {
 		Date assignmentDeadline = config.getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
 		assignmentDeadlineEl = uifactory.addDateChooser("assignementdeadline", "assignment.deadline", assignmentDeadline, stepsCont);
 		assignmentDeadlineEl.setDateChooserTimeEnabled(true);
-		assignmentDeadlineEl.setVisible(assignement);
+		assignmentDeadlineEl.setVisible(assignement && !useRelativeDates);
+		
+		String relativeDatePage = velocity_root + "/assignment_relative_date.html";
+		assignmentRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("assignmentRelativeDeadline", getTranslator(), relativeDatePage);
+		assignmentRelDeadlineCont.setRootForm(mainForm);
+		assignmentRelDeadlineCont.setLabel("assignment.deadline", null);
+		assignmentRelDeadlineCont.setVisible(assignement && useRelativeDates);
+		stepsCont.add(assignmentRelDeadlineCont);
+		
+		int numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1);
+		String assignmentNumOfDays = numOfDays >= 0 ? Integer.toString(numOfDays) : "";
+		String assignmentRelativeTo = config.getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+		assignementDeadlineDaysEl = uifactory.addTextElement("assignment.numOfDays", null, 4, assignmentNumOfDays, assignmentRelDeadlineCont);
+		assignementDeadlineDaysEl.setDisplaySize(4);
+		assignementDeadlineDaysEl.setDomReplacementWrapperRequired(false);
+		assignementDeadlineRelToEl = uifactory
+				.addDropdownSingleselect("assignmentrelativeto", "assignment.relative.to", null, assignmentRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null);
+		assignementDeadlineRelToEl.setDomReplacementWrapperRequired(false);
+		
+		boolean found = false;
+		if(StringHelper.containsNonWhitespace(assignmentRelativeTo)) {
+			for(String relativeDatesKey:relativeDatesKeys) {
+				if(relativeDatesKey.equals(assignmentRelativeTo)) {
+					assignementDeadlineRelToEl.select(relativeDatesKey, true);
+					found = true;
+				}
+			}
+		}
+		if(!found) {
+			assignementDeadlineRelToEl.select(relativeDatesKeys[0], true);
+		}
 		
 		//turning in
 		String[] submissionValues = new String[] { translate("submission.enabled") };
@@ -176,7 +247,37 @@ public class GTAWorkflowEditController extends FormBasicController {
 		Date submissionDeadline = config.getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE);
 		submissionDeadlineEl = uifactory.addDateChooser("submitdeadline", "submit.deadline", submissionDeadline, stepsCont);
 		submissionDeadlineEl.setDateChooserTimeEnabled(true);
-		submissionDeadlineEl.setVisible(submit);
+		submissionDeadlineEl.setVisible(submit && !useRelativeDates);
+
+		//relative deadline
+		String submitPage = velocity_root + "/submit_relative_date.html";
+		submissionRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("submitRelativeDeadline", getTranslator(), submitPage);
+		submissionRelDeadlineCont.setRootForm(mainForm);
+		submissionRelDeadlineCont.setLabel("submit.deadline", null);
+		submissionRelDeadlineCont.setVisible(submit && useRelativeDates);
+		stepsCont.add(submissionRelDeadlineCont);
+		
+		numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1);
+		String submitRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : "";
+		String submitRelTo = config.getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+		submissionDeadlineDaysEl = uifactory.addTextElement("submit.numOfDays", null, 4, submitRelDays, submissionRelDeadlineCont);
+		submissionDeadlineDaysEl.setDomReplacementWrapperRequired(false);
+		submissionDeadlineDaysEl.setDisplaySize(4);
+		submissionDeadlineRelToEl = uifactory
+				.addDropdownSingleselect("submitrelativeto", "submit.relative.to", null, submissionRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null);
+		submissionDeadlineRelToEl.setDomReplacementWrapperRequired(false);
+		found = false;
+		if(StringHelper.containsNonWhitespace(submitRelTo)) {
+			for(String relativeDatesKey:relativeDatesKeys) {
+				if(relativeDatesKey.equals(submitRelTo)) {
+					submissionDeadlineRelToEl.select(relativeDatesKey, true);
+					found = true;
+				}
+			}
+		}
+		if(!found) {
+			submissionDeadlineRelToEl.select(relativeDatesKeys[0], true);
+		}
 		
 		//review and correction
 		String[] reviewValues = new String[] { translate("review.enabled") };
@@ -203,6 +304,36 @@ public class GTAWorkflowEditController extends FormBasicController {
 		solutionVisibleAfterEl = uifactory.addDateChooser("visibleafter", "sample.solution.visible.after", solutionVisibleAfter, stepsCont);
 		solutionVisibleAfterEl.setDateChooserTimeEnabled(true);
 		solutionVisibleAfterEl.setVisible(sample);
+
+		//relative deadline
+		String solutionPage = velocity_root + "/solution_relative_date.html";
+		solutionVisibleRelCont = FormLayoutContainer.createCustomFormLayout("solutionRelativeDeadline", getTranslator(), solutionPage);
+		solutionVisibleRelCont.setRootForm(mainForm);
+		solutionVisibleRelCont.setLabel("sample.solution.visible.after", null);
+		solutionVisibleRelCont.setVisible(submit && useRelativeDates);
+		stepsCont.add(solutionVisibleRelCont);
+		
+		numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1);
+		String solutionRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : "";
+		String solutionRelTo = config.getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO);
+		solutionVisibleRelDaysEl = uifactory.addTextElement("solution.numOfDays", null, 4, solutionRelDays, solutionVisibleRelCont);
+		solutionVisibleRelDaysEl.setDisplaySize(4);
+		solutionVisibleRelDaysEl.setDomReplacementWrapperRequired(false);
+		solutionVisibleRelToEl = uifactory
+				.addDropdownSingleselect("solutionrelativeto", "solution.relative.to", null, solutionVisibleRelCont, relativeDatesKeys, relativeDatesValues, null);
+		solutionVisibleRelToEl.setDomReplacementWrapperRequired(false);
+		found = false;
+		if(StringHelper.containsNonWhitespace(solutionRelTo)) {
+			for(String relativeDatesKey:relativeDatesKeys) {
+				if(relativeDatesKey.equals(solutionRelTo)) {
+					solutionVisibleRelToEl.select(relativeDatesKey, true);
+					found = true;
+				}
+			}
+		}
+		if(!found) {
+			solutionVisibleRelToEl.select(relativeDatesKeys[0], true);
+		}
 		
 		//grading
 		String[] gradingValues = new String[] { translate("enabled") };
@@ -218,14 +349,6 @@ public class GTAWorkflowEditController extends FormBasicController {
 		uifactory.addFormSubmitButton("save", "save", buttonCont);
 	}
 	
-	private void updateTaskType() {
-		boolean groupOption = typeEl.isSelected(0);
-		chooseGroupButton.setVisible(groupOption);
-		groupListEl.setVisible(groupOption);
-		chooseAreaButton.setVisible(groupOption);
-		areaListEl.setVisible(groupOption);
-	}
-	
 	@Override
 	protected void doDispose() {
 		//
@@ -249,6 +372,22 @@ public class GTAWorkflowEditController extends FormBasicController {
 			}
 		}
 		
+		boolean relativeDates = relativeDatesEl.isAtLeastSelected(1);
+		assignementDeadlineDaysEl.clearError();
+		if(relativeDates && taskAssignmentEl.isAtLeastSelected(1)) {
+			allOk &= validateIntegerOrEmpty(assignementDeadlineDaysEl);
+		}
+		
+		submissionDeadlineDaysEl.clearError();
+		if(relativeDates && submissionEl.isAtLeastSelected(1)) {
+			allOk &= validateIntegerOrEmpty(submissionDeadlineDaysEl);
+		}
+		
+		solutionVisibleRelDaysEl.clearError();
+		if(relativeDates && sampleEl.isAtLeastSelected(1)) {
+			allOk &= validateIntegerOrEmpty(solutionVisibleRelDaysEl);
+		}
+		
 		taskAssignmentEl.clearError();
 		if(!taskAssignmentEl.isAtLeastSelected(1) && !submissionEl.isAtLeastSelected(1)
 				&& !reviewEl.isAtLeastSelected(1) && !revisionEl.isAtLeastSelected(1)
@@ -260,6 +399,21 @@ public class GTAWorkflowEditController extends FormBasicController {
 
 		return allOk & super.validateFormLogic(ureq);
 	}
+	
+	private boolean validateIntegerOrEmpty(TextElement textEl) {
+		boolean allOk = true;
+		textEl.clearError();
+		String val = textEl.getValue();
+		if(StringHelper.containsNonWhitespace(val)) {
+			if(StringHelper.isLong(val)) {
+				
+			} else {
+				textEl.setErrorKey("integer.element.int.error", null);
+				allOk &= false;
+			}
+		}
+		return allOk;
+	}
 
 	@Override
 	protected void formOK(UserRequest ureq) {
@@ -273,18 +427,31 @@ public class GTAWorkflowEditController extends FormBasicController {
 			config.setList(GTACourseNode.GTASK_GROUPS, new ArrayList<Long>(0));
 		}
 		
+		boolean relativeDates = relativeDatesEl.isAtLeastSelected(1);
+		config.setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, relativeDates);
+		
 		boolean assignment = taskAssignmentEl.isAtLeastSelected(1);
 		config.setBooleanEntry(GTACourseNode.GTASK_ASSIGNMENT, assignment);
-		if(assignment && assignmentDeadlineEl.getDate() != null) {
-			config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate());
+		if(assignment) {
+			if(relativeDates) {
+				setRelativeDates(assignementDeadlineDaysEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE,
+						assignementDeadlineRelToEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+			} else if(assignmentDeadlineEl.getDate() != null) {
+				config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate());
+			}
 		} else {
 			config.remove(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
 		}
 		
 		boolean turningIn = submissionEl.isAtLeastSelected(1);
 		config.setBooleanEntry(GTACourseNode.GTASK_SUBMIT, turningIn);
-		if(turningIn && submissionDeadlineEl.getDate() != null) {
-			config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate());
+		if(turningIn) {
+			if(relativeDates) {
+				setRelativeDates(submissionDeadlineDaysEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE,
+						submissionDeadlineRelToEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+			} else {
+				config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate());
+			}
 		} else {
 			config.remove(GTACourseNode.GTASK_SUBMIT_DEADLINE);
 		}
@@ -294,27 +461,47 @@ public class GTAWorkflowEditController extends FormBasicController {
 		
 		boolean sample = sampleEl.isAtLeastSelected(1);
 		config.setBooleanEntry(GTACourseNode.GTASK_SAMPLE_SOLUTION, sample);
-		if(sample && solutionVisibleAfterEl.getDate() != null) {
-			config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate());
+		if(sample) {
+			if(relativeDates) {
+				setRelativeDates(solutionVisibleRelDaysEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE,
+						solutionVisibleRelToEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO);
+			} else if(solutionVisibleAfterEl.getDate() != null) {
+				config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate());
+			}
 		} else {
 			config.remove(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
 		}
 
 		config.setBooleanEntry(GTACourseNode.GTASK_GRADING, gradingEl.isAtLeastSelected(1));
-		
 		fireEvent(ureq, Event.DONE_EVENT);
 	}
+	
+	private void setRelativeDates(TextElement daysEl, String daysKey, SingleSelection relativeToEl, String relativeToKey) {
+		String val = daysEl.getValue();
+		if(StringHelper.isLong(val)) {
+			try {
+				config.setIntValue(daysKey,  Integer.parseInt(val));
+			} catch (NumberFormatException e) {
+				logWarn("", e);
+			}
+			
+			String relativeTo = relativeToEl.getSelectedKey();
+			config.setStringValue(relativeToKey, relativeTo);
+		}
+	}
 
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
-		if(typeEl == source) {
-			updateTaskType();
-		} else if(submissionEl == source) {
-			submissionDeadlineEl.setVisible(submissionEl.isAtLeastSelected(1));
+		if(submissionEl == source) {
+			updateSubmissionDeadline();
 		} else if(taskAssignmentEl == source) {
-			assignmentDeadlineEl.setVisible(taskAssignmentEl.isAtLeastSelected(1));
+			updateAssignmentDeadline();
 		} else if(sampleEl == source) {
-			solutionVisibleAfterEl.setVisible(sampleEl.isAtLeastSelected(1));
+			updateSolutionDeadline();
+		} else if(relativeDatesEl == source) {
+			updateAssignmentDeadline();
+			updateSubmissionDeadline();
+			updateSolutionDeadline();
 		} else if(chooseGroupButton == source) {
 			doChooseGroup(ureq);
 		} else if(chooseAreaButton == source) {
@@ -324,10 +511,31 @@ public class GTAWorkflowEditController extends FormBasicController {
 		super.formInnerEvent(ureq, source, event);
 	}
 	
+	private void updateAssignmentDeadline() {
+		boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1);
+		boolean assignment = taskAssignmentEl.isAtLeastSelected(1);
+		assignmentDeadlineEl.setVisible(assignment && !userRelativeDate);
+		assignmentRelDeadlineCont.setVisible(assignment && userRelativeDate);
+	}
+	
+	private void updateSubmissionDeadline() {
+		boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1);
+		boolean submit = submissionEl.isAtLeastSelected(1);
+		submissionDeadlineEl.setVisible(submit && !userRelativeDate);
+		submissionRelDeadlineCont.setVisible(submit && userRelativeDate);
+	}
+	
+	private void updateSolutionDeadline() {
+		boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1);
+		boolean solution = sampleEl.isAtLeastSelected(1);
+		solutionVisibleAfterEl.setVisible(solution && !userRelativeDate);
+		solutionVisibleRelCont.setVisible(solution && userRelativeDate);
+	}
+	
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
 		if(groupSelectionCtrl == source) {
-			if (event == Event.DONE_EVENT) {
+			if (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
 				groupKeys = groupSelectionCtrl.getSelectedKeys();
 				groupListEl.setValue(getGroupNames(groupKeys));
 				if(courseEditorEnv.getCourseGroupManager().hasBusinessGroups()) {
@@ -339,7 +547,7 @@ public class GTAWorkflowEditController extends FormBasicController {
 			cmc.deactivate();
 			cleanUp();
 		} else if(areaSelectionCtrl == source) {
-			if (event == Event.DONE_EVENT) {
+			if (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
 				areaKeys = areaSelectionCtrl.getSelectedKeys();
 				areaListEl.setValue(getAreaNames(areaKeys));
 				if(courseEditorEnv.getCourseGroupManager().hasAreas()) {
@@ -358,9 +566,10 @@ public class GTAWorkflowEditController extends FormBasicController {
 
 	private void cleanUp() {
 		removeAsListenerAndDispose(groupSelectionCtrl);
+		removeAsListenerAndDispose(areaSelectionCtrl);
 		removeAsListenerAndDispose(cmc);
-		
 		groupSelectionCtrl = null;
+		areaSelectionCtrl = null;
 		cmc = null;
 	}
 	
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java
index 6acf87cee8689f1cbde2d0061c7ba9cb2722b69d..626e6e0bad6ecd6905b5a492b1f7c3567fc6b412 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java
@@ -120,7 +120,7 @@ class SubmitDocumentsController extends FormBasicController {
 		columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel("edit", DocCols.edit.ordinal(), "edit",
 				new BooleanCellRenderer(
 						new StaticFlexiCellRenderer(translate("edit"), "edit"),
-						new StaticFlexiCellRenderer(translate("replace"), "replace"))));
+						new StaticFlexiCellRenderer(translate("replace"), "edit"))));
 		columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel("delete", translate("delete"), "delete"));
 		
 		model = new DocumentTableModel(columnsModel);
@@ -139,7 +139,7 @@ class SubmitDocumentsController extends FormBasicController {
 		model.setObjects(docList);
 		tableEl.reset();
 		
-		if(maxDocs >0 && docList.size() >= maxDocs) {
+		if(maxDocs > 0 && docList.size() >= maxDocs) {
 			if(uploadDocButton != null) {
 				uploadDocButton.setEnabled(false);
 			}
@@ -205,9 +205,11 @@ class SubmitDocumentsController extends FormBasicController {
 	}
 	
 	private void cleanUp() {
+		removeAsListenerAndDispose(newDocumentEditorCtrl);
 		removeAsListenerAndDispose(confirmDeleteCtrl);
 		removeAsListenerAndDispose(uploadCtrl);
 		removeAsListenerAndDispose(cmc);
+		newDocumentEditorCtrl = null;
 		confirmDeleteCtrl = null;
 		uploadCtrl = null;
 		cmc = null;
@@ -230,10 +232,8 @@ class SubmitDocumentsController extends FormBasicController {
 				SubmittedSolution row = model.getObject(se.getIndex());
 				if("delete".equals(se.getCommand())) {
 					doConfirmDelete(ureq, row);
-				} else if("replace".equals(se.getCommand())) {
-					doReplaceDocument(ureq, row);
 				} else if("edit".equals(se.getCommand())) {
-					doEditDocumentEditor(ureq, row);
+					doEdit(ureq, row);
 				}
 			}
 		}
@@ -255,8 +255,16 @@ class SubmitDocumentsController extends FormBasicController {
 		updateModel();
 	}
 	
+	private void doEdit(UserRequest ureq, SubmittedSolution row) {
+		if(row.getFile().getName().endsWith(".html")) {
+			doEditDocumentEditor(ureq, row);
+		} else {
+			doReplaceDocument(ureq, row);
+		}
+	}
+	
 	private void doReplaceDocument(UserRequest ureq, SubmittedSolution row) {
-		replaceCtrl = new DocumentUploadController(ureq, getWindowControl());
+		replaceCtrl = new DocumentUploadController(ureq, getWindowControl(), row, row.getFile());
 		listenTo(replaceCtrl);
 
 		String title = translate("replace.document");
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html
new file mode 100644
index 0000000000000000000000000000000000000000..f1b4c29ad6f7ab910720bcb188a19b4619154d1c
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html
@@ -0,0 +1,6 @@
+<div class="form-inline">$r.render("assignment.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("assignment.relative.to")</div>
+#if($f.hasError("assignment.numOfDays"))
+	<div class="form-inline">
+		$r.render("assignment.numOfDays_ERROR")
+	</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html
index d6b772afede557bbfb4d30c1916ccd22b9b04202..2ac7eee5de15f955dd6f106130b5fe470a54ccaa 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html
@@ -10,11 +10,11 @@ $r.render("contextualSubscription")
 #if($assignmentEnabled)
 	<div class="o_step $assignmentCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.assignment.title")</h4>
+		<h4 class="o_title"> <a href="#o_step_assignement_content" data-toggle="collapse" aria-expanded="$collapse_assignement">$r.translate("run.assignment.title")</a></h4>
 		#if($assignmentDueDate)
 			<div class="o_meta">$r.translate("run.assignment.due.date", $assignmentDueDate)</div>
 		#end
-		<div class="o_content">
+		<div id="o_step_assignement_content" class="o_content collapse #if($collapse_assignement) in #end" aria-expanded="$collapse_assignement">
 		#if($r.available("assignedTask"))
 			$r.render("assignedTask")
 		#else
@@ -22,16 +22,23 @@ $r.render("contextualSubscription")
 		#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_assignement_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=assignment');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=assignment');
+		})
+	/* ]]> */</script>
 #end
 
 #if($submitEnabled)
 	<div class="o_step $submitCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.submit")</h4>
+		<h4 class="o_title"> <a href="#o_step_submit_content" data-toggle="collapse" aria-expanded="$collapse_submit">$r.translate("run.submit")</a></h4>
 		#if($submitDueDate)
 			<div class="o_meta">$r.translate("run.submit.due.date", $submitDueDate)</div>
 		#end
-		<div class="o_content">
+		<div id="o_step_submit_content" class="o_content collapse #if($collapse_submit) in #end" aria-expanded="$collapse_submit">
 		#if($r.available("submittedDocs"))
 			$r.render("submittedDocs")
 		#else
@@ -39,13 +46,20 @@ $r.render("contextualSubscription")
 		#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_submit_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=submit');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=submit');
+		})
+	/* ]]> */</script>
 #end
 
 #if($reviewAndCorrectionEnabled)
 	<div class="o_step $reviewCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.review")</h4>
-		<div class="o_content">
+		<h4 class="o_title"> <a href="#o_step_review_content" data-toggle="collapse" aria-expanded="$collapse_reviewAndCorrection">$r.translate("run.review")</a></h4>
+		<div id="o_step_review_content" class="o_content collapse #if($collapse_reviewAndCorrection) in #end" aria-expanded="$collapse_reviewAndCorrection">
 		#if($r.available("corrections"))
 			$r.render("corrections")
 			#if($r.available("coach.reviewed.button") || $r.available("coach.need.revision.button"))
@@ -61,43 +75,65 @@ $r.render("contextualSubscription")
 		#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_review_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=reviewAndCorrection');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=reviewAndCorrection');
+		})
+	/* ]]> */</script>
 #end
 
 #if($revisionEnabled)
 	<div class="o_step $revisionCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.revision")</h4>
-		<div class="o_content">
+		<h4 class="o_title"> <a href="#o_step_revision_content" data-toggle="collapse" aria-expanded="$collapse_revision">$r.translate("run.revision")</a></h4>
+		<div id="o_step_revision_content" class="o_content collapse #if($collapse_revision) in #end" aria-expanded="$collapse_revision">
 		#if($r.available("revisionDocs"))
 			$r.render("revisionDocs")
 		#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_revision_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=revision');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=revision');
+		})
+	/* ]]> */</script>
 #end
 
 #if($solutionEnabled)
 	<div class="o_step $solutionCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.solution")</h4>
+		<h4 class="o_title"> <a href="#o_step_solution_content" data-toggle="collapse" aria-expanded="$collapse_solution">$r.translate("run.solution")</a></h4>
 		#if($solutionAvailableDate)
 			<div class="o_meta">$r.translate("run.solution.available.date", $solutionAvailableDate)</div>
 		#end
-		<div class="o_content">
+		<div id="o_step_solution_content" class="o_content collapse #if($collapse_solution) in #end" aria-expanded="$collapse_solution">
 		#if($r.available("solutions"))
 			$r.render("solutions")
 		#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_solution_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=solution');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=solution');
+		})
+	/* ]]> */</script>
 #end
 	
 #if($gradingEnabled)
 	<div class="o_step $gradingCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.grading")</h4>
-		<div class="o_content">
+		<h4 class="o_title"> <a href="#o_step_grading_content" data-toggle="collapse" aria-expanded="$collapse_grading">$r.translate("run.grading")</a></h4>
+		<div id="o_step_grading_content" class="o_content collpase #if($collapse_grading) in #end" aria-expanded="$collapse_grading">
 		#if($r.available("grading"))
 			$r.render("grading")
 		#end
+		</div>
 		#if($userLog || $groupLog)
 		<div class="o_box">
 		    #o_togglebox_start("o_course_run_log" $r.translate("log.title"))
@@ -110,8 +146,14 @@ $r.render("contextualSubscription")
 		    #o_togglebox_end()
 		</div>
 		#end
-		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_grading_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=grading');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=grading');
+		})
+	/* ]]> */</script>
 #end
 
 </div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html
index c1b4893114003d231782f78f7744e34184349da6..886bc73dd8ef0dd6146ed86ce21a92e7776efefb 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html
@@ -1,5 +1,6 @@
-$r.render("contextualSubscription")
-<div class="o_block">[ugly]</div>
+<div class="clearfix">
+	$r.render("contextualSubscription")
+</div>
 
 #if($noGroupError)
 <div class="o_error" role="alert">$r.translate("error.no.group")</div>
@@ -18,16 +19,19 @@ $r.render("contextualSubscription")
 	#end
 </div>
 #end
+<div class="o_block">
+$collapse_assignement $collapse_submit $collapse_reviewAndCorrection $collapse_revision $collapse_solution $collapse_grading
+</div>
 
 <div class="o_process">
 #if($assignmentEnabled)
-	<div class="o_step $assignmentCssClass">
+	<div id="o_step_assignement" class="o_step $assignmentCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.assignment.title")</h4>
+		<h4 class="o_title" > <a href="#o_step_assignement_content" data-toggle="collapse" aria-expanded="$collapse_assignement">$r.translate("run.assignment.title")</a></h4>
 		#if($assignmentDueDate)
 			<div class="o_meta">$r.translate("run.assignment.due.date", $assignmentDueDate)</div>
 		#end
-		<div class="o_content">
+		<div id="o_step_assignement_content" class="o_content collapse #if($collapse_assignement) in #end" aria-expanded="$collapse_assignement">
 			#if($assignmentClosed)
 				<div class="o_error">$r.translate("error.assignment.closed")</div>
 			#end
@@ -41,16 +45,23 @@ $r.render("contextualSubscription")
 			#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_assignement_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=assignment');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=assignment');
+		})
+	/* ]]> */</script>
 #end
 
 #if($submitEnabled)
 	<div class="o_step $submitCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.submit")</h4>
+		<h4 class="o_title"> <a href="#o_step_submit_content" data-toggle="collapse" aria-expanded="$collapse_submit">$r.translate("run.submit")</a></h4>
 		#if($submitDueDate)
 			<div class="o_meta">$r.translate("run.submit.due.date", $submitDueDate)</div>
 		#end
-		<div class="o_content">
+		<div id="o_step_submit_content" class="o_content collapse #if($collapse_submit) in #end" aria-expanded="$collapse_submit">
 			#if($r.available("submitDocs"))
 				<p>$r.translate("run.submit.individual.description.${sumbitWay}")</p>
 				#if($groupWarning)
@@ -66,13 +77,20 @@ $r.render("contextualSubscription")
 			#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_submit_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=submit');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=submit');
+		})
+	/* ]]> */</script>
 #end
 
 #if($reviewAndCorrectionEnabled)
 	<div class="o_step $reviewCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.review")</h4>
-		<div class="o_content">
+		<h4 class="o_title"> <a href="#o_step_review_content" data-toggle="collapse" aria-expanded="$collapse_reviewAndCorrection">$r.translate("run.review")</a></h4>
+		<div id="o_step_review_content" class="o_content collapse #if($collapse_reviewAndCorrection) in #end" aria-expanded="$collapse_reviewAndCorrection">
 			#if($reviewMessage && !$reviewMessage.isEmpty())
 				<p>$r.escapeHtml($reviewMessage)</p>
 			#end
@@ -81,45 +99,88 @@ $r.render("contextualSubscription")
 			#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_review_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=reviewAndCorrection');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=reviewAndCorrection');
+		})
+	/* ]]> */</script>
 #end
 
 #if($revisionEnabled)
 	<div class="o_step $revisionCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.revision")</h4>
-		<div class="o_content">
+		<h4 class="o_title"> <a href="#o_step_revision_content" data-toggle="collapse" aria-expanded="$collapse_revision">$r.translate("run.revision")</a></h4>
+		<div id="o_step_revision_content" class="o_content collapse #if($collapse_revision) in #end" aria-expanded="$collapse_revision">
 			#if($r.available("revisionDocs"))
 				$r.render("revisionDocs")
 			#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_revision_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=revision');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=revision');
+		})
+	/* ]]> */</script>
 #end
 
 #if($solutionEnabled)
 	<div class="o_step $solutionCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.solution")</h4>
+		<h4 class="o_title"> <a href="#o_step_solution_content" data-toggle="collapse" aria-expanded="$collapse_solution">$r.translate("run.solution")</a></h4>
 		#if($solutionAvailableDate)
 			<div class="o_meta">$r.translate("run.solution.available.date", $solutionAvailableDate)</div>
 		#end
-		<div class="o_content">
+		<div id="o_step_solution_content" class="o_content collapse #if($collapse_solution) in #end" aria-expanded="$collapse_solution">
 			#if($r.available("solutions"))
 				$r.render("solutions")
 			#end
 		</div>
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_solution_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=solution');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=solution');
+		})
+	/* ]]> */</script>
 #end
 	
 #if($gradingEnabled)
 	<div class="o_step $gradingCssClass">
 		<div class="o_bar"></div>
-		<h4 class="o_title">$r.translate("run.grading")</h4>
-		<div class="o_content">
+		<h4 class="o_title"> <a href="#o_step_grading_content" data-toggle="collapse" aria-expanded="$collapse_grading">$r.translate("run.grading")</a></h4>
+		<div id="o_step_grading_content" class="o_content collpase #if($collapse_grading) in #end" aria-expanded="$collapse_grading">
+			#if($gradingInfoTextUser && !$gradingInfoTextUser.isEmpty())
+			<div class="panel panel-default o_disclaimer">
+			  	<div class="panel-heading" data-toggle="collapse" data-target="#collapseDisclaimer">
+			  		<h4 class="panel-title">
+			  			<i id="collapseDisclaimerToggler" class="o_icon o_icon-fw o_icon_close_togglebox"> </i>
+			  			$r.translate("info.title")
+			  		</h4>
+			  	</div>
+				<div id="collapseDisclaimer" class="panel-collapse collapse in"><div class="panel-body">
+					$r.formatLatexFormulas($gradingInfoTextUser)
+		  		</div></div>
+			</div>
+			<script type="text/javascript">
+				/* <![CDATA[ */
+					jQuery('#collapseDisclaimer').on('hide.bs.collapse', function () {
+						jQuery('#collapseDisclaimerToggler').removeClass('o_icon_close_togglebox').addClass('o_icon_open_togglebox');
+					})
+					jQuery('#collapseDisclaimer').on('show.bs.collapse', function () {
+						jQuery('#collapseDisclaimerToggler').removeClass('o_icon_open_togglebox').addClass('o_icon_close_togglebox');
+					})
+				/* ]]> */
+			</script>
+			#end
 			#if($r.available("grading"))
 				$r.render("grading")
 			#end
 		</div>
-		
 		#if($userLog || $groupLog)
 		<div class="o_box">
 		    #o_togglebox_start("o_course_run_log" $r.translate("log.title"))
@@ -133,5 +194,12 @@ $r.render("contextualSubscription")
 		</div>
 		#end
 	</div>
+	<script type='text/javascript'>/* <![CDATA[ */
+		jQuery('#o_step_grading_content').on('hide.bs.collapse', function () {
+	  		o_ffXHRNFEvent('$r.commandURIbg("hide")?step=grading');
+		}).on('show.bs.collapse', function () {
+			o_ffXHRNFEvent('$r.commandURIbg("show")?step=grading');
+		})
+	/* ]]> */</script>
 #end
 </div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html
new file mode 100644
index 0000000000000000000000000000000000000000..0e6f0ac5a33db349489cc823976f1827e10d1d3a
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html
@@ -0,0 +1,6 @@
+<div class="form-inline">$r.render("solution.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("solution.relative.to")</div>
+#if($f.hasError("solution.numOfDays"))
+	<div class="form-inline">
+		$r.render("solution.numOfDays_ERROR")
+	</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html
new file mode 100644
index 0000000000000000000000000000000000000000..e190568d69f4ef1a172ff311f52e92cdf04213c5
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html
@@ -0,0 +1,6 @@
+<div class="form-inline">$r.render("submit.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("submit.relative.to")</div>
+#if($f.hasError("submit.numOfDays"))
+	<div class="form-inline">
+		$r.render("submit.numOfDays_ERROR")
+	</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
index b4020e714f626fbefa6a050e2af43bbc9f96f6e8..baf1a7fb5d8d634f327274ee315673d50c6b2ba3 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
@@ -1,10 +1,11 @@
 #Tue Mar 31 14:20:51 CEST 2015
-add.solution=Musterl\u00F6sungen hochladen
+add.solution=Musterl\u00F6sung hochladen
 add.task=Aufgabe hinzuf\u00FCgen
 assessment.group.tool=Gruppe bewerten
 assignment.config.title=Aufgabe zuweisen
 assignment.deadline=Zuweisungstermin
 before=Vor
+bulk.download.title=Alle abgegebene Dokumenten herunterladen
 bulk.review=Beurteilung herunterladen
 bulk.solutions=Musterl\u00F6sungen herunterladen
 bulk.submitted.documents=Abgegebene Dokumente
@@ -18,7 +19,7 @@ coach.assessment=Bewerten
 coach.close.revision.button=Revisionsprozess schliessen
 coach.corrections.description=Sie haben folgende Korrekturen zur\u00FCckgegeben.
 coach.documents.successfully.reviewed=Begutachtet\!
-coach.need.revision.button=Ben\u00F6tigt \u00FCberarbeitung
+coach.need.revision.button=Ben\u00F6tigt \u00DCberarbeitung
 coach.reviewed.button=Begutachtet
 coach.revisions.description=Sie haben die folgenden Dokumente \u00FCberarbeitet.
 coach.submit.corrections.to.revision.button=Korrekturen absenden
@@ -32,6 +33,7 @@ confirm.delete.solution.title=L\u00F6sung l\u00F6schen
 confirmation.title=Abgabe Best\u00E4tigung
 create.areas=Lernbereich erstellen
 create.groups=Gruppe erstellen
+days.after=Tage nach
 document=Abgegebene Dokumente
 document.date=Datum
 download.task=Aufgabe herunterladen
@@ -42,11 +44,11 @@ embedded.editor=Abgabe mit externem Editor (PDF)
 enabled=eingeschaltet
 error.assignment.closed=Zuweisung ist geschlossen.
 error.duplicate.coaching=Der Teilnehmer ist Teilnehmer von mehreren Gruppen f\u00FCr diese Aufgabe.
-error.duplicate.memberships=Die folgende Teilnehmer sind Mitglieder von mehereren Gruppen\: {1}
+error.duplicate.memberships=Die folgenden Teilnehmer sind Mitglieder von mehreren Gruppen\: {1}
 error.editor.atLeastOne=Sie m\u00FCssen mindestens einen Editortyp w\u00E4hlen.
 error.max.documents=Sie d\u00FCrfen nicht mehr als <b>{0}</b> Dokument(e) abgeben. Aber sie k\u00F6nnen noch ein Dokument austauschen oder editieren.
 error.missing.group=Sie haben noch keine Gruppe gew\u00E4hlt
-error.missing.score.config=Fehlende Punkte konfiguration
+error.missing.score.config=Fehlende Bewertungskonfiguration
 error.missing.solutions=Sie haben noch keine L\u00F6sungen hochgeladen
 error.missing.tasks=Sie haben noch keine Aufgabe erstellt
 error.no.group=Sie sind in keiner Gruppe
@@ -61,6 +63,7 @@ group.apply.toall=F\u00FCr die ganze Gruppe
 group.passed=Gruppe bestanden/nicht bestanden
 group.score=Gruppe Punkte
 group.title=Leistungs\u00FCbersicht
+info.title=$org.olat.course.nodes.ms\:info.title
 log.title=\u00C4nderungsverlauf
 mail.confirm.assignment.body=Zuweisung war erfolgreich
 mail.confirm.assignment.subject=Zuweisung
@@ -83,6 +86,10 @@ pane.tab.workflow=Workflow
 preview=$org.olat.course.nodes.ta\:form.task.preview
 preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview
 preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview
+relative.dates=Relative Datum
+relative.to.course.start=Kurs Beginn
+relative.to.course.launch=Kurs erstes Besuch
+relative.to.enrollment=Enrollment
 replace=Austauschen
 replace.document=Dokument austauschen
 review.and.correction=\u00DCberarbeitung und Korrektur
@@ -95,26 +102,28 @@ run.coach=Korrigieren
 run.corrections.description=Ihr Betreuer hat folgende Dokumente f\u00FCr Sie hinzugef\u00FCgt\:
 run.documents.successfully.submitted=Ihr(e) Dokument(e) wurden erfolgreich eingereicht.
 run.grading=Bewertung
-run.pick.task.description=Bitte, w\u00E4hlen Sie eine Aufgabe aus.
+run.pick.task.description=Bitte w\u00E4hlen Sie eine Aufgabe aus.
 run.review=Begutachtung und Korrektur
 run.review.closed.without.documents=Ihr Betreuer hat die eingereichten Dokumente begutachtet.
 run.review.description=Ihr Betreuer wird nun die eingereichten Dokumente begutachten. Sie werden benachrichtigt sobald eine begutachtete oder korrigierte Version verf\u00FCgbar ist.
 run.review.waiting=Ihr Betreuer begutachtet nun die eingereichten Dokumente. Sie werden benachrichtigt sobald eine begutachtete oder korrigierte Version verf\u00FCgbar ist.
 run.revised.description=Die folgenden \u00FCberarbeiteten Dokumente wurden von Ihnen eingereicht\:
 run.revision=\u00DCberarbeitungsperiode
-run.revision.period.description=Your coach added a revision period. Create or upload a revised document.
+run.revision.period.description=Ihr Betreuer hat eine Revisionsperiode hinzugef\u00FCgt. Erstellen Sie ein \u00FCberarbeitetes Dokument oder laden Sie eines hoch.
 run.run=Aufgabe
 run.solution=L\u00F6sungen
 run.solution.available.date={0}
-run.solutions.description=Hier finden Sie Musterl\u00F6sungen, die Ihr Betreuer Ihnen zur Verf\u00FCgung stellt.
+run.solutions.description=Hier finden Sie die von Ihrem Betreuer zur Verf\u00FCgung gestellten Musterl\u00F6sungen.
 run.submit=Abgabe
-run.submit.button=Abgeben
+run.submit.button=Endg\u00FCltige Abgabe
+run.submit.confirm=Das Abgabe von dem Aufgabe ist endg\u00FCltig.
+run.submit.confirm.group=$\:run.submit.confirm<br/>Dies ist eine Gruppenaufgabe\! Die hier getroffene Auswahl ist f\u00FCr alle Mitglieder der Gruppe "{0}" g\u00FCltig\!
 run.submit.due.date=Abgabe Termin\: {0}
-run.submit.individual.description.all=Turn in your solution by either uploading a document you created on your computer or use the editor to write your solution right here.
-run.submit.individual.description.editor=Turn in your solution by using the editor to write your solution right here.
-run.submit.individual.description.upload=Turn in your solution by uploading a document you created on your computer.
-run.submit.revision.button=Revision abgeben
-run.submitted.description=The following solutions have been turned in by you\:
+run.submit.individual.description.all=Geben Sie Ihre L\u00F6sung ab indem Sie entweder ein Dokument hochladen oder den hier verf\u00FCgbaren Editor benutzen.
+run.submit.individual.description.editor=Schreiben Sie Ihre L\u00F6sung direkt im Editor.
+run.submit.individual.description.upload=Laden Sie Ihre L\u00F6sung als fertiges Dokument hoch.
+run.submit.revision.button=\u00DCberarbeitung abgeben
+run.submitted.description=Die folgenden L\u00F6sungen wurden von Ihnen abgegeben\:
 sample.solution=Musterl\u00F6sungen
 sample.solution.enabled=eingeschaltet
 sample.solution.visible.after=Sichtbar nach
@@ -123,24 +132,24 @@ sampling.reuse=Aufgabe wird mehreren Benutzern / Gruppe zugewiesen
 sampling.unique=Aufgabe wird genau einem Benutzer / Gruppe zugewiesen
 selected.group=Die Gruppe f\u00FCr diese Aufgabe ist\: <i class\="o_icon o_icon_group"> </i> "{0}"
 solution.file=Datei
-solution.list.description=Select "Add solution" to add a solution or "Edit" to modify an existing solution. Note that solutions are not assigned to a particular task.
+solution.list.description=W\u00E4hlen Sie "Musterl\u00F6sung hochladen" um eine Musterl\u00F6sung hinzuzuf\u00FCgen, oder "Editieren" um eine bestehende Musterl\u00F6sung zu bearbeiten. Bitte beachten Sie dass Musterl\u00F6sungen keiner spezifischen Aufgabe zugewiesen werden.
 solution.list.title=Musterl\u00F6sungen hochladen
 solution.title=Titel
 submission=Abgabe
 submission.confirmation=Hiermit wird best\u00E4tigt, dass $first $last ($email) die Datei "$filename" am $date um $time hochgeladen hat.
-submission.email.confirmation=Send text additionally as email
+submission.email.confirmation=Diesen Text zus\u00E4tzlich als E-Mail versenden.
 submission.enabled=eingeschaltet
 submission.mail.subject=OpenOLAT-Best\u00E4tigungs-E-Mail
-submission.text=Text after handling in
+submission.text=Text nach erfolgter Abgabe
 submit.deadline=Abgabetermin
 table.header.details.gta=$org.olat.course.nodes.ta\:table.header.details.ta
 table.header.group.name=Gruppe
-table.header.passed=Passed
+table.header.passed=Bestanden
 table.header.score=Punkte
 task.alreadyChosen=$org.olat.course.nodes.ta\:task.chosen
-task.assigned.description=The following task has been assigned to you\:
+task.assigned.description=Die folgende Aufgabe wurde Ihnen zugewiesen\:
 task.assignment=Zuweisung
-task.assignment.error=Ein unerwartete Fehler ist aufgetreten\!
+task.assignment.error=Ein unerwarteter Fehler ist aufgetreten\!
 task.assignment.type=$org.olat.course.nodes.ta\:form.task.type
 task.assignment.type.auto=$org.olat.course.nodes.ta\:form.task.type.auto
 task.assignment.type.manual=$org.olat.course.nodes.ta\:form.task.type.manual
@@ -151,14 +160,14 @@ task.execution.individual=Individuell
 task.file=Datei
 task.list.description=Ausw\u00E4hlen...
 task.list.title=Aufgaben
-task.steps.description=W\u00E4hlen Sie welche Elemente in der Aufgabe Workflow aktiviert sind und optional F\u00E4lligkeitsdatum f\u00FCr die Workflow-Management eingestellt sind.
-task.steps.title=Workflow Schritte
+task.steps.description=W\u00E4hlen Sie welche Elemente f\u00FCr den Workflow aktiviert werden sollen und geben Sie optional die dazugeh\u00F6rigen F\u00E4lligkeitsdaten f\u00FCr das Workflow-Management ein.
+task.steps.title=Abschnitte
 task.successfully.assigned=Aufgabe erfolgreich zugwiesen.
 task.text=$org.olat.course.nodes.ta\:form.task.text
 task.title=Titel
-task.type.description=Wenn als Gruppenaufgabe aktiviert ist, werden alle Schritte des Workflows als Gruppe und nicht als Einzelperson ausgef\u00FChrt.
-task.type.title=Aufgabetyp
+task.type.description=Wenn die Gruppenaufgabe aktiviert ist, werden alle Abschnitte des Workflows als Gruppe und nicht als Einzelperson ausgef\u00FChrt.
+task.type.title=Aufgabentyp
 upload.document=Dokument hochladen
-warning.group.pick.task=This is a group task\! The selection made here is valid for all members of the group "{0}"\! Make sur you discussed this selection within the group prior to select a task\! Only one member of the group can select the task for the group.
-warning.group.submit=This is a group task\! The submitted document is valid for all members of the group "{0}"\! Make sure you discussed this solution document prior to upload it here\! Only one member of the group can submit a solution on behalf of all group members.
-warning.group.task=This is a group task\! The task assignment, submit of documents and grading is performed as a group. Contact your group peers how to proceed to decide for a task and to collaboratively solve the task.
+warning.group.pick.task=Dies ist eine Gruppenaufgabe\! Die hier getroffene Auswahl ist f\u00FCr alle Mitglieder der Gruppe "{0}" g\u00FCltig\! Stellen Sie sicher dass diese Auswahl zuvor innerhalb Ihrer Gruppe diskutiert wurde\! Nur ein Gruppenmitglied kann die Gruppenaufgabe ausw\u00E4len.
+warning.group.submit=Dies ist eine Gruppenaufgabe\! Das abgegebene Dokument ist f\u00FCr alle Mitglieder der Gruppe "{0}" g\u00FCltig\! Stellen Sie sicher dass diese L\u00F6sung zuvor innerhalb  Ihrer Gruppe diskutiert wurde\! Nur ein Gruppenmitglied kann eine L\u00F6sung im Namen aller Gruppenmitglieder abgeben.
+warning.group.task=Dies ist eine Gruppenaufgabe\! Aufgbabenzuweisung, Abgabe sowie die Bewertung werden f\u00FCr die Gruppe vorgenommen. Besprechen Sie mit den anderen Teilnehmern ihrer Gruppe wie sie gemeinsam die Aufgabe ausw\u00E4hlen und bearbeiten wollen.
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
index a5cc5c5c225e45bc72056336c7507afb0797be15..ecb811532129fb2027dc0cc7893d6d00d679001f 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
@@ -1,10 +1,11 @@
 #Tue Mar 31 14:19:30 CEST 2015
 add.solution=Add solution
 add.task=Add task
-assessment.group.tool=Grade Group
+assessment.group.tool=Grade group
 assignment.config.title=Task assignment configuration
 assignment.deadline=Assignment deadline
 before=Before
+bulk.download.title=Download all submitted files
 bulk.review=Download review
 bulk.solutions=Download solutions
 bulk.submitted.documents=Download submitted documents
@@ -16,12 +17,12 @@ choosed.areas=Areas
 choosed.groups=Groups
 coach.assessment=Grade
 coach.close.revision.button=Close revision process
-coach.corrections.description=You have returned following corrections
+coach.corrections.description=You have returned the following revisions
 coach.documents.successfully.reviewed=Reviewed\!
 coach.need.revision.button=Needs revision
 coach.reviewed.button=Reviewed
 coach.revisions.description=You have reviewed the following documents
-coach.submit.corrections.to.revision.button=Send corrections
+coach.submit.corrections.to.revision.button=Send revisions
 coach.submitted.documents.description=These documents have been submitted
 coach.task.assigned.description=The following task has been assigned
 coach.waiting.assignment=Waiting for assignment
@@ -30,8 +31,9 @@ condition.accessibility.title=Access
 confirm.delete.solution.description=Do you want to delete the solution "{0}"?
 confirm.delete.solution.title=Delete submitted solution 
 confirmation.title=Submit confirmation
-create.areas=Create area
+create.areas=Create learning area
 create.groups=Create group
+days.after=Days after
 document=Submitted documents
 document.date=Date
 download.task=Download task
@@ -41,10 +43,10 @@ editor.title=Submission configuration
 embedded.editor=Submit files created with external editor (e.g. PDF)
 enabled=enabled
 error.assignment.closed=Assignment is closed
-error.duplicate.coaching=This user is member of multiple groups in this task.
-error.duplicate.memberships=The following users are member of multiple groups\: {1}
+error.duplicate.coaching=This user is a member of multiple groups in this task.
+error.duplicate.memberships=The following users are members of multiple groups\: {1}
 error.editor.atLeastOne=You must choose at least one kind of editor.
-error.max.documents=You are not allowd to submit more than <b>{0}</b> Documents. You are able to edit or change a document.
+error.max.documents=You are not allowed to submit more than <b>{0}</b> Documents. You may edit or change a document.
 error.missing.group=You have not selected a group yet
 error.missing.score.config=Missing score configuration
 error.missing.solutions=You have not submitted any solutions yet
@@ -61,17 +63,18 @@ group.apply.toall=For the whole group
 group.passed=Groups passed/failed
 group.score=Group score
 group.title=Score summary
+info.title=$org.olat.course.nodes.ms\:info.title
 log.title=Change log
-mail.confirm.assignment.body=Assignment was succesful
+mail.confirm.assignment.body=Assignment was successful
 mail.confirm.assignment.subject=Assignment
 max.documents=Max. number of documents
 notifications.correction=New correction "{0}" of "{1}"
-notifications.header=Grouptask in course "{0}"
+notifications.header=Group task in course "{0}"
 notifications.revision.group=New revisions "{0}" of "{2}" uploaded for group "{1}"
 notifications.revision.individual=New revisions "{0}" of "{1}"
 notifications.solution=New sample solutions "{0}"
 notifications.submission.group=New documents "{0}" von "{2}" submitted for group "{1}"
-notifications.submission.individual=Neue submitted documents "{0}" of "{1}"
+notifications.submission.individual=New submitted documents "{0}" of "{1}"
 open.editor=Open solution editor
 open.group=Open group
 pane.tab.accessibility=Access
@@ -83,6 +86,10 @@ pane.tab.workflow=Workflow
 preview=$org.olat.course.nodes.ta\:form.task.preview
 preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview
 preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview
+relative.dates=Relative dates
+relative.to.course.start=Course begin
+relative.to.course.launch=Course first launch
+relative.to.enrollment=Enrollment
 replace=Replace
 replace.document=Replace document
 review.and.correction=Review and correction
@@ -99,7 +106,7 @@ run.pick.task.description=Please select a task from the list below.
 run.review=Review and correction
 run.review.closed.without.documents=Your coach has reviewed the submitted documents.
 run.review.description=Your coach is now reviewing your submitted documents. You will get notified when a reviewed or corrected version is available.
-run.review.waiting=Your coach is now reviewing the turned in documents. You will get notified when a reviewed or corrected version is available.
+run.review.waiting=Your coach is now reviewing the submitted documents. You will get notified when a reviewed or corrected version is available.
 run.revised.description=The following revised documents have been submitted by you\:
 run.revision=Revision period
 run.revision.period.description=Your coach added a revision period. Create or upload a revised document.
@@ -108,13 +115,15 @@ run.solution=Sample solution
 run.solution.available.date={0}
 run.solutions.description=The following sample solutions are available for download\:
 run.submit=Submit
-run.submit.button=Submit
+run.submit.button=Final task submission
+run.submit.confirm=The submission of the task is final. 
+run.submit.confirm.group=$\:run.submit.confirm<br/>This is a group task! The submitted document is valid for all members of the group "{0}"\!
 run.submit.due.date=Due date\: {0}
-run.submit.individual.description.all=Turn in your solution by either uploading a document you created on your computer or use the editor to write your solution right here.
-run.submit.individual.description.editor=Turn in your solution by using the editor to write your solution right here.
-run.submit.individual.description.upload=Turn in your solution by uploading a document you created on your computer.
-run.submit.revision.button=Submit revisions
-run.submitted.description=The following solutions have been turned in by you\:
+run.submit.individual.description.all=Submit your solution by either uploading a document you created on your computer or use the editor to write your solution right here.
+run.submit.individual.description.editor=Submit your solution by using the editor to write your solution right here.
+run.submit.individual.description.upload=Submit your solution by uploading a document you created on your computer.
+run.submit.revision.button=Submit revision
+run.submitted.description=The following solutions have been submitted by you\:
 sample.solution=Sample solution
 sample.solution.enabled=enabled
 sample.solution.visible.after=Visible after
@@ -123,7 +132,7 @@ sampling.reuse=Your task will be assigned to more than one user / group
 sampling.unique=Your task will be assigned to only one single user / group
 selected.group=The group for this task is\: <i class\="o_icon o_icon_group"> </i> "{0}"
 solution.file=File
-solution.list.description=Select "Add solution" to add a solution or "Edit" to modify an existing solution. Note that solutions are not assigned to a particular task.
+solution.list.description=Select "Add solution" to add a solution or "Edit" to modify an existing solution. Please note that solutions are not assigned to a particular task.
 solution.list.title=Upload sample solutions
 solution.title=Title
 submission=Submission
@@ -131,7 +140,7 @@ submission.confirmation=The submission of the file "$filename" for $first $last
 submission.email.confirmation=Send text additionally as email
 submission.enabled=drop box enabled
 submission.mail.subject=OpenOLAT-confirmation-E-Mail
-submission.text=Text after handling in
+submission.text=Text after handing in
 submit.deadline=Submission deadline
 table.header.details.gta=$org.olat.course.nodes.ta\:table.header.details.ta
 table.header.group.name=Group
@@ -151,14 +160,14 @@ task.execution.individual=individual
 task.file=File
 task.list.description=Select "Add task" to add a new task or "Edit" to modify an existing task. If all users work on the same task, create a single task.
 task.list.title=Tasks
-task.steps.description=Select which elements in the task workflow are enabled and set optional due date for the workflow management.
+task.steps.description=Select which elements in the task workflow are enabled and set optional due dates for the workflow management.
 task.steps.title=Workflow steps
-task.successfully.assigned=The task is successfully assigned to you.
+task.successfully.assigned=The task was successfully assigned to you.
 task.text=$org.olat.course.nodes.ta\:form.task.text
 task.title=Title
 task.type.description=When group task is enabled, all steps of the workflow are executed as a group and not as an individual.
 task.type.title=Task type
 upload.document=Upload document
-warning.group.pick.task=This is a group task\! The selection made here is valid for all members of the group "{0}"\! Make sure you discussed this selection within the group prior to select a task\! Only one member of the group can select the task for the group.
-warning.group.submit=This is a group task\! The submitted document is valid for all members of the group "{0}"\! Make sure you discussed this solution document prior to upload it here\! Only one member of the group can submit a solution on behalf of all group members.
-warning.group.task=This is a group task\! The task assignment, submit of documents and grading is performed as a group. Contact your group peers how to proceed to decide for a task and to collaboratively solve the task.
+warning.group.pick.task=This is a group task\! The selection made here is valid for all members of the group "{0}"\! Make sure you discussed this selection within the group prior to selecting a task\! Only one member of the group can select the task for the group.
+warning.group.submit=This is a group task\! The submitted document is valid for all members of the group "{0}"\! Make sure you discussed this solution document prior to uploading it here\! Only one member of the group can submit a solution on behalf of all group members.
+warning.group.task=This is a group task\! The task assignment, submission of documents and grading are performed as a group. Contact your group peers on how to proceed on deciding for a task and to collaboratively solve the task.
diff --git a/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java b/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java
index f5b6c7678838c6a09364add7f3a47dbfd6d3ac50..44aad7023de334df682f25838fa60a316df2d5c4 100644
--- a/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java
+++ b/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java
@@ -28,8 +28,8 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
 import org.olat.course.archiver.ArchiveResource;
 import org.olat.course.nodes.ArchiveOptions;
-import org.olat.course.nodes.AssessableCourseNode;
 import org.olat.course.nodes.AssessmentToolOptions;
+import org.olat.course.nodes.TACourseNode;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.resource.OLATResource;
 
@@ -44,10 +44,10 @@ public class BulkDownloadToolController extends BasicController {
 
 	private final ArchiveOptions options;
 	private final OLATResource courseOres;
-	private final AssessableCourseNode courseNode;
+	private final TACourseNode courseNode;
 	
 	public BulkDownloadToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
-			AssessmentToolOptions asOptions, AssessableCourseNode courseNode) {
+			AssessmentToolOptions asOptions, TACourseNode courseNode) {
 		super(ureq, wControl);
 		this.options = new ArchiveOptions();
 		this.options.setGroup(asOptions.getGroup());
diff --git a/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java b/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java
index 8d2195a118ecfdbff2cd3d53e4ce6f8a1fafa8e7..044a9736112fde16757dab38e39eb907c21807ad 100644
--- a/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java
+++ b/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java
@@ -101,7 +101,7 @@ public class CourseReminderEditController extends FormBasicController {
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator());
+		FormLayoutContainer generalCont = FormLayoutContainer.createVerticalFormLayout("general", getTranslator());
 		generalCont.setRootForm(mainForm);
 		formLayout.add(generalCont);
 
@@ -111,6 +111,9 @@ public class CourseReminderEditController extends FormBasicController {
 		String desc = reminder.getDescription();
 		descriptionEl = uifactory.addTextElement("reminder.description", "reminder.description", 128, desc, generalCont);
 		
+		String sendTime = getSendTimeDescription();
+		uifactory.addStaticTextElement("send.time.description.label", sendTime, generalCont);
+		
 		//rules
 		String rulePage = velocity_root + "/edit_rules.html";
 		rulesCont = FormLayoutContainer.createCustomFormLayout("rules", getTranslator(), rulePage);
@@ -137,20 +140,28 @@ public class CourseReminderEditController extends FormBasicController {
 		}
 		
 		//email content
-		FormLayoutContainer contentCont = FormLayoutContainer.createDefaultFormLayout("contents", getTranslator());
+		FormLayoutContainer contentCont = FormLayoutContainer.createVerticalFormLayout("contents", getTranslator());
 		contentCont.setRootForm(mainForm);
 		formLayout.add(contentCont);
 		
 		String emailContent = reminder == null ? "" : reminder.getEmailBody();
 		emailEl = uifactory.addRichTextElementForStringDataMinimalistic("email.content", "email.content", emailContent, 10, 60, contentCont, getWindowControl());
 		
-		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		String buttonPage = velocity_root + "/edit_rules_buttons.html";
+		FormLayoutContainer buttonLayout = FormLayoutContainer.createCustomFormLayout("buttons", getTranslator(), buttonPage);
 		buttonLayout.setRootForm(mainForm);
-		contentCont.add(buttonLayout);
+		formLayout.add(buttonLayout);
 		uifactory.addFormSubmitButton("save", buttonLayout);
 		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
 	}
 	
+	protected String getSendTimeDescription() {
+		String interval = reminderModule.getInterval();
+		String desc = translate("interval." + interval);
+		String time = reminderModule.getDefaultSendTime();
+		return translate("send.time.description", new String[] { desc, time} );
+	}
+	
 	protected RuleElement initRuleForm(UserRequest ureq, RuleSPI ruleSpi, ReminderRule rule) {
 		String id = Integer.toString(counter++);
 		String type = ruleSpi.getClass().getSimpleName();
diff --git a/src/main/java/org/olat/course/reminder/ui/_content/edit_rules_buttons.html b/src/main/java/org/olat/course/reminder/ui/_content/edit_rules_buttons.html
new file mode 100644
index 0000000000000000000000000000000000000000..8c7c295ee00c713da68de7fc7a2cf4a38bce9dab
--- /dev/null
+++ b/src/main/java/org/olat/course/reminder/ui/_content/edit_rules_buttons.html
@@ -0,0 +1,4 @@
+<div class="o_block o_button_group">
+	$r.render("save")
+	$r.render("cancel")
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties
index 62ec93e6b9ed03ee2e994166640375d60231aeca..43be3091d38820d6da0e12a67983da1811fcc85d 100644
--- a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties
@@ -34,6 +34,8 @@ rule.submission.task=Grupenaufgabe Dokumente abgeben
 rules.description=Wenn folgende Bedingungen erf\u00FCllt sind
 send=Erinnerung jetzt schicken
 send.reminder=Geschickte Erinnerungen
+send.time.description={0} um {1}
+send.time.description.label=Intervall
 show.sent=Geschickte Erinnerungen zeigen
 table.header.actions=<i class\='o_icon o_icon_actions o_icon-lg'> </i>
 table.header.creationDate=Erstellt am
diff --git a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties
index 1cc150f0a03ffbf1c02d2ce6834a2df767179d1c..b7f032d2fe2054898a26d459ae289849b6eeaaf4 100644
--- a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties
@@ -34,6 +34,8 @@ rule.submission.task=Group task documents submission
 rules.description=When matching all of the following conditions
 send=Send reminders now
 send.reminder=Send reminders
+send.time.description={0} at {1}
+send.time.description.label=Interval
 show.sent=Show sent reminders
 table.header.actions=<i class\='o_icon o_icon_actions o_icon-lg'> </i>
 table.header.creationDate=Creation date
diff --git a/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java b/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java
index cd2b1a919d73e21af3ab1e5fe0af34c1ba0a7486..6a6ad015db95c13ef37fcb2f8e455253bea94716 100644
--- a/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java
+++ b/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java
@@ -26,6 +26,7 @@ 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;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
@@ -33,8 +34,8 @@ import org.olat.core.gui.components.form.flexible.impl.Form;
 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.FormLayoutContainer;
-import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.ExtendedFlexiTableSearchController;
+import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
@@ -60,7 +61,7 @@ public class BusinessGroupSearchController extends FormBasicController implement
 	private TextElement owner;
 	private TextElement description;
 	private TextElement courseTitle;
-	private FormSubmit searchButton;
+	private FormLink searchButton;
 	private SingleSelection rolesEl;
 	private SingleSelection publicEl;
 	private SingleSelection resourceEl;
@@ -174,7 +175,10 @@ public class BusinessGroupSearchController extends FormBasicController implement
 		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator());
 		formLayout.add(buttonLayout);
 		buttonLayout.setElementCssClass("o_sel_group_search_groups_buttons");
-		searchButton = uifactory.addFormSubmitButton("search", "search", buttonLayout);
+		searchButton = uifactory.addFormLink("search", buttonLayout, Link.BUTTON);
+		searchButton.setElementCssClass("o_sel_group_search_button");
+		searchButton.setCustomEnabledLinkCSS("btn btn-primary");
+		
 		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
 	}
 
diff --git a/src/main/java/org/olat/modules/reminder/ReminderInterval.java b/src/main/java/org/olat/modules/reminder/ReminderInterval.java
new file mode 100644
index 0000000000000000000000000000000000000000..41fdf81b275f32ec400b2848f1fa46909cd74183
--- /dev/null
+++ b/src/main/java/org/olat/modules/reminder/ReminderInterval.java
@@ -0,0 +1,74 @@
+/**
+ * <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.modules.reminder;
+
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 11.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum ReminderInterval {
+	
+	every24(24, "interval.24"),
+	every12(12, "interval.12"),
+	every8(8, "interval.8"),
+	every6(6, "interval.6"),
+	every4(4, "interval.4"),
+	every2(2, "interval.2"),
+	every1(1, "interval.1");
+	
+	private final int interval;
+	private final String key;
+	private final String i18nKey;
+	
+	private ReminderInterval(int interval, String i18nKey) {
+		this.interval = interval;
+		this.key = Integer.toString(interval);
+		this.i18nKey = i18nKey;
+	}
+	
+	public String key() {
+		return key;
+	}
+
+	public int interval() {
+		return interval;
+	}
+
+	public String i18nKey() {
+		return i18nKey;
+	}
+	
+	public static final ReminderInterval byKey(String key) {
+		ReminderInterval interval = null;
+		if(StringHelper.containsNonWhitespace(key)) {
+			for(ReminderInterval value:values()) {
+				if(key.equals(value.key())) {
+					interval = value;
+					break;
+				}
+			}
+		}
+		return interval;
+	}
+}
diff --git a/src/main/java/org/olat/modules/reminder/ReminderModule.java b/src/main/java/org/olat/modules/reminder/ReminderModule.java
index 7d9212acd5f1cb44141ce9997ee06c2754d7a4e2..8897b11119733e9c3aae1bd7544d94bb38f01355 100644
--- a/src/main/java/org/olat/modules/reminder/ReminderModule.java
+++ b/src/main/java/org/olat/modules/reminder/ReminderModule.java
@@ -53,6 +53,7 @@ public class ReminderModule extends AbstractSpringModule {
 	private static final String SMS_ENABLED = "sms.enabled";
 	private static final String SEND_TIME = "default.send.time";
 	private static final String SEND_TIMEZONE = "default.send.timezone";
+	private static final String INTERVAL = "send.interval";
 	
 	@Value("${reminders.enabled:true}")
 	private boolean enabled;
@@ -63,6 +64,9 @@ public class ReminderModule extends AbstractSpringModule {
 	private String defaultSendTime;
 	@Value("${reminders.default.send.timezone:server}")
 	private String defaultSendTimeZone;
+	@Value("${reminders.interval:24}")
+	private String interval;
+	
 	
 	@Autowired
 	private List<RuleSPI> ruleSpies;
@@ -91,6 +95,11 @@ public class ReminderModule extends AbstractSpringModule {
 			defaultSendTime = sendTimeObj;
 		}
 		
+		String intervalObj = getStringPropertyValue(INTERVAL, true);
+		if(StringHelper.containsNonWhitespace(intervalObj)) {
+			interval = intervalObj;
+		}
+
 		String sendTimezoneObj = getStringPropertyValue(SEND_TIMEZONE, true);
 		if(StringHelper.containsNonWhitespace(sendTimezoneObj)) {
 			defaultSendTimeZone = sendTimezoneObj;
@@ -121,7 +130,7 @@ public class ReminderModule extends AbstractSpringModule {
 		return selectedSpi;
 	}
 	/**
-	 * Default 0 0 9 * * ?
+	 * Default 0 0 9/1 * * ?
 	 * 
 	 */
 	private void configureQuartzJob() {
@@ -132,6 +141,7 @@ public class ReminderModule extends AbstractSpringModule {
 				String currentCronExpression = cronTrigger.getCronExpression();
 				String cronExpression = getCronExpression();
 				if(!cronExpression.equals(currentCronExpression)) {
+					log.info("Start reminder with this cron expression: " + cronExpression);
 					cronTrigger.setCronExpression(cronExpression);
 					scheduler.rescheduleJob("reminderTrigger", Scheduler.DEFAULT_GROUP, (Trigger)cronTrigger.clone());
 				}
@@ -141,7 +151,7 @@ public class ReminderModule extends AbstractSpringModule {
 		}
 	}
 	
-	private String getCronExpression() {
+	protected String getCronExpression() {
 		StringBuilder sb = new StringBuilder();
 		int hour = 9;
 		int minute = 0;
@@ -152,7 +162,21 @@ public class ReminderModule extends AbstractSpringModule {
 			minute = parsedTime.getMinute();
 		}
 
-		sb.append("0 ").append(minute).append(" ").append(hour).append(" * * ?");
+		ReminderInterval intervalVal = ReminderInterval.byKey(getInterval());
+		String cronInterval;
+		if(intervalVal != null && !ReminderInterval.every24.equals(intervalVal)) {
+			int i = intervalVal.interval();
+			if(i < hour) {
+				//correct the first time the cron job starts
+				int rest = hour % i;
+				hour = rest;
+			}
+			cronInterval = "/" + intervalVal.interval();
+		} else {
+			cronInterval = "";//or 24 hours
+		}
+
+		sb.append("0 ").append(minute).append(" ").append(hour).append(cronInterval).append(" * * ?");
 		return sb.toString();
 	}
 
@@ -174,6 +198,15 @@ public class ReminderModule extends AbstractSpringModule {
 		setStringProperty(SMS_ENABLED, Boolean.toString(smsEnabled), true);
 	}
 
+	public String getInterval() {
+		return interval;
+	}
+
+	public void setInterval(String interval) {
+		this.interval = interval;
+		setStringProperty(INTERVAL, interval, true);
+	}
+
 	public String getDefaultSendTime() {
 		return defaultSendTime;
 	}
@@ -199,4 +232,11 @@ public class ReminderModule extends AbstractSpringModule {
 		this.defaultSendTimeZone = timeZone.getID();
 		setStringProperty(SEND_TIMEZONE, defaultSendTimeZone, true);
 	}
+	
+	public void setScheduler(String interval, String defaultSendTime) {
+		this.interval = interval;
+		this.defaultSendTime = defaultSendTime;
+		setStringProperty(INTERVAL, interval, true);
+		setStringProperty(SEND_TIME, defaultSendTime, true);
+	}
 }
diff --git a/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java b/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java
index 7ecd9cf6cad9147fc471411be0ac71e199e64154..038f39c427e03ce7853fff412f7293a8a1252048 100644
--- a/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java
+++ b/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java
@@ -33,6 +33,8 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.reminder.ReminderInterval;
 import org.olat.modules.reminder.ReminderModule;
 import org.olat.modules.reminder.model.SendTime;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,17 +49,40 @@ public class ReminderAdminController extends FormBasicController {
 	
 	private static final String[] enableKeys = new String[]{ "on" };
 	
+	private static final String[] intervalKeys = new String[]{
+		ReminderInterval.every24.key(),
+		ReminderInterval.every12.key(),
+		ReminderInterval.every8.key(),
+		ReminderInterval.every6.key(),
+		ReminderInterval.every4.key(),
+		ReminderInterval.every2.key(),
+		ReminderInterval.every1.key()
+	};
+	
 	private MultipleSelectionElement enableEl;
 	private IntegerElement hoursEl, minutesEl;
 	private SingleSelection timezoneEl;
+	private SingleSelection intervalEl;
 	private FormLayoutContainer timeLayout;
 	
+	private String[] intervalValues;
+	
 	@Autowired
 	private ReminderModule reminderModule;
 	
 	public ReminderAdminController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
 		
+		intervalValues = new String[]{
+				translate(ReminderInterval.every24.i18nKey()),
+				translate(ReminderInterval.every12.i18nKey()),
+				translate(ReminderInterval.every8.i18nKey()),
+				translate(ReminderInterval.every6.i18nKey()),
+				translate(ReminderInterval.every4.i18nKey()),
+				translate(ReminderInterval.every2.i18nKey()),
+				translate(ReminderInterval.every1.i18nKey())
+		};
+		
 		initForm(ureq);
 	}
 	
@@ -76,6 +101,21 @@ public class ReminderAdminController extends FormBasicController {
 		enableEl.addActionListener(FormEvent.ONCHANGE);
 		enableEl.select(enableKeys[0], reminderModule.isEnabled());
 		
+		String interval = reminderModule.getInterval();
+		intervalEl = uifactory.addDropdownSingleselect("interval", formLayout, intervalKeys, intervalValues, null);
+		boolean found = false;
+		if(StringHelper.containsNonWhitespace(interval)) {
+			for(String intervalKey:intervalKeys) {
+				if(intervalKey.equals(interval)) {
+					intervalEl.select(intervalKey, true);
+					found = true;
+				}
+			}
+		}
+		if(!found) {
+			intervalEl.select(intervalKeys[0], true);
+		}
+		
 		int hour = 9;
 		int minute = 0;
 		
@@ -130,6 +170,7 @@ public class ReminderAdminController extends FormBasicController {
 		if(enableEl == source) {
 			boolean enabled = enableEl.isAtLeastSelected(1);
 			timeLayout.setVisible(enabled);
+			intervalEl.setVisible(enabled);
 			//enableSmsEl.setVisible(enabled);
 		}
 		
@@ -142,10 +183,12 @@ public class ReminderAdminController extends FormBasicController {
 		reminderModule.setEnabled(enabled);
 		
 		if(enabled) {
+			String interval = intervalEl.getSelectedKey();
+
 			int hours = hoursEl.getIntValue();
 			int minutes = minutesEl.getIntValue();
 			String sendTime = hours + ":" + minutes;
-			reminderModule.setDefaultSendTime(sendTime);
+			reminderModule.setScheduler(interval, sendTime);
 			
 			if(timezoneEl.isOneSelected()) {
 				String timeZoneID = timezoneEl.getSelectedKey();
diff --git a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties
index 7d98b715cc95ebe37f6fa2204dcdac3b628a3c21..5c51a614e73a506bf8a953736a542a9bd699230a 100644
--- a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties
@@ -6,6 +6,13 @@ default.send.time=Default time to send reminders
 enable.reminders=Activate course reminders
 enable.sms.reminders=SMS remidners
 error.group.not.found=Gruppe existiert nicht in diesem Kurs
+interval.24=1 Mal pro Tag
+interval.12=2 Mal pro Tag
+interval.8=3 Mal pro Tag
+interval.6=4 Mal pro Tag
+interval.4=Jede vier Stunde
+interval.2=Jede zwei Stunde
+interval.1=Jede Stunde
 reminder.admin.title=Course reminders
 rule.after.date=Nach dem Datum
 rule.course.enrollment.date=Enrollment date
@@ -14,3 +21,4 @@ rule.group.member=Gruppen Teilnehmer
 rule.initial.course.launch.date=Initial course launch date
 rule.recent.course.launch.date=Recent course launch date
 rule.user.property=User property
+
diff --git a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties
index 73c6889bd777ae38d7e6310c45b44ae8b774bba6..86a11b81a50d5783b0b76e3a81688b943fbbc5ab 100644
--- a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties
@@ -6,6 +6,14 @@ default.send.time=Default time to send reminders
 enable.reminders=Activate course reminders
 enable.sms.reminders=SMS remidners
 error.group.not.found=Group doesn't exist within this course
+interval=Interval
+interval.24=1 time per day
+interval.12=2 time per day
+interval.8=3 time per day
+interval.6=4 time per day
+interval.4=Every 4 hours
+interval.2=Every 2 hours
+interval.1=Every hour
 reminder.admin.title=Course reminders
 rule.after.date=After date
 rule.course.enrollment.date=Enrollment date
diff --git a/src/main/java/org/olat/repository/RepositoryService.java b/src/main/java/org/olat/repository/RepositoryService.java
index a6705bf391d5f48d6c232db2cd303ef756140489..5bf667a94ae41a116a464c5cfc31068416dab289 100644
--- a/src/main/java/org/olat/repository/RepositoryService.java
+++ b/src/main/java/org/olat/repository/RepositoryService.java
@@ -20,8 +20,10 @@
 package org.olat.repository;
 
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 import org.olat.basesecurity.Group;
 import org.olat.basesecurity.IdentityRef;
@@ -110,6 +112,24 @@ public interface RepositoryService {
 	 */
 	public int countMembers(List<? extends RepositoryEntryRef> res);
 	
+	/**
+	 * Return the smallest enrollment date.
+	 * 
+	 * @param re
+	 * @param identity
+	 * @return
+	 */
+	public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles);
+	
+	/**
+	 * Return the smallest enrollment date.
+	 * 
+	 * @param re
+	 * @param identity
+	 * @return
+	 */
+	public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles);
+	
 	/**
 	 * @param re The repository entry
 	 * @return True if the configuration allowed user to leave the entry right now
diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
index 62757174ac1311c3d5c2f56cafb793d7d53b2a3e..ece81e4c76fc0d8e6d91adf54ee57ab4986e8bff 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
@@ -22,9 +22,11 @@ package org.olat.repository.manager;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.persistence.EntityManager;
@@ -273,6 +275,81 @@ public class RepositoryEntryRelationDAO {
 		return count == null ? 0 : count.intValue();
 	}
 	
+	public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) {
+		if(re == null || identity == null) return null;
+		
+		List<String> roleList = null;
+		if(roles != null && roles.length > 0 && roles[0] != null) {
+			roleList = new ArrayList<>(roles.length);
+			for(String role:roles) {
+				roleList.add(role);
+			}
+		}
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("select min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v")
+		  .append(" inner join v.groups as relGroup")
+		  .append(" inner join relGroup.group as baseGroup")
+		  .append(" inner join baseGroup.members as members")
+		  .append(" where v.key=:repoKey and members.identity.key=:identityKey");
+		if(roleList != null && roleList.size() > 0) {
+			sb.append(" and members.role in (:roles)");
+		}
+
+		TypedQuery<Date> datesQuery = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Date.class)
+				.setParameter("repoKey", re.getKey())
+				.setParameter("identityKey", identity.getKey());
+		if(roleList != null && roleList.size() > 0) {
+			datesQuery.setParameter("roles", roleList);
+		}
+		
+		List<Date> dates = datesQuery.getResultList();
+		return dates.isEmpty() ? null : dates.get(0);
+	}
+	
+	public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) {
+		if(re == null) return null;
+		
+		List<String> roleList = null;
+		if(roles != null && roles.length > 0 && roles[0] != null) {
+			roleList = new ArrayList<>(roles.length);
+			for(String role:roles) {
+				roleList.add(role);
+			}
+		}
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("select members.identity.key, min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v")
+		  .append(" inner join v.groups as relGroup")
+		  .append(" inner join relGroup.group as baseGroup")
+		  .append(" inner join baseGroup.members as members")
+		  .append(" where v.key=:repoKey");
+		if(roleList != null && roleList.size() > 0) {
+			sb.append(" and members.role in (:roles)");
+		}
+		sb.append(" group by members.identity.key");
+
+		TypedQuery<Object[]> datesQuery = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Object[].class)
+				.setParameter("repoKey", re.getKey());
+		if(roleList != null && roleList.size() > 0) {
+			datesQuery.setParameter("roles", roleList);
+		}
+		
+		List<Object[]> dateList = datesQuery.getResultList();
+		Map<Long,Date> dateMap = new HashMap<>((dateList.size() * 2) + 1);
+		for(Object[] dateArr:dateList) {
+			Long key = (Long)dateArr[0];
+			Date date = (Date)dateArr[1];
+			if(key != null && date != null) {
+				dateMap.put(key, date);
+			}
+		}
+
+		return dateMap;
+	}
+	
 	
 	public List<Long> getAuthorKeys(RepositoryEntryRef re) {
 		
diff --git a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
index 8aaa4288bb276117df47a5926fa6bb90bba0122f..3978977bff08bfb96f009d2e0be31a2000fe6f75 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
@@ -26,6 +26,7 @@ import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 import org.olat.basesecurity.BaseSecurity;
@@ -440,6 +441,16 @@ public class RepositoryServiceImpl implements RepositoryService {
 	public int countMembers(List<? extends RepositoryEntryRef> res) {
 		return reToGroupDao.countMembers(res);
 	}
+	
+	@Override
+	public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) {
+		return reToGroupDao.getEnrollmentDate(re, identity, roles);
+	}
+
+	@Override
+	public Map<Long, Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) {
+		return reToGroupDao.getEnrollmentDates(re, roles);
+	}
 
 	@Override
 	public List<Long> getAuthors(RepositoryEntryRef re) {
diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
index 1ea2a850189879ac5e7f366ad4c3897b487520ef..9e4406023df1cd8ede3269a5d5c5344c3c6db41c 100644
--- a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
+++ b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
@@ -635,14 +635,17 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 	
 	protected final void doClose(UserRequest ureq) {
 		// Now try to go back to place that is attacked to (optional) root back business path
-		if (launchedFromPoint != null && StringHelper.containsNonWhitespace(launchedFromPoint.getBusinessPath())) {
+		if (launchedFromPoint != null && StringHelper.containsNonWhitespace(launchedFromPoint.getBusinessPath())
+				&& launchedFromPoint.getEntries() != null && launchedFromPoint.getEntries().size() > 0) {
 			BusinessControl bc = BusinessControlFactory.getInstance().createFromPoint(launchedFromPoint);
-			WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
-			try {
-				//make the resume secure. If something fail, don't generate a red screen
-				NewControllerFactory.getInstance().launch(ureq, bwControl);
-			} catch (Exception e) {
-				logError("Error while resuming with root leve back business path::" + launchedFromPoint.getBusinessPath(), e);
+			if(bc.hasContextEntry()) {
+				WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
+				try {
+					//make the resume secure. If something fail, don't generate a red screen
+					NewControllerFactory.getInstance().launch(ureq, bwControl);
+				} catch (Exception e) {
+					logError("Error while resuming with root level back business path::" + launchedFromPoint.getBusinessPath(), e);
+				}
 			}
 		}
 		
diff --git a/src/main/webapp/static/js/functions.js b/src/main/webapp/static/js/functions.js
index 8d49e8e9bf2549e1b9d068e19232923389f507cd..b5ee2a76d3eb9ad4acbe8710b1fbe5d346d5c7b9 100644
--- a/src/main/webapp/static/js/functions.js
+++ b/src/main/webapp/static/js/functions.js
@@ -1192,6 +1192,22 @@ function o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt) {
 	})
 }
 
+function o_ffXHRNFEvent(targetUrl) {
+	var data = new Object();
+	jQuery.ajax(targetUrl,{
+		type:'GET',
+		data: data,
+		cache: false,
+		dataType: 'json',
+		success: function(data, textStatus, jqXHR) {
+			console.log('Hourra');
+		},
+		error: function(jqXHR, textStatus, errorThrown) {
+			if(window.console) console.log('Error status', textStatus);
+		}
+	})
+}
+
 //
 // param formId a String with flexi form id
 function setFlexiFormDirtyByListener(e){
diff --git a/src/main/webapp/static/js/js.plugins.min.js b/src/main/webapp/static/js/js.plugins.min.js
index 83a059c472439599a227179a4108a2ac72cfb6f1..8d6fcf1b229eb084c706c5265093fe9f176d6daf 100644
--- a/src/main/webapp/static/js/js.plugins.min.js
+++ b/src/main/webapp/static/js/js.plugins.min.js
@@ -5,7 +5,7 @@
  * Dual licensed under the MIT or GPL Version 2 licenses.
  *
  */
-jQuery.periodic=function(l,h){if(jQuery.isFunction(l)){h=l;l={}}var c=jQuery.extend({},jQuery.periodic.defaults,{ajax_complete:j,increment:g,reset:f,cancel:i},l);c.cur_period=c.period;c.tid=false;var e="";b();return c;function b(){i();c.tid=setTimeout(function(){h.call(c);g();if(c.tid){b()}},c.cur_period)}function j(n,m){if(m==="success"&&e!==n.responseText){e=n.responseText;f()}}function g(){c.cur_period*=c.decay;if(c.cur_period<c.period){f()}else{if(c.cur_period>c.max_period){c.cur_period=c.max_period;if(c.on_max!==undefined){c.on_max.call(c)}}}}function f(){c.cur_period=c.period;b()}function i(){clearTimeout(c.tid);c.tid=null}function k(){}function a(){}function d(){}};jQuery.periodic.defaults={period:4000,max_period:1800000,decay:1.5,on_max:undefined};var Hashtable=(function(){var p="function";var n=(typeof Array.prototype.splice==p)?function(s,r){s.splice(r,1)}:function(u,t){var s,v,r;if(t===u.length-1){u.length=t}else{s=u.slice(t+1);u.length=t;for(v=0,r=s.length;v<r;++v){u[t+v]=s[v]}}};function a(t){var r;if(typeof t=="string"){return t}else{if(typeof t.hashCode==p){r=t.hashCode();return(typeof r=="string")?r:a(r)}else{if(typeof t.toString==p){return t.toString()}else{try{return String(t)}catch(s){return Object.prototype.toString.call(t)}}}}}function g(r,s){return r.equals(s)}function e(r,s){return(typeof s.equals==p)?s.equals(r):(r===s)}function c(r){return function(s){if(s===null){throw new Error("null is not a valid "+r)}else{if(typeof s=="undefined"){throw new Error(r+" must not be undefined")}}}}var q=c("key"),l=c("value");function d(u,s,t,r){this[0]=u;this.entries=[];this.addEntry(s,t);if(r!==null){this.getEqualityFunction=function(){return r}}}var h=0,j=1,f=2;function o(r){return function(t){var s=this.entries.length,v,u=this.getEqualityFunction(t);while(s--){v=this.entries[s];if(u(t,v[0])){switch(r){case h:return true;case j:return v;case f:return[s,v[1]]}}}return false}}function k(r){return function(u){var v=u.length;for(var t=0,s=this.entries.length;t<s;++t){u[v+t]=this.entries[t][r]}}}d.prototype={getEqualityFunction:function(r){return(typeof r.equals==p)?g:e},getEntryForKey:o(j),getEntryAndIndexForKey:o(f),removeEntryForKey:function(s){var r=this.getEntryAndIndexForKey(s);if(r){n(this.entries,r[0]);return r[1]}return null},addEntry:function(r,s){this.entries[this.entries.length]=[r,s]},keys:k(0),values:k(1),getEntries:function(s){var u=s.length;for(var t=0,r=this.entries.length;t<r;++t){s[u+t]=this.entries[t].slice(0)}},containsKey:o(h),containsValue:function(s){var r=this.entries.length;while(r--){if(s===this.entries[r][1]){return true}}return false}};function m(s,t){var r=s.length,u;while(r--){u=s[r];if(t===u[0]){return r}}return null}function i(r,s){var t=r[s];return(t&&(t instanceof d))?t:null}function b(t,r){var w=this;var v=[];var u={};var x=(typeof t==p)?t:a;var s=(typeof r==p)?r:null;this.put=function(B,C){q(B);l(C);var D=x(B),E,A,z=null;E=i(u,D);if(E){A=E.getEntryForKey(B);if(A){z=A[1];A[1]=C}else{E.addEntry(B,C)}}else{E=new d(D,B,C,s);v[v.length]=E;u[D]=E}return z};this.get=function(A){q(A);var B=x(A);var C=i(u,B);if(C){var z=C.getEntryForKey(A);if(z){return z[1]}}return null};this.containsKey=function(A){q(A);var z=x(A);var B=i(u,z);return B?B.containsKey(A):false};this.containsValue=function(A){l(A);var z=v.length;while(z--){if(v[z].containsValue(A)){return true}}return false};this.clear=function(){v.length=0;u={}};this.isEmpty=function(){return !v.length};var y=function(z){return function(){var A=[],B=v.length;while(B--){v[B][z](A)}return A}};this.keys=y("keys");this.values=y("values");this.entries=y("getEntries");this.remove=function(B){q(B);var C=x(B),z,A=null;var D=i(u,C);if(D){A=D.removeEntryForKey(B);if(A!==null){if(!D.entries.length){z=m(v,C);n(v,z);delete u[C]}}}return A};this.size=function(){var A=0,z=v.length;while(z--){A+=v[z].entries.length}return A};this.each=function(C){var z=w.entries(),A=z.length,B;while(A--){B=z[A];C(B[0],B[1])}};this.putAll=function(H,C){var B=H.entries();var E,F,D,z,A=B.length;var G=(typeof C==p);while(A--){E=B[A];F=E[0];D=E[1];if(G&&(z=w.get(F))){D=C(F,z,D)}w.put(F,D)}};this.clone=function(){var z=new b(t,r);z.putAll(w);return z}}return b})();(function(b){b.fn.ooLog=function(f,d,e){var c=null;b(this).each(function(){c=b(this).data("_ooLog");if(c==undefined){c=new a();b(this).data("_ooLog",c)}});if(f==undefined){return c}else{if(typeof f==="string"){if(c){c.log(f,d,e)}}}};function a(){return this}a.prototype={isDebugEnabled:function(){return o_info.JSTracingLogDebugEnabled},log:function(e,c,d){if(!this.isDebugEnabled()){return}jQuery.post(o_info.JSTracingUri,{level:e,logMsg:c,jsFile:d})}}})(jQuery);(function(b){b.fn.ooTranslator=function(){var d=null;b(document).each(function(){d=b(document).data("_ooTranslator");if(d==undefined){d=new a();b(document).data("_ooTranslator",d)}});return d};function a(){return this}a.prototype={mapperUrl:null,translators:null,initialize:function(d){this.mapperUrl=d;this.translators=new Object()},getTranslator:function(d,f){if(this.translators[d]==null){this.translators[d]=new Object()}if(this.translators[d][f]==null){var e=this.mapperUrl+"/"+d+"/"+f+"/translations.js";jQuery.ajax(e,{async:false,dataType:"json",success:function(g,i,h){jQuery(document).ooTranslator()._createTranslator(g,d,f)}})}return this.translators[d][f]},_createTranslator:function(e,d,f){this.translators[d][f]=new c().initialize(e,d,f)}};function c(){return this}c.prototype={localizationData:null,bundle:null,locale:null,initialize:function(f,d,e){this.bundle=e;this.locale=d;this.localizationData=f;return this},translate:function(d){if(this.localizationData[d]){return this.localizationData[d]}else{return this.bundle+":"+d}}}})(jQuery);+function(b){var a=function(){this.addExtraElements();this.state={busy:false,brandW:0,sitesW:0,sitesDirty:false,sites:{collapsed:this.isSitesCollapsed(),extended:this.isSitesExtended},tabsW:0,tabsDirty:false,tabs:{collapsed:this.isTabsCollapsed(),extended:this.isTabsExtended()},toolsW:0,toolsDirty:false,tools:{collapsed:this.isToolsCollapsed(),extended:this.isToolsExtended()},offCanvasWidth:0,moreW:0};var c=b("#o_offcanvas_right").css("width");if(c){this.state.offCanvasWidth=parseInt(c.replace(/[^\d.]/g,""));this.initListners();this.calculateWidth();this.optimize()}};a.prototype.initListners=function(){b(window).resize(b.proxy(this.onResizeCallback,this));b(document).on("oo.nav.sites.modified",b.proxy(function(){this.state.sitesDirty=true},this));b(document).on("oo.nav.tabs.modified",b.proxy(function(){this.state.tabsDirty=true},this));b(document).on("oo.nav.tools.modified",b.proxy(function(){this.state.toolsDirty=true},this));b(document).on("oo.dom.replacement.after",b.proxy(this.onDOMreplacementCallback,this));b(window).on("orientationchange",b.proxy(this.hideRight,this));b("#o_navbar_right-toggle").on("click",b.proxy(this.toggleRight,this));b("#o_offcanvas_right .o_offcanvas_close").on("click",b.proxy(this.hideRight,this));b("#o_navbar_more").on("shown.bs.dropdown",this.onDropdownShown);b("#o_navbar_more").on("hidden.bs.dropdown",this.onDropdownHidden)};a.prototype.onResizeCallback=function(){if(!this.state.busy){this.state.busy=true;this.calculateWidth();this.optimize();this.state.busy=false}};a.prototype.onDOMreplacementCallback=function(){if(!this.state.busy&&(this.state.sitesDirty||this.state.tabsDirty||this.state.toolsDirty)){this.state.busy=true;this.cleanupMoreDropdown();this.calculateWidth();this.optimize();this.state.sitesDirty=false;this.state.tabsDirty=false;this.state.toolsDirty=false;this.state.busy=false}};a.prototype.onDropdownShown=function(c){var f=b("#o_navbar_more .dropdown-menu");if(f.length){var d=f.offset().left;if(d<0){f.removeClass("dropdown-menu-right")}}};a.prototype.onDropdownHidden=function(c){var d=b("#o_navbar_more .dropdown-menu");d.addClass("dropdown-menu-right")};a.prototype.calculateWidth=function(){var c=b("#o_navbar_container .o_navbar-collapse");this.state.navbarW=c.innerWidth();this.state.brandW=b(".o_navbar-brand").outerWidth(true);this.state.sitesW=this.getSites().outerWidth(true);this.state.tabsW=this.getTabs().outerWidth(true);this.state.toolsW=this.getTools().outerWidth(false);this.state.moreW=b("#o_navbar_more:visible").outerWidth(true)};a.prototype.getOverflow=function(c){var d=this.state.navbarW;d-=this.state.sitesW;d-=this.state.tabsW;d-=this.state.toolsW;d-=this.state.brandW;d-=this.state.moreW;d-=25;return -d};a.prototype.optimize=function(h){var c=this.getOverflow();var k=this.getSites();var l=this.getTabs();var g=this.getTools();var n=this.getMoreDropdown();var f=this.getOffcanvasRight();this.updateState();while(c>0&&(!this.state.tabs.collapsed||!this.state.sites.collapsed||!this.state.tools.collapsed)){if(!this.state.tabs.collapsed){this.collapse(l,n,"li","o_dropdown_tab")}else{if(!this.state.sites.collapsed){this.collapse(k,n,"li","o_dropdown_site")}else{if(!this.state.tools.collapsed){this.collapse(g,f,".o_navbar_tool:not(#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu)","o_tool_right")}}}this.calculateWidth();c=this.getOverflow();this.updateState()}while(c<0&&(!this.state.tabs.extended||!this.state.sites.extended||!this.state.tools.extended)){if(!this.state.tools.extended){var m=this.extend(f,g.children("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").first(),".o_tool_right","o_tool_right",true);if(!m){break}}if(!this.state.sites.extended){var j=this.extend(n,k,"li","o_dropdown_site");if(!j){break}}else{if(!this.state.tabs.extended){var d=this.extend(n,l,"li","o_dropdown_tab");if(!d){break}}}this.calculateWidth();c=this.getOverflow();this.updateState()}if(this.state.sites.extended&&this.state.tabs.extended){var i=b("#o_navbar_more");i.css("display","none")}this.checkToolsOrder()};a.prototype.updateState=function(){this.state.sites.collapsed=this.isSitesCollapsed();this.state.sites.extended=this.isSitesExtended();this.state.tabs.collapsed=this.isTabsCollapsed();this.state.tabs.extended=this.isTabsExtended();this.state.tools.collapsed=this.isToolsCollapsed();this.state.tools.extended=this.isToolsExtended()};a.prototype.collapse=function(g,d,c,f){var e=g.find(c);if(e.length){e=e.last()}if(e.length){f&&e.addClass(f);if(d){e.prependTo(d)}}this.updateDropdownToggle(g)};a.prototype.extend=function(g,d,c,i,f){var e=g.find(c);if(e.length){e=e.first()}var j=false;if(e.length){if(d){if(f){d.before(e)}else{e.appendTo(d)}this.updateDropdownToggle(g);this.calculateWidth();var h=this.getOverflow();if(h>0){e.prependTo(g)}else{i&&e.removeClass(i);j=true}}}this.updateDropdownToggle(g);return j};a.prototype.updateDropdownToggle=function(c){var d=c.parents(".o_dropdown_toggle");if(!d.length){return}if(c.children().length){d.css("display","block")}else{d.css("display","none")}};a.prototype.addExtraElements=function(){var d=b("#o_navbar_container .o_navbar-collapse");var c=b("#o_navbar_more");if(c.length==0){c=b('<ul id="o_navbar_more" class="nav o_navbar-nav o_dropdown_toggle"><li><a class="dropdown-toggle" data-toggle="dropdown" href="#"">'+o_info.i18n_topnav_more+' <b class="caret"></b></a><ul class="dropdown-menu dropdown-menu-right"></ul></li></ul>');c.appendTo(d)}this.getSites().append('<li class="divider o_dropdown_site"></li>');b("#o_navbar_help .o_icon, #o_navbar_print .o_icon").addClass("o_icon-fw")};a.prototype.cleanupMoreDropdown=function(){if(!this.state.sitesDirty){var f=this.getSites();var d=this.getMoreDropdown().children(".o_dropdown_site");d.appendTo(f)}else{this.getSites().append('<li class="divider o_dropdown_site"></li>')}if(!this.state.tabsDirty){var e=this.getTabs();var c=this.getMoreDropdown().children(".o_dropdown_tab");c.prependTo(e)}this.getMoreDropdown().empty()};a.prototype.checkToolsOrder=function(){var f=this.getTools();var e=f.find("#o_navbar_help");var d=f.find("#o_navbar_print");var c=f.find("#o_navbar_imclient");if(c&&d){c.after(d)}if(c&&e){c.after(e)}};a.prototype.showRight=function(){if(!this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;var d=this;var c=b("#o_offcanvas_right");c.show().transition({x:-d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;b("body").addClass("o_offcanvas_right_visible");var e=b.proxy(d.hideRightOnClick,d);setTimeout(function(){b("html").on("click",e)},10)})}};a.prototype.hideRightOnClick=function(c){if("INPUT"!=c.target.nodeName){this.hideRight()}};a.prototype.hideRight=function(){if(this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;b("html").off("click",b.proxy(this.hideRight,this));var d=this;var c=b("#o_offcanvas_right");c.transition({x:d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;c.hide();b("body").removeClass("o_offcanvas_right_visible")})}};a.prototype.toggleRight=function(){if(this.isOffcanvasVisible()){this.hideRight()}else{this.showRight()}};a.prototype.isOffcanvasVisible=function(){return b("#o_offcanvas_right:visible").length};a.prototype.getSites=function(){return b("#o_navbar_container .o_navbar_sites")};a.prototype.getTabs=function(){return b("#o_navbar_container .o_navbar_tabs")};a.prototype.getTools=function(){return b("#o_navbar_container #o_navbar_tools_permanent")};a.prototype.getMoreDropdown=function(){return b("#o_navbar_more .dropdown-menu")};a.prototype.getOffcanvasRight=function(){return b("#o_offcanvas_right_container .o_navbar-right")};a.prototype.isSitesCollapsed=function(){return !this.getSites().children("li").not(".divider").length};a.prototype.isSitesExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_site").not(".divider").length};a.prototype.isTabsCollapsed=function(){return !this.getTabs().children("li").length};a.prototype.isTabsExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_tab").length};a.prototype.isToolsCollapsed=function(){return !this.getTools().children(".o_navbar_tool").not("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").length};a.prototype.isToolsExtended=function(){return !this.getOffcanvasRight().children(".o_tool_right").length};b(document).ready(function(){var d=b("#o_navbar_wrapper");if(d){var c=new a();window.OPOL.navbar=c}})}(jQuery);+function(b){b.fn.ooBgCarrousel=function(){return new a()};var a=function(){};a.prototype.initCarrousel=function(g){this.settings=b.extend({query:null,images:[],shuffle:false,shuffleFirst:false,durationshow:5000,durationout:500,durationin:500,easeout:"ease",easein:"ease"},g);this.pos=null;if(this.settings.query==null||this.settings.images.length==0){return}this.initialImage=this.settings.images[0];if(this.settings.shuffle){var f=this.settings.images;for(var d,c,e=f.length;e;d=parseInt(Math.random()*e),c=f[--e],f[e]=f[d],f[d]=c){}}if(this.settings.shuffleFirst){this._replaceImage()}this.rotate()};a.prototype.rotate=function(){setTimeout(b.proxy(this._hideCurrent,this),this.settings.durationshow)};a.prototype._hideCurrent=function(){var c=b(this.settings.query);if(c&&c.size()>0){c.transition({opacity:0,duration:this.settings.durationout,easing:this.settings.easeout},b.proxy(this._showNext,this))}};a.prototype._replaceImage=function(d){if(!d){d=b(this.settings.query)}if(d&&d.size()>0){this.newImg="";this.oldImg="";if(this.pos==null){this.pos=1;this.oldImg=this.initialImage}else{this.oldImg=this.settings.images[this.pos];this.pos++;if(this.settings.images.length==this.pos){this.pos=0}}this.newImg=this.settings.images[this.pos];var c=d.css("background-image");if(c.indexOf(this.oldImg)==-1){d.transition({opacity:1,duration:0});return}var e=c.replace(this.oldImg,this.newImg);d.css("background-image",e)}};a.prototype._showNext=function(){var c=b(this.settings.query);this._replaceImage(c);c.transition({opacity:1,duration:this.settings.durationin,easing:this.settings.easein},b.proxy(this.rotate,this))}}(jQuery);!function(a){function b(){function b(a){"remove"===a&&this.each(function(a,b){var c=e(b);c&&c.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(a,b){var c=tinymce.get(b.id.replace(/_parent$/,""));c&&c.remove()})}function d(a){var c,d=this;if(null!=a)b.call(d),d.each(function(b,c){var d;(d=tinymce.get(c.id))&&d.setContent(a)});else if(d.length>0&&(c=tinymce.get(d[0].id)))return c.getContent()}function e(a){var b=null;return a&&a.id&&g.tinymce&&(b=tinymce.get(a.id)),b}function f(a){return!!(a&&a.length&&g.tinymce&&a.is(":tinymce"))}var h={};a.each(["text","html","val"],function(b,g){var i=h[g]=a.fn[g],j="text"===g;a.fn[g]=function(b){var g=this;if(!f(g))return i.apply(g,arguments);if(b!==c)return d.call(g.filter(":tinymce"),b),i.apply(g.not(":tinymce"),arguments),g;var h="",k=arguments;return(j?g:g.eq(0)).each(function(b,c){var d=e(c);h+=d?j?d.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):d.getContent({save:!0}):i.apply(a(c),k)}),h}}),a.each(["append","prepend"],function(b,d){var g=h[d]=a.fn[d],i="prepend"===d;a.fn[d]=function(a){var b=this;return f(b)?a!==c?("string"==typeof a&&b.filter(":tinymce").each(function(b,c){var d=e(c);d&&d.setContent(i?a+d.getContent():d.getContent()+a)}),g.apply(b.not(":tinymce"),arguments),b):void 0:g.apply(b,arguments)}}),a.each(["remove","replaceWith","replaceAll","empty"],function(c,d){var e=h[d]=a.fn[d];a.fn[d]=function(){return b.call(this,d),e.apply(this,arguments)}}),h.attr=a.fn.attr,a.fn.attr=function(b,g){var i=this,j=arguments;if(!b||"value"!==b||!f(i))return g!==c?h.attr.apply(i,j):h.attr.apply(i,j);if(g!==c)return d.call(i.filter(":tinymce"),g),h.attr.apply(i.not(":tinymce"),j),i;var k=i[0],l=e(k);return l?l.getContent({save:!0}):h.attr.apply(a(k),j)}}var c,d,e,f=[],g=window;a.fn.tinymce=function(c){function h(){var d=[],f=0;e||(b(),e=!0),l.each(function(a,b){var e,g=b.id,h=c.oninit;g||(b.id=g=tinymce.DOM.uniqueId()),tinymce.get(g)||(e=new tinymce.Editor(g,c,tinymce.EditorManager),d.push(e),e.on("init",function(){var a,b=h;l.css("visibility",""),h&&++f==d.length&&("string"==typeof b&&(a=-1===b.indexOf(".")?null:tinymce.resolve(b.replace(/\.\w+$/,"")),b=tinymce.resolve(b)),b.apply(a||tinymce,d))}))}),a.each(d,function(a,b){b.render()})}var i,j,k,l=this,m="";if(!l.length)return l;if(!c)return window.tinymce?tinymce.get(l[0].id):null;if(l.css("visibility","hidden"),g.tinymce||d||!(i=c.script_url))1===d?f.push(h):h();else{d=1,j=i.substring(0,i.lastIndexOf("/")),-1!=i.indexOf(".min")&&(m=".min"),g.tinymce=g.tinyMCEPreInit||{base:j,suffix:m},-1!=i.indexOf("gzip")&&(k=c.language||"en",i=i+(/\?/.test(i)?"&":"?")+"js=true&core=true&suffix="+escape(m)+"&themes="+escape(c.theme||"modern")+"&plugins="+escape(c.plugins||"")+"&languages="+(k||""),g.tinyMCE_GZ||(g.tinyMCE_GZ={start:function(){function b(a){tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(a))}b("langs/"+k+".js"),b("themes/"+c.theme+"/theme"+m+".js"),b("themes/"+c.theme+"/langs/"+k+".js"),a.each(c.plugins.split(","),function(a,c){c&&(b("plugins/"+c+"/plugin"+m+".js"),b("plugins/"+c+"/langs/"+k+".js"))})},end:function(){}}));var n=document.createElement("script");n.type="text/javascript",n.onload=n.onreadystatechange=function(b){b=b||window.event,2===d||"load"!=b.type&&!/complete|loaded/.test(n.readyState)||(tinymce.dom.Event.domLoaded=1,d=2,c.script_loaded&&c.script_loaded(),h(),a.each(f,function(a,b){b()}))},n.src=i,document.body.appendChild(n)}return l},a.extend(a.expr[":"],{tinymce:function(a){var b;return a.id&&"tinymce"in window&&(b=tinymce.get(a.id),b&&b.editorManager===tinymce)?!0:!1}})}(jQuery);OPOL={};var o2c=0;var o3c=new Array();o_info.guibusy=false;o_info.linkbusy=false;o_info.debug=true;var BLoader={_ajaxLoadedJS:new Array(),_isAlreadyLoadedJS:function(b){var a=true;jQuery("head script[src]").each(function(d,c){if(jQuery(c).attr("src").indexOf(b)!=-1){a=false}});if(jQuery.inArray(b,this._ajaxLoadedJS)!=-1){a=false}return !a},loadJS:function(b,c,a){if(!this._isAlreadyLoadedJS(b)){if(o_info.debug){o_log("BLoader::loadJS: loading ajax::"+a+" url::"+b)}if(a){jQuery.ajax(b,{async:false,dataType:"script",cache:true,success:function(d,f,e){}});this._ajaxLoadedJS.push(b)}else{jQuery.getScript(b)}if(o_info.debug){o_log("BLoader::loadJS: loading DONE url::"+b)}}else{if(o_info.debug){o_log("BLoader::loadJS: already loaded url::"+b)}}},executeGlobalJS:function(jsString,contextDesc){try{if(window.execScript){window.execScript(jsString)}else{window.eval(jsString)}}catch(e){if(window.console){console.log(contextDesc,"cannot execute js",jsString)}if(o_info.debug){o_logerr("BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString))}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString),"functions.js::BLoader::executeGlobalJS::"+contextDesc)}if(window.location.href.indexOf("o_winrndo")!=-1){window.location.reload()}else{window.location.href=window.location.href+(window.location.href.indexOf("?")!=-1?"&":"?")+"o_winrndo=1"}}},loadCSS:function(b,o,q){var r=window.document;try{if(r.createStyleSheet){var j=r.styleSheets;var d=0;var p=0;for(i=0;i<j.length;i++){var m=j[i];var g=m.href;if(g==b){d++;if(m.disabled){m.disabled=false;return}else{if(o_info.debug){o_logwarn("BLoader::loadCSS: style: "+b+" already in document and not disabled! (duplicate add)")}return}}if(m.id=="o_theme_css"){p=i}}if(d>1&&o_info.debug){o_logwarn("BLoader::loadCSS: apply styles: num of stylesheets found was not 0 or 1:"+d)}if(q){p=j.length}var f=r.createStyleSheet(b,p)}else{var c=jQuery("#"+o);if(c&&c.size()>0){if(o_info.debug){o_logwarn("BLoader::loadCSS: stylesheet already found in doc when trying to add:"+b+", with id "+o)}return}else{var a=jQuery('<link id="'+o+'" rel="stylesheet" type="text/css" href="'+b+'">');if(q){a.insertBefore(jQuery("#o_fontSize_css"))}else{a.insertBefore(jQuery("#o_theme_css"))}}}}catch(n){if(window.console){console.log(n)}if(o_info.debug){o_logerr("BLoader::loadCSS: Error when loading CSS from URL::"+b)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::loadCSS: Error when loading CSS from URL::"+b,"functions.js::BLoader::loadCSS")}}},unLoadCSS:function(a,m){var n=window.document;try{if(n.createStyleSheet){var f=n.styleSheets;var d=0;var o=a;var b=window.location.href.substring(0,window.location.href.indexOf("/",8));if(a.indexOf(b)==0){o=a.substring(b.length)}for(i=0;i<f.length;i++){var g=f[i].href;if(g==a||g==o){d++;if(!f[i].disabled){f[i].disabled=true}else{if(o_info.debug){o_logwarn("stylesheet: when removing: matching url, but already disabled! url:"+g)}}}}if(d!=1&&o_info.debug){o_logwarn("stylesheet: when removeing: num of stylesheets found was not 1:"+d)}}else{var c=jQuery("#"+m);if(c){c.href="";c.remove();c=null;return}else{if(o_info.debug){o_logwarn("no link with id found to remove, id:"+m+", url "+a)}}}}catch(j){if(o_info.debug){o_logerr("BLoader::unLoadCSS: Error when unloading CSS from URL::"+a)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::unLoadCSS: Error when unloading CSS from URL::"+a,"functions.js::BLoader::loadCSS")}}}};var BFormatter={formatLatexFormulas:function(b){try{if(window.jsMath){if(jsMath.loaded&&jsMath.tex2math&&jsMath.tex2math.loaded){jsMath.Process()}else{jsMath.Autoload.LoadJsMath();setTimeout(function(){BFormatter.formatLatexFormulas(b)},100)}}}catch(a){if(window.console){console.log("error in BFormatter.formatLatexFormulas: ",a)}}}};function o_init(){try{o_getMainWin().o_afterserver()}catch(a){if(o_info.debug){o_log("error in o_init: "+showerror(a))}}}function o_initEmPxFactor(){o_info.emPxFactor=jQuery("#o_width_1em").width();if(o_info.emPxFactor==0||o_info.emPxFactor=="undefined"){o_info.emPxFactor=12;if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Could not read with of element b_width_1em, set o_info.emPxFactor to 12","functions.js")}}}function o_getMainWin(){try{if(window.OPOL){return window}else{if(window.opener&&window.opener.OPOL){return window.opener}}}catch(a){if(o_info.debug){o_logerr('Exception while getting main window. rror::"'+showerror(a))}if(window.console){console.log('Exception while getting main window. rror::"'+showerror(a),"functions.js");console.log(a)}}throw"Can not find main OpenOLAT window"}function o_beforeserver(){o_info.linkbusy=true;showAjaxBusy();if(window.suppressOlatOnUnloadOnce){window.suppressOlatOnUnloadOnce=false}else{if(window.olatonunload){olatonunload()}}}function o_afterserver(){o2c=0;o_info.linkbusy=false;removeAjaxBusy()}function o2cl(){if(o_info.linkbusy){return false}else{var a=(o2c==0||confirm(o_info.dirty_form));if(a){o_beforeserver()}return a}}function o2cl_noDirtyCheck(){if(o_info.linkbusy){return false}else{o_beforeserver();return true}}function o2cl_secure(){try{if(o2cl()){return true}else{return false}}catch(a){return false}}function o3cl(d){if(o_info.linkbusy){return false}else{var b=o3c1.indexOf(d)>-1;var a=(b&&o3c1.length>1)||o3c1.length>0;var c=(!a||confirm(o_info.dirty_form));if(c){o_beforeserver()}return c}}function o_onc(a){var b=a.responseText;BLoader.executeGlobalJS("o_info.last_o_onc="+b+";","o_onc");o_ainvoke(o_info.last_o_onc,false)}function o_allowNextClick(){o_info.linkbusy=false;removeAjaxBusy()}function removeBusyAfterDownload(c,b,a){o2c=0;o_afterserver()}Array.prototype.search=function(c,d){var a=this.length;for(var b=0;b<a;b++){if(this[b].constructor==Array){if(this[b].search(c,d)){return true;break}}else{if(d){if(this[b].indexOf(c)!=-1){return true;break}}else{if(this[b]==c){return true;break}}}}return false};if(!Function.prototype.curry){Function.prototype.curry=function(){if(arguments.length<1){return this}var a=this;var b=Array.prototype.slice.call(arguments);return function(){return a.apply(this,b.concat(Array.prototype.slice.call(arguments)))}}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(c){if(this==null){throw new TypeError()}var d=Object(this);var a=d.length>>>0;if(a===0){return -1}var e=0;if(arguments.length>1){e=Number(arguments[1]);if(e!=e){e=0}else{if(e!=0&&e!=Infinity&&e!=-Infinity){e=(e>0||-1)*Math.floor(Math.abs(e))}}}if(e>=a){return -1}var b=e>=0?e:Math.max(a-Math.abs(e),0);for(;b<a;b++){if(b in d&&d[b]===c){return b}}return -1}}var b_onDomReplacementFinished_callbacks=new Array();function b_AddOnDomReplacementFinishedCallback(a){var b=jQuery(document).ooLog().isDebugEnabled();if(b){jQuery(document).ooLog("debug","callback stack size: "+b_onDomReplacementFinished_callbacks.length,"functions.js ADD")}if(b&&b_onDomReplacementFinished_callbacks.toSource){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js ADD")}b_onDomReplacementFinished_callbacks.push(a);if(b){jQuery(document).ooLog("debug","push to callback stack, func: "+a,"functions.js ADD")}}var b_changedDomEl=new Array();function b_AddOnDomReplacementFinishedUniqueCallback(a){if(a.constructor==Array){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","add: its an ARRAY! ","functions.js ADD")}if(b_onDomReplacementFinished_callbacks.search(a[0])){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","push to callback stack, already there!!: "+a[0],"functions.js ADD")}return}}b_AddOnDomReplacementFinishedCallback(a)}var o_debug_trid=0;function o_ainvoke(N){if(N==undefined){return}o_info.inainvoke=true;var I=N.cmdcnt;if(I>0){jQuery(document).trigger("oo.dom.replacement.before");b_changedDomEl=new Array();if(o_info.debug){o_debug_trid++}var y=N.cmds;for(var T=0;T<I;T++){var J=y[T];var A=J.cmd;var R=J.cda;var U=J.w;var c=this.window;var K;if(c){switch(A){case 1:var M=R.e;BLoader.executeGlobalJS(M,"o_ainvoker::jsexec");if(o_info.debug){o_log("c1: execute jscode: "+M)}case 2:var u=R.cc;var F=R.cps;for(var Q=0;Q<u;Q++){var m=F[Q];var h=m.cid;var P=m.cidvis;var H=m.cw;var x=m.hfrag;var O=m.jsol;var g=m.hdr;if(o_info.debug){o_log("c2: redraw: "+m.cname+" ("+h+") "+m.hfragsize+" bytes, listener(s): "+m.clisteners)}var W=g+"\n\n"+x;var C="";var S=false;var E="o_c"+h;var B=jQuery("#"+E);if(B==null||B.length==0){E="o_fi"+h;B=jQuery("#"+E);S=true}if(B!=null){var w=jQuery("div.o_richtext_mce textarea",B);for(var L=0;L<w.length;L++){try{var a=jQuery(w.get(L)).attr("id");if(typeof top.tinymce!=undefined){top.tinymce.remove("#"+a)}}catch(Z){if(window.console){console.log(Z)}}}if(P){B.css("display","")}else{B.css("display","none")}if(S||!H){B.replaceWith(W)}else{try{B.empty().html(W);if(W.length>0&&B.get(0).innerHTML==""){B.get(0).innerHTML=W}}catch(Z){if(window.console){console.log(Z)}if(window.console){console.log("Fragment",W)}}b_changedDomEl.push(E)}B=null;if(C!=""){C.each(function(e){BLoader.executeGlobalJS(e,"o_ainvoker::inscripts")})}if(O!=""){BLoader.executeGlobalJS(O,"o_ainvoker::jsol")}}}break;case 3:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 5:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 6:c.o2c=0;c.o_afterserver();break;case 7:var o=c.document.location;var z=o.protocol+"//"+o.hostname;if(o.port!=""){z+=":"+o.port}var v=R.cssrm;for(Q=0;Q<v.length;Q++){var D=v[Q];var G=D.id;var f=z+D.url;BLoader.unLoadCSS(f,G);if(o_info.debug){o_log("c7: rm css: id:"+G+" ,url:'"+f+"'")}}var V=R.cssadd;for(k=0;k<V.length;k++){var D=V[k];var G=D.id;var f=z+D.url;var n=D.pt;BLoader.loadCSS(f,G,n);if(o_info.debug){o_log("c7: add css: id:"+G+" ,url:'"+f+"'")}}var p=R.jsadd;for(l=0;l<p.length;l++){var D=p[l];var Y=D.before;if(jQuery.type(Y)==="string"){BLoader.executeGlobalJS(Y,"o_ainvoker::preJsAdd")}var f=D.url;var q=D.enc;if(jQuery.type(f)==="string"){BLoader.loadJS(f,q,true)}if(o_info.debug){o_log("c7: add js: "+f)}}break;default:if(o_info.debug){o_log("?: unknown command "+A)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), ?: unknown command "+A,"functions.js")}break}}else{if(o_info.debug){o_log("could not find window??")}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), could not find window??","functions.js")}}}var b=b_onDomReplacementFinished_callbacks.length;if(b_onDomReplacementFinished_callbacks.toSource&&jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js")}for(mycounter=0;b>mycounter;mycounter++){if(mycounter>50){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stopped executing DOM replacement callback functions - to many functions::"+b_onDomReplacementFinished_callbacks.length,"functions.js")}break}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize before shift: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}var s=b_onDomReplacementFinished_callbacks.shift();if(typeof s.length==="number"){if(s[0]=="glosshighlighter"){var d=s[1];if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","arr fct: "+d,"functions.js")}s=d}}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Executing DOM replacement callback function #"+mycounter+" with timeout funct::"+s,"functions.js")}s();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize after timeout: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}}jQuery(document).trigger("oo.dom.replacement.after")}o_info.inainvoke=false}function clearAfterAjaxIframeCall(){if(o_info.linkbusy){o_afterserver();showMessageBox("info",o_info.i18n_noresponse_title,o_info.i18n_noresponse,undefined)}}function showAjaxBusy(){setTimeout(function(){if(o_info.linkbusy){try{if(jQuery("#o_ajax_busy_backdrop").length==0){jQuery("#o_body").addClass("o_ajax_busy");jQuery("#o_ajax_busy").modal({show:true,backdrop:"static",keyboard:"false"});jQuery("#o_ajax_busy").after('<div id="o_ajax_busy_backdrop" class="modal-backdrop in"></div>');jQuery("#o_ajax_busy>.modal-backdrop").remove();jQuery("#o_ajax_busy_backdrop").css({"z-index":1200})}}catch(a){if(window.console){console.log(a)}}}},700)}function removeAjaxBusy(){try{jQuery("#o_body").removeClass("o_ajax_busy");jQuery("#o_ajax_busy_backdrop").remove();jQuery("#o_ajax_busy").modal("hide")}catch(a){if(window.console){console.log(a)}}}function setFormDirty(c){o2c=1;var a=document.getElementById(c);if(a!=null){var b=a.olat_fosm_0;if(b==null){b=a.olat_fosm}if(b){b.className="btn o_button_dirty"}}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in setFormDirty, myForm was null for formId="+c,"functions.js")}}}function contextHelpWindow(a){helpWindow=window.open(a,"HelpWindow","height=760, width=940, left=0, top=0, location=no, menubar=no, resizable=yes, scrollbars=yes, toolbar=no");helpWindow.focus()}function o_openPopUp(b,d,c,a,e){attributes="height="+a+", width="+c+", resizable=yes, scrollbars=yes, left=100, top=100, ";if(e){attributes+="location=yes, menubar=yes, status=yes, toolbar=yes"}else{attributes+="location=no, menubar=no, status=no, toolbar=no"}var f=window.open(b,d,attributes);f.focus();if(o_info.linkbusy){o_afterserver()}}function b_handleFileUploadFormChange(e,b,d){var f=e.value;slashPos=f.lastIndexOf("/");if(slashPos!=-1){f=f.substring(slashPos+1)}slashPos=f.lastIndexOf("\\");if(slashPos!=-1){f=f.substring(slashPos+1)}b.value=f;if(d){d.className="o_button_dirty"}var c=e.form.elements;for(i=0;i<c.length;i++){var a=c[i];if(a.name==b.name&&i+1<c.length){c[i+1].focus()}}}function gotonode(a){try{if(typeof o_activateCourseNode!="undefined"){o_activateCourseNode(a)}else{if(opener&&typeof opener.o_activateCourseNode!="undefined"){opener.o_activateCourseNode(a)}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode(), could not find main window","functions.js")}}}}catch(b){alert("Goto node error:"+b);if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode()::"+b.message,"functions.js")}}}function o_openUriInMainWindow(b){try{var a=o_getMainWin();a.focus();a.location.replace(b)}catch(c){showMessageBox("error","Error","Can not find main OpenOLAT window to open URL.")}}function o_viewportHeight(){var a=jQuery(document).height();if(a>0){return a}else{return 600}}OPOL.getMainColumnsMaxHeight=function(){var j=0,f=0,a=0,c=0,h=0,b,g=jQuery("#o_main_left_content"),e=jQuery("#o_main_right_content"),d=jQuery("#o_main_center_content");if(g!="undefined"&&g!=null){j=g.outerHeight(true)}if(e!="undefined"&&e!=null){f=e.outerHeight(true)}if(d!="undefined"&&d!=null){a=d.outerHeight(true)}c=(j>f?j:f);c=(c>a?c:a);if(c>0){return c}b=jQuery("#o_main");if(b!="undefined"&&b!=null){h=b.height()}if(b>0){return b}return o_viewportHeight()};OPOL.adjustHeight=function(){try{var a=0;col1=jQuery("#o_main_left_content").outerHeight(true);col2=jQuery("#o_main_right_content").outerHeight(true);col3=jQuery("#o_main_center_content").outerHeight(true);a=Math.max(col1,col2,col3);if(col1!=null){jQuery("#o_main_left").css({"min-height":a+"px"})}if(col2!=null){jQuery("#o_main_right").css({"min-height":a+"px"})}if(col3!=null){jQuery("#o_main_center").css({"min-height":a+"px"})}}catch(b){if(window.console){console.log(b)}}};jQuery(window).resize(function(){clearTimeout(o_info.resizeId);o_info.resizeId=setTimeout(function(){jQuery(document).trigger("oo.window.resize.after")},500)});jQuery(document).on("oo.window.resize.after",OPOL.adjustHeight);jQuery(document).on("oo.dom.replacement.after",OPOL.adjustHeight);jQuery().ready(OPOL.adjustHeight);function o_scrollToElement(a){try{jQuery("html, body").animate({scrollTop:jQuery(a).offset().top},333)}catch(b){}}function o_popover(c,b,a){if(typeof(a)==="undefined"){a="bottom"}jQuery("#"+c).popover({placement:a,html:true,trigger:"click",container:"body",content:function(){return jQuery("#"+b).clone().html()}}).on("shown.bs.popover",function(){var d=function(f){jQuery("#"+c).popover("hide");jQuery("body").unbind("click",d)};setTimeout(function(){jQuery("body").on("click",d)},5)})}function o_popoverWithTitle(d,c,b,a){if(typeof(a)==="undefined"){a="bottom"}return jQuery("#"+d).popover({placement:a,html:true,title:b,trigger:"click",container:"body",content:function(){return jQuery("#"+c).clone().html()}}).on("shown.bs.popover",function(){var e=function(f){jQuery("#"+d).popover("destroy");jQuery("body").unbind("click",e)};setTimeout(function(){jQuery("body").on("click",e)},5)})}function o_shareLinkPopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:c}).on("shown.bs.popover",function(){var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)});a.attr("title",a.attr("data-original-title"))}function o_QRCodePopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:'<div id="'+d+'_pop" class="o_qrcode"></div>'}).on("shown.bs.popover",function(){o_info.qr=o_QRCode(d+"_pop",(jQuery.isFunction(c)?c():c));var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)}).on("hidden.bs.popover",function(){try{o_info.qr.clear();delete o_info.qr}catch(f){}});a.attr("title",a.attr("data-original-title"))}function o_QRCode(c,b){try{BLoader.loadJS(o_info.o_baseURI+"/js/jquery/qrcodejs/qrcode.min.js","utf8",true);return new QRCode(document.getElementById(c),b)}catch(a){return null}}function b_resizeIframeToMainMaxHeight(f){var d=jQuery("#"+f);if(d!="undefined"&&d!=null){var c=OPOL.getMainColumnsMaxHeight()-110;var b=o_viewportHeight()-100;b=b-d.offset().top;var e=jQuery("#b_footer");if(e!="undefined"&&e!=null){b=b-e.outerHeight(true)}var a=(b>c?b:c);d.height(a)}}var o_debu_oldcn,o_debu_oldtt;function o_debu_show(b,a){if(o_debu_oldcn){o_debu_hide(o_debu_oldcn,o_debu_oldtt)}jQuery(b).addClass("o_dev_m");jQuery(a).show();o_debu_oldtt=a;o_debu_oldcn=b}function o_debu_hide(b,a){jQuery(a).hide();jQuery(b).removeClass("o_dev_m")}function o_dbg_mark(a){var b=jQuery("#"+a);if(b){b.css("background-color","#FCFCB8");b.css("border","3px solid #00F")}}function o_dbg_unmark(a){var b=jQuery("#"+a);if(b){b.css("border","");b.css("background-color","")}}function o_clearConsole(){o_log_all="";o_log(null)}var o_log_all="";function o_log(b){if(b){o_log_all="\n"+o_debug_trid+"> "+b+o_log_all;o_log_all=o_log_all.substr(0,4000)}var a=jQuery("#o_debug_cons");if(a){if(o_log_all.length==4000){o_log_all=o_log_all+"\n... (stripped: to long)... "}a.value=o_log_all}if(!jQuery.type(window.console)==="undefined"){window.console.log(b)}}function o_logerr(a){o_log("ERROR:"+a)}function o_logwarn(a){o_log("WARN:"+a)}function showerror(c){var a="";for(var b in c){a+=b+": "+c[b]+"\n"}return"error detail:\n"+a}function o_ffEvent(e,d,c,h,j){var f,g,b,a;f=document.getElementById(d);g=f.value;f.value=c;b=document.getElementById(h);a=b.value;b.value=j;if(document.forms[e].onsubmit()){document.forms[e].submit()}f.value=g;b.value=a}function o_ffXHREvent(f,e,a,h,j){var c=new Object();c.dispatchuri=a;c.dispatchevent=j;if(arguments.length>5){var g=arguments.length;for(var d=5;d<g;d=d+2){if(g>d+1){c[arguments[d]]=arguments[d+1]}}}var b=jQuery("#"+f).attr("action");jQuery.ajax(b,{type:"GET",data:c,cache:false,dataType:"json",success:function(n,o,m){o_ainvoke(n)},error:function(m,o,n){if(window.console){console.log("Error status",o)}}})}function setFlexiFormDirtyByListener(a){setFlexiFormDirty(a.data.formId)}function setFlexiFormDirty(b){var a=o3c.indexOf(b)>-1;if(!a){o3c.push(b)}jQuery("#"+b).each(function(){var c=jQuery(this).data("FlexiSubmit");if(c!=null){jQuery("#"+c).addClass("btn o_button_dirty");o2c=1}})}function o_ffRegisterSubmit(b,a){jQuery("#"+b).data("FlexiSubmit",a)}function showInfoBox(g,d){var c=Math.floor(Math.random()*65536).toString(16);var f='<div id="'+c+'" class="o_alert_info "><div class="alert alert-info clearfix o_sel_info_message"><i class="o_icon o_icon_close"></i><h3><i class="o_icon o_icon_info"></i> '+g+"</h3><p>"+d+"</p></div></div>";var a=jQuery("#o_messages").prepend(f);var e=(d.length>150)?8000:((d.length>70)?6000:4000);var b=function(){jQuery("#"+c).transition({top:"-100%"},333,function(){jQuery("#"+c).remove()})};jQuery("#"+c).show().transition({top:0},333);jQuery("#"+c).click(function(h){b()});o_scrollToElement("#o_top");g=null;d=null;a=null;e=null;setTimeout(function(){try{b()}catch(h){}},8000)}function showMessageBox(b,f,d,a){if(b=="info"){showInfoBox(f,d);return null}else{var c='<div id="myFunctionalModal" class="modal fade" role="dialog"><div class="modal-dialog"><div class="modal-content">';c+='<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>';c+='<h4 class="modal-title">'+f+"</h4></div>";c+='<div class="modal-body alert ';if("warn"==b){c+="alert-warning"}else{if("error"==b){c+="alert-danger"}else{c+="alert-info"}}c+='"><p>'+d+"</p></div></div></div></div>";jQuery("#myFunctionalModal").remove();jQuery("body").append(c);var e=jQuery("#myFunctionalModal").modal("show").on("hidden.bs.modal",function(g){jQuery("#myFunctionalModal").remove()});o_scrollToElement("#o_top");return e}}function tableFormInjectCommandAndSubmit(a,b,c){document.forms[a].elements.cmd.value=b;document.forms[a].elements.param.value=c;document.forms[a].submit()}function o_table_toggleCheck(d,c){var a=document.forms[d].elements.tb_ms;len=a.length;if(typeof(len)=="undefined"){a.checked=c}else{var b;for(b=0;b<len;b++){a[b].checked=c}}}function onTreeStartDrag(a,b){jQuery(a.target).addClass("o_dnd_proxy")}function onTreeStopDrag(a,b){jQuery(a.target).removeClass("o_dnd_proxy")}function onTreeDrop(g,h){var a=jQuery(h.draggable[0]);var f=jQuery(this);f.css({position:"",width:""});var c=f.droppable("option","endUrl");if(c.lastIndexOf("/")==(c.length-1)){c=c.substring(0,c.length-1)}var e=a.attr("id");var b=e.substring(2,e.length);c+="%3Atnidle%3A"+b;var d=f.attr("id");if(d.indexOf("ds")==0){c+="%3Asne%3Ayes"}else{if(d.indexOf("dt")==0){c+="%3Asne%3Aend"}}jQuery(".ui-droppable").each(function(j,m){jQuery(m).droppable("disable")});frames.oaa0.location.href=c+"/"}function treeAcceptDrop(a){return true}function treeAcceptDrop_notWithChildren(a){var c=false;var b=jQuery(a);var e=b.attr("id");if(e!=undefined&&(e.indexOf("dd")==0||e.indexOf("ds")==0||e.indexOf("dt")==0||e.indexOf("da")==0||e.indexOf("row")==0)){var g=jQuery(this);var j=g.attr("id");var d=e.substring(2,e.length);var f=j.substring(2,j.length);if(d!=f){var h=jQuery("#dd"+d).parents("li");if(h.length>0&&jQuery(h.get(0)).find("#dd"+f).length==0){c=true}}}return c}function treeAcceptDrop_portfolio(b){var d=false;var c=jQuery(b);var f=c.attr("id");if(treeNode_isDragNode(f)){var h=jQuery(this);var n=h.attr("id");var e=f.substring(2,f.length);var g=n.substring(2,n.length);var m=f.indexOf("ds")==0||f.indexOf("dt")==0;if(e!=g){var j=treeNode_portfolioType(c);var a=treeNode_portfolioType(h);if(j=="artefact"){if(a=="page"||a=="struct"||a=="artefact"){d=true}}else{if(j=="struct"){if(a=="page"||a=="struct"){d=true}}else{if(j=="page"){if(a=="map"||a=="page"){d=true}}}}}}return d}function treeNode_portfolioType(e){var c=jQuery(e.get(0));var d=treeNode_portfolioTypes(c);if(d==null){var a=c.parent("a");if(a.length>0){d=treeNode_portfolioTypes(jQuery(a.get(0)))}else{if(c.attr("id").indexOf("ds")==0){var b=c.prev("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}else{if(c.attr("id").indexOf("dt")==0){var b=c.next("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}}}}return d}function treeNode_portfolioTypes(a){if(a.find===undefined){return null}else{if(a.find(".o_ep_icon_struct").length>0||a.hasClass("o_ep_icon_struct")){return"struct"}else{if(a.find(".o_ep_icon_page").length>0||a.hasClass("o_ep_icon_page")){return"page"}else{if(a.find(".o_ep_icon_map").length>0||a.hasClass("o_ep_icon_map")){return"map"}else{if(a.find(".o_ep_artefact").length>0||a.hasClass("o_ep_artefact")){return"artefact"}}}}}return null}function treeNode_isDragNode(a){if(a!=undefined&&(a.indexOf("dd")==0||a.indexOf("ds")==0||a.indexOf("dt")==0||a.indexOf("da")==0||a.indexOf("row")==0)){return true}return false}function o_choice_toggleCheck(c,b){var d=document.forms[c].elements;len=d.length;if(typeof(len)=="undefined"){d.checked=b}else{var a;for(a=0;a<len;a++){if(d[a].type=="checkbox"&&d[a].getAttribute("class")=="o_checkbox"){d[a].checked=b}}}}function b_briefcase_isChecked(c,e){var b;var a=document.getElementById(c);var d=0;for(b=0;a.elements[b];b++){if(a.elements[b].type=="checkbox"&&a.elements[b].name=="paths"&&a.elements[b].checked){d++}}if(d<1){alert(e);return false}return true}function b_briefcase_toggleCheck(d,c){var a=document.getElementById(d);len=a.elements.length;var b;for(b=0;b<len;b++){if(a.elements[b].name=="paths"){a.elements[b].checked=c}}}function o_doPrint(){var d=jQuery("div.o_iframedisplay iframe");if(d.length>0){try{var a=d[0];frames[a.name].focus();frames[a.name].print();return}catch(c){for(i=0;frames.length>i;i++){a=frames[i];if(a.name=="oaa0"){continue}var b=document.getElementsByName(a.name)[0];if(b&&b.getAttribute("class")=="ext-shim"){continue}if(a.name!=""){try{frames[a.name].focus();frames[a.name].print()}catch(c){window.print()}return}}window.print()}}else{window.print()}}function b_attach_i18n_inline_editing(){jQuery("span.o_translation_i18nitem").hover(function(){jQuery(this.firstChild).show();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Entered i18nitem::"+this.firstChild,"functions.js:b_attach_i18n_inline_editing()")}},function(){jQuery("a.o_translation_i18nitem_launcher").hide();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Leaving i18nitem::"+this,"functions.js:b_attach_i18n_inline_editing()")}});jQuery("a.o_translation_i18nitem_launcher").hover(function(){var a=jQuery(this).parent("span.o_translation_i18nitem");a.effect("highlight")});b_AddOnDomReplacementFinishedCallback(b_attach_i18n_inline_editing)}function b_hideExtMessageBox(){}var BDebugger={_lastDOMCount:0,_lastObjCount:0,_knownGlobalOLATObjects:["o_afterserver","o_onc","o_getMainWin","o_ainvoke","o_info","o_beforeserver","o_ffEvent","o_openPopUp","o_debu_show","o_logwarn","o_dbg_unmark","o_ffRegisterSubmit","o_clearConsole","o_init","o_log","o_allowNextClick","o_dbg_mark","o_debu_hide","o_logerr","o_debu_oldcn","o_debu_oldtt","o_openUriInMainWindow","o_debug_trid","o_log_all"],_countDOMElements:function(){return document.getElementsByTagName("*").length},_countGlobalObjects:function(){var a=0;for(prop in window){a++}return a},logDOMCount:function(){var b=BDebugger;var a=b._countDOMElements();var c=a-b._lastDOMCount;console.log((c>0?"+":"")+c+" \t"+a+" \tDOM element count after DOM replacement");b._lastDOMCount=a;a=null},logGlobalObjCount:function(){var b=BDebugger;var a=b._countGlobalObjects();var c=a-b._lastObjCount;console.log((c>0?"+":"")+c+" \t"+a+" \tGlobal object count after DOM replacement");b._lastObjCount=a;a=null},logGlobalOLATObjects:function(){var b=BDebugger;var a=new Array();for(prop in window){if(prop.indexOf("o_")==0&&b._knownGlobalOLATObjects.indexOf(prop)==-1){a.push(prop)}}if(a.length>0){console.log(a.length+" global OLAT objects found:");a.each(function(c){console.log("\t"+typeof window[c]+" \t"+c)})}},logManagedOLATObjects:function(){var a=BDebugger;if(o_info.objectMap.length>0){console.log(o_info.objectMap.length+" managed OLAT objects found:");o_info.objectMap.eachKey(function(b){var c=o_info.objectMap.get(b);console.log("\t"+typeof c+" \t"+b);return true})}}};/*!
+jQuery.periodic=function(l,h){if(jQuery.isFunction(l)){h=l;l={}}var c=jQuery.extend({},jQuery.periodic.defaults,{ajax_complete:j,increment:g,reset:f,cancel:i},l);c.cur_period=c.period;c.tid=false;var e="";b();return c;function b(){i();c.tid=setTimeout(function(){h.call(c);g();if(c.tid){b()}},c.cur_period)}function j(n,m){if(m==="success"&&e!==n.responseText){e=n.responseText;f()}}function g(){c.cur_period*=c.decay;if(c.cur_period<c.period){f()}else{if(c.cur_period>c.max_period){c.cur_period=c.max_period;if(c.on_max!==undefined){c.on_max.call(c)}}}}function f(){c.cur_period=c.period;b()}function i(){clearTimeout(c.tid);c.tid=null}function k(){}function a(){}function d(){}};jQuery.periodic.defaults={period:4000,max_period:1800000,decay:1.5,on_max:undefined};var Hashtable=(function(){var p="function";var n=(typeof Array.prototype.splice==p)?function(s,r){s.splice(r,1)}:function(u,t){var s,v,r;if(t===u.length-1){u.length=t}else{s=u.slice(t+1);u.length=t;for(v=0,r=s.length;v<r;++v){u[t+v]=s[v]}}};function a(t){var r;if(typeof t=="string"){return t}else{if(typeof t.hashCode==p){r=t.hashCode();return(typeof r=="string")?r:a(r)}else{if(typeof t.toString==p){return t.toString()}else{try{return String(t)}catch(s){return Object.prototype.toString.call(t)}}}}}function g(r,s){return r.equals(s)}function e(r,s){return(typeof s.equals==p)?s.equals(r):(r===s)}function c(r){return function(s){if(s===null){throw new Error("null is not a valid "+r)}else{if(typeof s=="undefined"){throw new Error(r+" must not be undefined")}}}}var q=c("key"),l=c("value");function d(u,s,t,r){this[0]=u;this.entries=[];this.addEntry(s,t);if(r!==null){this.getEqualityFunction=function(){return r}}}var h=0,j=1,f=2;function o(r){return function(t){var s=this.entries.length,v,u=this.getEqualityFunction(t);while(s--){v=this.entries[s];if(u(t,v[0])){switch(r){case h:return true;case j:return v;case f:return[s,v[1]]}}}return false}}function k(r){return function(u){var v=u.length;for(var t=0,s=this.entries.length;t<s;++t){u[v+t]=this.entries[t][r]}}}d.prototype={getEqualityFunction:function(r){return(typeof r.equals==p)?g:e},getEntryForKey:o(j),getEntryAndIndexForKey:o(f),removeEntryForKey:function(s){var r=this.getEntryAndIndexForKey(s);if(r){n(this.entries,r[0]);return r[1]}return null},addEntry:function(r,s){this.entries[this.entries.length]=[r,s]},keys:k(0),values:k(1),getEntries:function(s){var u=s.length;for(var t=0,r=this.entries.length;t<r;++t){s[u+t]=this.entries[t].slice(0)}},containsKey:o(h),containsValue:function(s){var r=this.entries.length;while(r--){if(s===this.entries[r][1]){return true}}return false}};function m(s,t){var r=s.length,u;while(r--){u=s[r];if(t===u[0]){return r}}return null}function i(r,s){var t=r[s];return(t&&(t instanceof d))?t:null}function b(t,r){var w=this;var v=[];var u={};var x=(typeof t==p)?t:a;var s=(typeof r==p)?r:null;this.put=function(B,C){q(B);l(C);var D=x(B),E,A,z=null;E=i(u,D);if(E){A=E.getEntryForKey(B);if(A){z=A[1];A[1]=C}else{E.addEntry(B,C)}}else{E=new d(D,B,C,s);v[v.length]=E;u[D]=E}return z};this.get=function(A){q(A);var B=x(A);var C=i(u,B);if(C){var z=C.getEntryForKey(A);if(z){return z[1]}}return null};this.containsKey=function(A){q(A);var z=x(A);var B=i(u,z);return B?B.containsKey(A):false};this.containsValue=function(A){l(A);var z=v.length;while(z--){if(v[z].containsValue(A)){return true}}return false};this.clear=function(){v.length=0;u={}};this.isEmpty=function(){return !v.length};var y=function(z){return function(){var A=[],B=v.length;while(B--){v[B][z](A)}return A}};this.keys=y("keys");this.values=y("values");this.entries=y("getEntries");this.remove=function(B){q(B);var C=x(B),z,A=null;var D=i(u,C);if(D){A=D.removeEntryForKey(B);if(A!==null){if(!D.entries.length){z=m(v,C);n(v,z);delete u[C]}}}return A};this.size=function(){var A=0,z=v.length;while(z--){A+=v[z].entries.length}return A};this.each=function(C){var z=w.entries(),A=z.length,B;while(A--){B=z[A];C(B[0],B[1])}};this.putAll=function(H,C){var B=H.entries();var E,F,D,z,A=B.length;var G=(typeof C==p);while(A--){E=B[A];F=E[0];D=E[1];if(G&&(z=w.get(F))){D=C(F,z,D)}w.put(F,D)}};this.clone=function(){var z=new b(t,r);z.putAll(w);return z}}return b})();(function(b){b.fn.ooLog=function(f,d,e){var c=null;b(this).each(function(){c=b(this).data("_ooLog");if(c==undefined){c=new a();b(this).data("_ooLog",c)}});if(f==undefined){return c}else{if(typeof f==="string"){if(c){c.log(f,d,e)}}}};function a(){return this}a.prototype={isDebugEnabled:function(){return o_info.JSTracingLogDebugEnabled},log:function(e,c,d){if(!this.isDebugEnabled()){return}jQuery.post(o_info.JSTracingUri,{level:e,logMsg:c,jsFile:d})}}})(jQuery);(function(b){b.fn.ooTranslator=function(){var d=null;b(document).each(function(){d=b(document).data("_ooTranslator");if(d==undefined){d=new a();b(document).data("_ooTranslator",d)}});return d};function a(){return this}a.prototype={mapperUrl:null,translators:null,initialize:function(d){this.mapperUrl=d;this.translators=new Object()},getTranslator:function(d,f){if(this.translators[d]==null){this.translators[d]=new Object()}if(this.translators[d][f]==null){var e=this.mapperUrl+"/"+d+"/"+f+"/translations.js";jQuery.ajax(e,{async:false,dataType:"json",success:function(g,i,h){jQuery(document).ooTranslator()._createTranslator(g,d,f)}})}return this.translators[d][f]},_createTranslator:function(e,d,f){this.translators[d][f]=new c().initialize(e,d,f)}};function c(){return this}c.prototype={localizationData:null,bundle:null,locale:null,initialize:function(f,d,e){this.bundle=e;this.locale=d;this.localizationData=f;return this},translate:function(d){if(this.localizationData[d]){return this.localizationData[d]}else{return this.bundle+":"+d}}}})(jQuery);+function(b){var a=function(){this.addExtraElements();this.state={busy:false,brandW:0,sitesW:0,sitesDirty:false,sites:{collapsed:this.isSitesCollapsed(),extended:this.isSitesExtended},tabsW:0,tabsDirty:false,tabs:{collapsed:this.isTabsCollapsed(),extended:this.isTabsExtended()},toolsW:0,toolsDirty:false,tools:{collapsed:this.isToolsCollapsed(),extended:this.isToolsExtended()},offCanvasWidth:0,moreW:0};var c=b("#o_offcanvas_right").css("width");if(c){this.state.offCanvasWidth=parseInt(c.replace(/[^\d.]/g,""));this.initListners();this.calculateWidth();this.optimize()}};a.prototype.initListners=function(){b(window).resize(b.proxy(this.onResizeCallback,this));b(document).on("oo.nav.sites.modified",b.proxy(function(){this.state.sitesDirty=true},this));b(document).on("oo.nav.tabs.modified",b.proxy(function(){this.state.tabsDirty=true},this));b(document).on("oo.nav.tools.modified",b.proxy(function(){this.state.toolsDirty=true},this));b(document).on("oo.dom.replacement.after",b.proxy(this.onDOMreplacementCallback,this));b(window).on("orientationchange",b.proxy(this.hideRight,this));b("#o_navbar_right-toggle").on("click",b.proxy(this.toggleRight,this));b("#o_offcanvas_right .o_offcanvas_close").on("click",b.proxy(this.hideRight,this));b("#o_navbar_more").on("shown.bs.dropdown",this.onDropdownShown);b("#o_navbar_more").on("hidden.bs.dropdown",this.onDropdownHidden)};a.prototype.onResizeCallback=function(){if(!this.state.busy){this.state.busy=true;this.calculateWidth();this.optimize();this.state.busy=false}};a.prototype.onDOMreplacementCallback=function(){if(!this.state.busy&&(this.state.sitesDirty||this.state.tabsDirty||this.state.toolsDirty)){this.state.busy=true;this.cleanupMoreDropdown();this.calculateWidth();this.optimize();this.state.sitesDirty=false;this.state.tabsDirty=false;this.state.toolsDirty=false;this.state.busy=false}};a.prototype.onDropdownShown=function(c){var f=b("#o_navbar_more .dropdown-menu");if(f.length){var d=f.offset().left;if(d<0){f.removeClass("dropdown-menu-right")}}};a.prototype.onDropdownHidden=function(c){var d=b("#o_navbar_more .dropdown-menu");d.addClass("dropdown-menu-right")};a.prototype.calculateWidth=function(){var c=b("#o_navbar_container .o_navbar-collapse");this.state.navbarW=c.innerWidth();this.state.brandW=b(".o_navbar-brand").outerWidth(true);this.state.sitesW=this.getSites().outerWidth(true);this.state.tabsW=this.getTabs().outerWidth(true);this.state.toolsW=this.getTools().outerWidth(false);this.state.moreW=b("#o_navbar_more:visible").outerWidth(true)};a.prototype.getOverflow=function(c){var d=this.state.navbarW;d-=this.state.sitesW;d-=this.state.tabsW;d-=this.state.toolsW;d-=this.state.brandW;d-=this.state.moreW;d-=25;return -d};a.prototype.optimize=function(h){var c=this.getOverflow();var k=this.getSites();var l=this.getTabs();var g=this.getTools();var n=this.getMoreDropdown();var f=this.getOffcanvasRight();this.updateState();while(c>0&&(!this.state.tabs.collapsed||!this.state.sites.collapsed||!this.state.tools.collapsed)){if(!this.state.tabs.collapsed){this.collapse(l,n,"li","o_dropdown_tab")}else{if(!this.state.sites.collapsed){this.collapse(k,n,"li","o_dropdown_site")}else{if(!this.state.tools.collapsed){this.collapse(g,f,".o_navbar_tool:not(#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu)","o_tool_right")}}}this.calculateWidth();c=this.getOverflow();this.updateState()}while(c<0&&(!this.state.tabs.extended||!this.state.sites.extended||!this.state.tools.extended)){if(!this.state.tools.extended){var m=this.extend(f,g.children("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").first(),".o_tool_right","o_tool_right",true);if(!m){break}}if(!this.state.sites.extended){var j=this.extend(n,k,"li","o_dropdown_site");if(!j){break}}else{if(!this.state.tabs.extended){var d=this.extend(n,l,"li","o_dropdown_tab");if(!d){break}}}this.calculateWidth();c=this.getOverflow();this.updateState()}if(this.state.sites.extended&&this.state.tabs.extended){var i=b("#o_navbar_more");i.css("display","none")}this.checkToolsOrder()};a.prototype.updateState=function(){this.state.sites.collapsed=this.isSitesCollapsed();this.state.sites.extended=this.isSitesExtended();this.state.tabs.collapsed=this.isTabsCollapsed();this.state.tabs.extended=this.isTabsExtended();this.state.tools.collapsed=this.isToolsCollapsed();this.state.tools.extended=this.isToolsExtended()};a.prototype.collapse=function(g,d,c,f){var e=g.find(c);if(e.length){e=e.last()}if(e.length){f&&e.addClass(f);if(d){e.prependTo(d)}}this.updateDropdownToggle(g)};a.prototype.extend=function(g,d,c,i,f){var e=g.find(c);if(e.length){e=e.first()}var j=false;if(e.length){if(d){if(f){d.before(e)}else{e.appendTo(d)}this.updateDropdownToggle(g);this.calculateWidth();var h=this.getOverflow();if(h>0){e.prependTo(g)}else{i&&e.removeClass(i);j=true}}}this.updateDropdownToggle(g);return j};a.prototype.updateDropdownToggle=function(c){var d=c.parents(".o_dropdown_toggle");if(!d.length){return}if(c.children().length){d.css("display","block")}else{d.css("display","none")}};a.prototype.addExtraElements=function(){var d=b("#o_navbar_container .o_navbar-collapse");var c=b("#o_navbar_more");if(c.length==0){c=b('<ul id="o_navbar_more" class="nav o_navbar-nav o_dropdown_toggle"><li><a class="dropdown-toggle" data-toggle="dropdown" href="#"">'+o_info.i18n_topnav_more+' <b class="caret"></b></a><ul class="dropdown-menu dropdown-menu-right"></ul></li></ul>');c.appendTo(d)}this.getSites().append('<li class="divider o_dropdown_site"></li>');b("#o_navbar_help .o_icon, #o_navbar_print .o_icon").addClass("o_icon-fw")};a.prototype.cleanupMoreDropdown=function(){if(!this.state.sitesDirty){var f=this.getSites();var d=this.getMoreDropdown().children(".o_dropdown_site");d.appendTo(f)}else{this.getSites().append('<li class="divider o_dropdown_site"></li>')}if(!this.state.tabsDirty){var e=this.getTabs();var c=this.getMoreDropdown().children(".o_dropdown_tab");c.prependTo(e)}this.getMoreDropdown().empty()};a.prototype.checkToolsOrder=function(){var f=this.getTools();var e=f.find("#o_navbar_help");var d=f.find("#o_navbar_print");var c=f.find("#o_navbar_imclient");if(c&&d){c.after(d)}if(c&&e){c.after(e)}};a.prototype.showRight=function(){if(!this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;var d=this;var c=b("#o_offcanvas_right");c.show().transition({x:-d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;b("body").addClass("o_offcanvas_right_visible");var e=b.proxy(d.hideRightOnClick,d);setTimeout(function(){b("html").on("click",e)},10)})}};a.prototype.hideRightOnClick=function(c){if("INPUT"!=c.target.nodeName){this.hideRight()}};a.prototype.hideRight=function(){if(this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;b("html").off("click",b.proxy(this.hideRight,this));var d=this;var c=b("#o_offcanvas_right");c.transition({x:d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;c.hide();b("body").removeClass("o_offcanvas_right_visible")})}};a.prototype.toggleRight=function(){if(this.isOffcanvasVisible()){this.hideRight()}else{this.showRight()}};a.prototype.isOffcanvasVisible=function(){return b("#o_offcanvas_right:visible").length};a.prototype.getSites=function(){return b("#o_navbar_container .o_navbar_sites")};a.prototype.getTabs=function(){return b("#o_navbar_container .o_navbar_tabs")};a.prototype.getTools=function(){return b("#o_navbar_container #o_navbar_tools_permanent")};a.prototype.getMoreDropdown=function(){return b("#o_navbar_more .dropdown-menu")};a.prototype.getOffcanvasRight=function(){return b("#o_offcanvas_right_container .o_navbar-right")};a.prototype.isSitesCollapsed=function(){return !this.getSites().children("li").not(".divider").length};a.prototype.isSitesExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_site").not(".divider").length};a.prototype.isTabsCollapsed=function(){return !this.getTabs().children("li").length};a.prototype.isTabsExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_tab").length};a.prototype.isToolsCollapsed=function(){return !this.getTools().children(".o_navbar_tool").not("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").length};a.prototype.isToolsExtended=function(){return !this.getOffcanvasRight().children(".o_tool_right").length};b(document).ready(function(){var d=b("#o_navbar_wrapper");if(d){var c=new a();window.OPOL.navbar=c}})}(jQuery);+function(b){b.fn.ooBgCarrousel=function(){return new a()};var a=function(){};a.prototype.initCarrousel=function(g){this.settings=b.extend({query:null,images:[],shuffle:false,shuffleFirst:false,durationshow:5000,durationout:500,durationin:500,easeout:"ease",easein:"ease"},g);this.pos=null;if(this.settings.query==null||this.settings.images.length==0){return}this.initialImage=this.settings.images[0];if(this.settings.shuffle){var f=this.settings.images;for(var d,c,e=f.length;e;d=parseInt(Math.random()*e),c=f[--e],f[e]=f[d],f[d]=c){}}if(this.settings.shuffleFirst){this._replaceImage()}this.rotate()};a.prototype.rotate=function(){setTimeout(b.proxy(this._hideCurrent,this),this.settings.durationshow)};a.prototype._hideCurrent=function(){var c=b(this.settings.query);if(c&&c.size()>0){c.transition({opacity:0,duration:this.settings.durationout,easing:this.settings.easeout},b.proxy(this._showNext,this))}};a.prototype._replaceImage=function(d){if(!d){d=b(this.settings.query)}if(d&&d.size()>0){this.newImg="";this.oldImg="";if(this.pos==null){this.pos=1;this.oldImg=this.initialImage}else{this.oldImg=this.settings.images[this.pos];this.pos++;if(this.settings.images.length==this.pos){this.pos=0}}this.newImg=this.settings.images[this.pos];var c=d.css("background-image");if(c.indexOf(this.oldImg)==-1){d.transition({opacity:1,duration:0});return}var e=c.replace(this.oldImg,this.newImg);d.css("background-image",e)}};a.prototype._showNext=function(){var c=b(this.settings.query);this._replaceImage(c);c.transition({opacity:1,duration:this.settings.durationin,easing:this.settings.easein},b.proxy(this.rotate,this))}}(jQuery);!function(a){function b(){function b(a){"remove"===a&&this.each(function(a,b){var c=e(b);c&&c.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(a,b){var c=tinymce.get(b.id.replace(/_parent$/,""));c&&c.remove()})}function d(a){var c,d=this;if(null!=a)b.call(d),d.each(function(b,c){var d;(d=tinymce.get(c.id))&&d.setContent(a)});else if(d.length>0&&(c=tinymce.get(d[0].id)))return c.getContent()}function e(a){var b=null;return a&&a.id&&g.tinymce&&(b=tinymce.get(a.id)),b}function f(a){return!!(a&&a.length&&g.tinymce&&a.is(":tinymce"))}var h={};a.each(["text","html","val"],function(b,g){var i=h[g]=a.fn[g],j="text"===g;a.fn[g]=function(b){var g=this;if(!f(g))return i.apply(g,arguments);if(b!==c)return d.call(g.filter(":tinymce"),b),i.apply(g.not(":tinymce"),arguments),g;var h="",k=arguments;return(j?g:g.eq(0)).each(function(b,c){var d=e(c);h+=d?j?d.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):d.getContent({save:!0}):i.apply(a(c),k)}),h}}),a.each(["append","prepend"],function(b,d){var g=h[d]=a.fn[d],i="prepend"===d;a.fn[d]=function(a){var b=this;return f(b)?a!==c?("string"==typeof a&&b.filter(":tinymce").each(function(b,c){var d=e(c);d&&d.setContent(i?a+d.getContent():d.getContent()+a)}),g.apply(b.not(":tinymce"),arguments),b):void 0:g.apply(b,arguments)}}),a.each(["remove","replaceWith","replaceAll","empty"],function(c,d){var e=h[d]=a.fn[d];a.fn[d]=function(){return b.call(this,d),e.apply(this,arguments)}}),h.attr=a.fn.attr,a.fn.attr=function(b,g){var i=this,j=arguments;if(!b||"value"!==b||!f(i))return g!==c?h.attr.apply(i,j):h.attr.apply(i,j);if(g!==c)return d.call(i.filter(":tinymce"),g),h.attr.apply(i.not(":tinymce"),j),i;var k=i[0],l=e(k);return l?l.getContent({save:!0}):h.attr.apply(a(k),j)}}var c,d,e,f=[],g=window;a.fn.tinymce=function(c){function h(){var d=[],f=0;e||(b(),e=!0),l.each(function(a,b){var e,g=b.id,h=c.oninit;g||(b.id=g=tinymce.DOM.uniqueId()),tinymce.get(g)||(e=new tinymce.Editor(g,c,tinymce.EditorManager),d.push(e),e.on("init",function(){var a,b=h;l.css("visibility",""),h&&++f==d.length&&("string"==typeof b&&(a=-1===b.indexOf(".")?null:tinymce.resolve(b.replace(/\.\w+$/,"")),b=tinymce.resolve(b)),b.apply(a||tinymce,d))}))}),a.each(d,function(a,b){b.render()})}var i,j,k,l=this,m="";if(!l.length)return l;if(!c)return window.tinymce?tinymce.get(l[0].id):null;if(l.css("visibility","hidden"),g.tinymce||d||!(i=c.script_url))1===d?f.push(h):h();else{d=1,j=i.substring(0,i.lastIndexOf("/")),-1!=i.indexOf(".min")&&(m=".min"),g.tinymce=g.tinyMCEPreInit||{base:j,suffix:m},-1!=i.indexOf("gzip")&&(k=c.language||"en",i=i+(/\?/.test(i)?"&":"?")+"js=true&core=true&suffix="+escape(m)+"&themes="+escape(c.theme||"modern")+"&plugins="+escape(c.plugins||"")+"&languages="+(k||""),g.tinyMCE_GZ||(g.tinyMCE_GZ={start:function(){function b(a){tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(a))}b("langs/"+k+".js"),b("themes/"+c.theme+"/theme"+m+".js"),b("themes/"+c.theme+"/langs/"+k+".js"),a.each(c.plugins.split(","),function(a,c){c&&(b("plugins/"+c+"/plugin"+m+".js"),b("plugins/"+c+"/langs/"+k+".js"))})},end:function(){}}));var n=document.createElement("script");n.type="text/javascript",n.onload=n.onreadystatechange=function(b){b=b||window.event,2===d||"load"!=b.type&&!/complete|loaded/.test(n.readyState)||(tinymce.dom.Event.domLoaded=1,d=2,c.script_loaded&&c.script_loaded(),h(),a.each(f,function(a,b){b()}))},n.src=i,document.body.appendChild(n)}return l},a.extend(a.expr[":"],{tinymce:function(a){var b;return a.id&&"tinymce"in window&&(b=tinymce.get(a.id),b&&b.editorManager===tinymce)?!0:!1}})}(jQuery);OPOL={};var o2c=0;var o3c=new Array();o_info.guibusy=false;o_info.linkbusy=false;o_info.debug=true;var BLoader={_ajaxLoadedJS:new Array(),_isAlreadyLoadedJS:function(b){var a=true;jQuery("head script[src]").each(function(d,c){if(jQuery(c).attr("src").indexOf(b)!=-1){a=false}});if(jQuery.inArray(b,this._ajaxLoadedJS)!=-1){a=false}return !a},loadJS:function(b,c,a){if(!this._isAlreadyLoadedJS(b)){if(o_info.debug){o_log("BLoader::loadJS: loading ajax::"+a+" url::"+b)}if(a){jQuery.ajax(b,{async:false,dataType:"script",cache:true,success:function(d,f,e){}});this._ajaxLoadedJS.push(b)}else{jQuery.getScript(b)}if(o_info.debug){o_log("BLoader::loadJS: loading DONE url::"+b)}}else{if(o_info.debug){o_log("BLoader::loadJS: already loaded url::"+b)}}},executeGlobalJS:function(jsString,contextDesc){try{if(window.execScript){window.execScript(jsString)}else{window.eval(jsString)}}catch(e){if(window.console){console.log(contextDesc,"cannot execute js",jsString)}if(o_info.debug){o_logerr("BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString))}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString),"functions.js::BLoader::executeGlobalJS::"+contextDesc)}if(window.location.href.indexOf("o_winrndo")!=-1){window.location.reload()}else{window.location.href=window.location.href+(window.location.href.indexOf("?")!=-1?"&":"?")+"o_winrndo=1"}}},loadCSS:function(b,o,q){var r=window.document;try{if(r.createStyleSheet){var j=r.styleSheets;var d=0;var p=0;for(i=0;i<j.length;i++){var m=j[i];var g=m.href;if(g==b){d++;if(m.disabled){m.disabled=false;return}else{if(o_info.debug){o_logwarn("BLoader::loadCSS: style: "+b+" already in document and not disabled! (duplicate add)")}return}}if(m.id=="o_theme_css"){p=i}}if(d>1&&o_info.debug){o_logwarn("BLoader::loadCSS: apply styles: num of stylesheets found was not 0 or 1:"+d)}if(q){p=j.length}var f=r.createStyleSheet(b,p)}else{var c=jQuery("#"+o);if(c&&c.size()>0){if(o_info.debug){o_logwarn("BLoader::loadCSS: stylesheet already found in doc when trying to add:"+b+", with id "+o)}return}else{var a=jQuery('<link id="'+o+'" rel="stylesheet" type="text/css" href="'+b+'">');if(q){a.insertBefore(jQuery("#o_fontSize_css"))}else{a.insertBefore(jQuery("#o_theme_css"))}}}}catch(n){if(window.console){console.log(n)}if(o_info.debug){o_logerr("BLoader::loadCSS: Error when loading CSS from URL::"+b)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::loadCSS: Error when loading CSS from URL::"+b,"functions.js::BLoader::loadCSS")}}},unLoadCSS:function(a,m){var n=window.document;try{if(n.createStyleSheet){var f=n.styleSheets;var d=0;var o=a;var b=window.location.href.substring(0,window.location.href.indexOf("/",8));if(a.indexOf(b)==0){o=a.substring(b.length)}for(i=0;i<f.length;i++){var g=f[i].href;if(g==a||g==o){d++;if(!f[i].disabled){f[i].disabled=true}else{if(o_info.debug){o_logwarn("stylesheet: when removing: matching url, but already disabled! url:"+g)}}}}if(d!=1&&o_info.debug){o_logwarn("stylesheet: when removeing: num of stylesheets found was not 1:"+d)}}else{var c=jQuery("#"+m);if(c){c.href="";c.remove();c=null;return}else{if(o_info.debug){o_logwarn("no link with id found to remove, id:"+m+", url "+a)}}}}catch(j){if(o_info.debug){o_logerr("BLoader::unLoadCSS: Error when unloading CSS from URL::"+a)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::unLoadCSS: Error when unloading CSS from URL::"+a,"functions.js::BLoader::loadCSS")}}}};var BFormatter={formatLatexFormulas:function(b){try{if(window.jsMath){if(jsMath.loaded&&jsMath.tex2math&&jsMath.tex2math.loaded){jsMath.Process()}else{jsMath.Autoload.LoadJsMath();setTimeout(function(){BFormatter.formatLatexFormulas(b)},100)}}}catch(a){if(window.console){console.log("error in BFormatter.formatLatexFormulas: ",a)}}}};function o_init(){try{o_getMainWin().o_afterserver()}catch(a){if(o_info.debug){o_log("error in o_init: "+showerror(a))}}}function o_initEmPxFactor(){o_info.emPxFactor=jQuery("#o_width_1em").width();if(o_info.emPxFactor==0||o_info.emPxFactor=="undefined"){o_info.emPxFactor=12;if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Could not read with of element b_width_1em, set o_info.emPxFactor to 12","functions.js")}}}function o_getMainWin(){try{if(window.OPOL){return window}else{if(window.opener&&window.opener.OPOL){return window.opener}}}catch(a){if(o_info.debug){o_logerr('Exception while getting main window. rror::"'+showerror(a))}if(window.console){console.log('Exception while getting main window. rror::"'+showerror(a),"functions.js");console.log(a)}}throw"Can not find main OpenOLAT window"}function o_beforeserver(){o_info.linkbusy=true;showAjaxBusy();if(window.suppressOlatOnUnloadOnce){window.suppressOlatOnUnloadOnce=false}else{if(window.olatonunload){olatonunload()}}}function o_afterserver(){o2c=0;o_info.linkbusy=false;removeAjaxBusy()}function o2cl(){if(o_info.linkbusy){return false}else{var a=(o2c==0||confirm(o_info.dirty_form));if(a){o_beforeserver()}return a}}function o2cl_noDirtyCheck(){if(o_info.linkbusy){return false}else{o_beforeserver();return true}}function o2cl_secure(){try{if(o2cl()){return true}else{return false}}catch(a){return false}}function o3cl(d){if(o_info.linkbusy){return false}else{var b=o3c1.indexOf(d)>-1;var a=(b&&o3c1.length>1)||o3c1.length>0;var c=(!a||confirm(o_info.dirty_form));if(c){o_beforeserver()}return c}}function o_onc(a){var b=a.responseText;BLoader.executeGlobalJS("o_info.last_o_onc="+b+";","o_onc");o_ainvoke(o_info.last_o_onc,false)}function o_allowNextClick(){o_info.linkbusy=false;removeAjaxBusy()}function removeBusyAfterDownload(c,b,a){o2c=0;o_afterserver()}Array.prototype.search=function(c,d){var a=this.length;for(var b=0;b<a;b++){if(this[b].constructor==Array){if(this[b].search(c,d)){return true;break}}else{if(d){if(this[b].indexOf(c)!=-1){return true;break}}else{if(this[b]==c){return true;break}}}}return false};if(!Function.prototype.curry){Function.prototype.curry=function(){if(arguments.length<1){return this}var a=this;var b=Array.prototype.slice.call(arguments);return function(){return a.apply(this,b.concat(Array.prototype.slice.call(arguments)))}}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(c){if(this==null){throw new TypeError()}var d=Object(this);var a=d.length>>>0;if(a===0){return -1}var e=0;if(arguments.length>1){e=Number(arguments[1]);if(e!=e){e=0}else{if(e!=0&&e!=Infinity&&e!=-Infinity){e=(e>0||-1)*Math.floor(Math.abs(e))}}}if(e>=a){return -1}var b=e>=0?e:Math.max(a-Math.abs(e),0);for(;b<a;b++){if(b in d&&d[b]===c){return b}}return -1}}var b_onDomReplacementFinished_callbacks=new Array();function b_AddOnDomReplacementFinishedCallback(a){var b=jQuery(document).ooLog().isDebugEnabled();if(b){jQuery(document).ooLog("debug","callback stack size: "+b_onDomReplacementFinished_callbacks.length,"functions.js ADD")}if(b&&b_onDomReplacementFinished_callbacks.toSource){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js ADD")}b_onDomReplacementFinished_callbacks.push(a);if(b){jQuery(document).ooLog("debug","push to callback stack, func: "+a,"functions.js ADD")}}var b_changedDomEl=new Array();function b_AddOnDomReplacementFinishedUniqueCallback(a){if(a.constructor==Array){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","add: its an ARRAY! ","functions.js ADD")}if(b_onDomReplacementFinished_callbacks.search(a[0])){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","push to callback stack, already there!!: "+a[0],"functions.js ADD")}return}}b_AddOnDomReplacementFinishedCallback(a)}var o_debug_trid=0;function o_ainvoke(N){if(N==undefined){return}o_info.inainvoke=true;var I=N.cmdcnt;if(I>0){jQuery(document).trigger("oo.dom.replacement.before");b_changedDomEl=new Array();if(o_info.debug){o_debug_trid++}var y=N.cmds;for(var T=0;T<I;T++){var J=y[T];var A=J.cmd;var R=J.cda;var U=J.w;var c=this.window;var K;if(c){switch(A){case 1:var M=R.e;BLoader.executeGlobalJS(M,"o_ainvoker::jsexec");if(o_info.debug){o_log("c1: execute jscode: "+M)}case 2:var u=R.cc;var F=R.cps;for(var Q=0;Q<u;Q++){var m=F[Q];var h=m.cid;var P=m.cidvis;var H=m.cw;var x=m.hfrag;var O=m.jsol;var g=m.hdr;if(o_info.debug){o_log("c2: redraw: "+m.cname+" ("+h+") "+m.hfragsize+" bytes, listener(s): "+m.clisteners)}var W=g+"\n\n"+x;var C="";var S=false;var E="o_c"+h;var B=jQuery("#"+E);if(B==null||B.length==0){E="o_fi"+h;B=jQuery("#"+E);S=true}if(B!=null){var w=jQuery("div.o_richtext_mce textarea",B);for(var L=0;L<w.length;L++){try{var a=jQuery(w.get(L)).attr("id");if(typeof top.tinymce!=undefined){top.tinymce.remove("#"+a)}}catch(Z){if(window.console){console.log(Z)}}}if(P){B.css("display","")}else{B.css("display","none")}if(S||!H){B.replaceWith(W)}else{try{B.empty().html(W);if(W.length>0&&B.get(0).innerHTML==""){B.get(0).innerHTML=W}}catch(Z){if(window.console){console.log(Z)}if(window.console){console.log("Fragment",W)}}b_changedDomEl.push(E)}B=null;if(C!=""){C.each(function(e){BLoader.executeGlobalJS(e,"o_ainvoker::inscripts")})}if(O!=""){BLoader.executeGlobalJS(O,"o_ainvoker::jsol")}}}break;case 3:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 5:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 6:c.o2c=0;c.o_afterserver();break;case 7:var o=c.document.location;var z=o.protocol+"//"+o.hostname;if(o.port!=""){z+=":"+o.port}var v=R.cssrm;for(Q=0;Q<v.length;Q++){var D=v[Q];var G=D.id;var f=z+D.url;BLoader.unLoadCSS(f,G);if(o_info.debug){o_log("c7: rm css: id:"+G+" ,url:'"+f+"'")}}var V=R.cssadd;for(k=0;k<V.length;k++){var D=V[k];var G=D.id;var f=z+D.url;var n=D.pt;BLoader.loadCSS(f,G,n);if(o_info.debug){o_log("c7: add css: id:"+G+" ,url:'"+f+"'")}}var p=R.jsadd;for(l=0;l<p.length;l++){var D=p[l];var Y=D.before;if(jQuery.type(Y)==="string"){BLoader.executeGlobalJS(Y,"o_ainvoker::preJsAdd")}var f=D.url;var q=D.enc;if(jQuery.type(f)==="string"){BLoader.loadJS(f,q,true)}if(o_info.debug){o_log("c7: add js: "+f)}}break;default:if(o_info.debug){o_log("?: unknown command "+A)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), ?: unknown command "+A,"functions.js")}break}}else{if(o_info.debug){o_log("could not find window??")}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), could not find window??","functions.js")}}}var b=b_onDomReplacementFinished_callbacks.length;if(b_onDomReplacementFinished_callbacks.toSource&&jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js")}for(mycounter=0;b>mycounter;mycounter++){if(mycounter>50){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stopped executing DOM replacement callback functions - to many functions::"+b_onDomReplacementFinished_callbacks.length,"functions.js")}break}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize before shift: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}var s=b_onDomReplacementFinished_callbacks.shift();if(typeof s.length==="number"){if(s[0]=="glosshighlighter"){var d=s[1];if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","arr fct: "+d,"functions.js")}s=d}}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Executing DOM replacement callback function #"+mycounter+" with timeout funct::"+s,"functions.js")}s();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize after timeout: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}}jQuery(document).trigger("oo.dom.replacement.after")}o_info.inainvoke=false}function clearAfterAjaxIframeCall(){if(o_info.linkbusy){o_afterserver();showMessageBox("info",o_info.i18n_noresponse_title,o_info.i18n_noresponse,undefined)}}function showAjaxBusy(){setTimeout(function(){if(o_info.linkbusy){try{if(jQuery("#o_ajax_busy_backdrop").length==0){jQuery("#o_body").addClass("o_ajax_busy");jQuery("#o_ajax_busy").modal({show:true,backdrop:"static",keyboard:"false"});jQuery("#o_ajax_busy").after('<div id="o_ajax_busy_backdrop" class="modal-backdrop in"></div>');jQuery("#o_ajax_busy>.modal-backdrop").remove();jQuery("#o_ajax_busy_backdrop").css({"z-index":1200})}}catch(a){if(window.console){console.log(a)}}}},700)}function removeAjaxBusy(){try{jQuery("#o_body").removeClass("o_ajax_busy");jQuery("#o_ajax_busy_backdrop").remove();jQuery("#o_ajax_busy").modal("hide")}catch(a){if(window.console){console.log(a)}}}function setFormDirty(c){o2c=1;var a=document.getElementById(c);if(a!=null){var b=a.olat_fosm_0;if(b==null){b=a.olat_fosm}if(b){b.className="btn o_button_dirty"}}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in setFormDirty, myForm was null for formId="+c,"functions.js")}}}function contextHelpWindow(a){helpWindow=window.open(a,"HelpWindow","height=760, width=940, left=0, top=0, location=no, menubar=no, resizable=yes, scrollbars=yes, toolbar=no");helpWindow.focus()}function o_openPopUp(b,d,c,a,e){attributes="height="+a+", width="+c+", resizable=yes, scrollbars=yes, left=100, top=100, ";if(e){attributes+="location=yes, menubar=yes, status=yes, toolbar=yes"}else{attributes+="location=no, menubar=no, status=no, toolbar=no"}var f=window.open(b,d,attributes);f.focus();if(o_info.linkbusy){o_afterserver()}}function b_handleFileUploadFormChange(e,b,d){var f=e.value;slashPos=f.lastIndexOf("/");if(slashPos!=-1){f=f.substring(slashPos+1)}slashPos=f.lastIndexOf("\\");if(slashPos!=-1){f=f.substring(slashPos+1)}b.value=f;if(d){d.className="o_button_dirty"}var c=e.form.elements;for(i=0;i<c.length;i++){var a=c[i];if(a.name==b.name&&i+1<c.length){c[i+1].focus()}}}function gotonode(a){try{if(typeof o_activateCourseNode!="undefined"){o_activateCourseNode(a)}else{if(opener&&typeof opener.o_activateCourseNode!="undefined"){opener.o_activateCourseNode(a)}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode(), could not find main window","functions.js")}}}}catch(b){alert("Goto node error:"+b);if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode()::"+b.message,"functions.js")}}}function o_openUriInMainWindow(b){try{var a=o_getMainWin();a.focus();a.location.replace(b)}catch(c){showMessageBox("error","Error","Can not find main OpenOLAT window to open URL.")}}function o_viewportHeight(){var a=jQuery(document).height();if(a>0){return a}else{return 600}}OPOL.getMainColumnsMaxHeight=function(){var j=0,f=0,a=0,c=0,h=0,b,g=jQuery("#o_main_left_content"),e=jQuery("#o_main_right_content"),d=jQuery("#o_main_center_content");if(g!="undefined"&&g!=null){j=g.outerHeight(true)}if(e!="undefined"&&e!=null){f=e.outerHeight(true)}if(d!="undefined"&&d!=null){a=d.outerHeight(true)}c=(j>f?j:f);c=(c>a?c:a);if(c>0){return c}b=jQuery("#o_main");if(b!="undefined"&&b!=null){h=b.height()}if(b>0){return b}return o_viewportHeight()};OPOL.adjustHeight=function(){try{var a=0;col1=jQuery("#o_main_left_content").outerHeight(true);col2=jQuery("#o_main_right_content").outerHeight(true);col3=jQuery("#o_main_center_content").outerHeight(true);a=Math.max(col1,col2,col3);if(col1!=null){jQuery("#o_main_left").css({"min-height":a+"px"})}if(col2!=null){jQuery("#o_main_right").css({"min-height":a+"px"})}if(col3!=null){jQuery("#o_main_center").css({"min-height":a+"px"})}}catch(b){if(window.console){console.log(b)}}};jQuery(window).resize(function(){clearTimeout(o_info.resizeId);o_info.resizeId=setTimeout(function(){jQuery(document).trigger("oo.window.resize.after")},500)});jQuery(document).on("oo.window.resize.after",OPOL.adjustHeight);jQuery(document).on("oo.dom.replacement.after",OPOL.adjustHeight);jQuery().ready(OPOL.adjustHeight);function o_scrollToElement(a){try{jQuery("html, body").animate({scrollTop:jQuery(a).offset().top},333)}catch(b){}}function o_popover(c,b,a){if(typeof(a)==="undefined"){a="bottom"}jQuery("#"+c).popover({placement:a,html:true,trigger:"click",container:"body",content:function(){return jQuery("#"+b).clone().html()}}).on("shown.bs.popover",function(){var d=function(f){jQuery("#"+c).popover("hide");jQuery("body").unbind("click",d)};setTimeout(function(){jQuery("body").on("click",d)},5)})}function o_popoverWithTitle(d,c,b,a){if(typeof(a)==="undefined"){a="bottom"}return jQuery("#"+d).popover({placement:a,html:true,title:b,trigger:"click",container:"body",content:function(){return jQuery("#"+c).clone().html()}}).on("shown.bs.popover",function(){var e=function(f){jQuery("#"+d).popover("destroy");jQuery("body").unbind("click",e)};setTimeout(function(){jQuery("body").on("click",e)},5)})}function o_shareLinkPopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:c}).on("shown.bs.popover",function(){var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)});a.attr("title",a.attr("data-original-title"))}function o_QRCodePopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:'<div id="'+d+'_pop" class="o_qrcode"></div>'}).on("shown.bs.popover",function(){o_info.qr=o_QRCode(d+"_pop",(jQuery.isFunction(c)?c():c));var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)}).on("hidden.bs.popover",function(){try{o_info.qr.clear();delete o_info.qr}catch(f){}});a.attr("title",a.attr("data-original-title"))}function o_QRCode(c,b){try{BLoader.loadJS(o_info.o_baseURI+"/js/jquery/qrcodejs/qrcode.min.js","utf8",true);return new QRCode(document.getElementById(c),b)}catch(a){return null}}function b_resizeIframeToMainMaxHeight(f){var d=jQuery("#"+f);if(d!="undefined"&&d!=null){var c=OPOL.getMainColumnsMaxHeight()-110;var b=o_viewportHeight()-100;b=b-d.offset().top;var e=jQuery("#b_footer");if(e!="undefined"&&e!=null){b=b-e.outerHeight(true)}var a=(b>c?b:c);d.height(a)}}var o_debu_oldcn,o_debu_oldtt;function o_debu_show(b,a){if(o_debu_oldcn){o_debu_hide(o_debu_oldcn,o_debu_oldtt)}jQuery(b).addClass("o_dev_m");jQuery(a).show();o_debu_oldtt=a;o_debu_oldcn=b}function o_debu_hide(b,a){jQuery(a).hide();jQuery(b).removeClass("o_dev_m")}function o_dbg_mark(a){var b=jQuery("#"+a);if(b){b.css("background-color","#FCFCB8");b.css("border","3px solid #00F")}}function o_dbg_unmark(a){var b=jQuery("#"+a);if(b){b.css("border","");b.css("background-color","")}}function o_clearConsole(){o_log_all="";o_log(null)}var o_log_all="";function o_log(b){if(b){o_log_all="\n"+o_debug_trid+"> "+b+o_log_all;o_log_all=o_log_all.substr(0,4000)}var a=jQuery("#o_debug_cons");if(a){if(o_log_all.length==4000){o_log_all=o_log_all+"\n... (stripped: to long)... "}a.value=o_log_all}if(!jQuery.type(window.console)==="undefined"){window.console.log(b)}}function o_logerr(a){o_log("ERROR:"+a)}function o_logwarn(a){o_log("WARN:"+a)}function showerror(c){var a="";for(var b in c){a+=b+": "+c[b]+"\n"}return"error detail:\n"+a}function o_ffEvent(e,d,c,h,j){var f,g,b,a;f=document.getElementById(d);g=f.value;f.value=c;b=document.getElementById(h);a=b.value;b.value=j;if(document.forms[e].onsubmit()){document.forms[e].submit()}f.value=g;b.value=a}function o_ffXHREvent(f,e,a,h,j){var c=new Object();c.dispatchuri=a;c.dispatchevent=j;if(arguments.length>5){var g=arguments.length;for(var d=5;d<g;d=d+2){if(g>d+1){c[arguments[d]]=arguments[d+1]}}}var b=jQuery("#"+f).attr("action");jQuery.ajax(b,{type:"GET",data:c,cache:false,dataType:"json",success:function(n,o,m){o_ainvoke(n)},error:function(m,o,n){if(window.console){console.log("Error status",o)}}})}function o_ffXHRNFEvent(b){var a=new Object();jQuery.ajax(b,{type:"GET",data:a,cache:false,dataType:"json",success:function(d,e,c){console.log("Hourra")},error:function(c,e,d){if(window.console){console.log("Error status",e)}}})}function setFlexiFormDirtyByListener(a){setFlexiFormDirty(a.data.formId)}function setFlexiFormDirty(b){var a=o3c.indexOf(b)>-1;if(!a){o3c.push(b)}jQuery("#"+b).each(function(){var c=jQuery(this).data("FlexiSubmit");if(c!=null){jQuery("#"+c).addClass("btn o_button_dirty");o2c=1}})}function o_ffRegisterSubmit(b,a){jQuery("#"+b).data("FlexiSubmit",a)}function showInfoBox(g,d){var c=Math.floor(Math.random()*65536).toString(16);var f='<div id="'+c+'" class="o_alert_info "><div class="alert alert-info clearfix o_sel_info_message"><i class="o_icon o_icon_close"></i><h3><i class="o_icon o_icon_info"></i> '+g+"</h3><p>"+d+"</p></div></div>";var a=jQuery("#o_messages").prepend(f);var e=(d.length>150)?8000:((d.length>70)?6000:4000);var b=function(){jQuery("#"+c).transition({top:"-100%"},333,function(){jQuery("#"+c).remove()})};jQuery("#"+c).show().transition({top:0},333);jQuery("#"+c).click(function(h){b()});o_scrollToElement("#o_top");g=null;d=null;a=null;e=null;setTimeout(function(){try{b()}catch(h){}},8000)}function showMessageBox(b,f,d,a){if(b=="info"){showInfoBox(f,d);return null}else{var c='<div id="myFunctionalModal" class="modal fade" role="dialog"><div class="modal-dialog"><div class="modal-content">';c+='<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>';c+='<h4 class="modal-title">'+f+"</h4></div>";c+='<div class="modal-body alert ';if("warn"==b){c+="alert-warning"}else{if("error"==b){c+="alert-danger"}else{c+="alert-info"}}c+='"><p>'+d+"</p></div></div></div></div>";jQuery("#myFunctionalModal").remove();jQuery("body").append(c);var e=jQuery("#myFunctionalModal").modal("show").on("hidden.bs.modal",function(g){jQuery("#myFunctionalModal").remove()});o_scrollToElement("#o_top");return e}}function tableFormInjectCommandAndSubmit(a,b,c){document.forms[a].elements.cmd.value=b;document.forms[a].elements.param.value=c;document.forms[a].submit()}function o_table_toggleCheck(d,c){var a=document.forms[d].elements.tb_ms;len=a.length;if(typeof(len)=="undefined"){a.checked=c}else{var b;for(b=0;b<len;b++){a[b].checked=c}}}function onTreeStartDrag(a,b){jQuery(a.target).addClass("o_dnd_proxy")}function onTreeStopDrag(a,b){jQuery(a.target).removeClass("o_dnd_proxy")}function onTreeDrop(g,h){var a=jQuery(h.draggable[0]);var f=jQuery(this);f.css({position:"",width:""});var c=f.droppable("option","endUrl");if(c.lastIndexOf("/")==(c.length-1)){c=c.substring(0,c.length-1)}var e=a.attr("id");var b=e.substring(2,e.length);c+="%3Atnidle%3A"+b;var d=f.attr("id");if(d.indexOf("ds")==0){c+="%3Asne%3Ayes"}else{if(d.indexOf("dt")==0){c+="%3Asne%3Aend"}}jQuery(".ui-droppable").each(function(j,m){jQuery(m).droppable("disable")});frames.oaa0.location.href=c+"/"}function treeAcceptDrop(a){return true}function treeAcceptDrop_notWithChildren(a){var c=false;var b=jQuery(a);var e=b.attr("id");if(e!=undefined&&(e.indexOf("dd")==0||e.indexOf("ds")==0||e.indexOf("dt")==0||e.indexOf("da")==0||e.indexOf("row")==0)){var g=jQuery(this);var j=g.attr("id");var d=e.substring(2,e.length);var f=j.substring(2,j.length);if(d!=f){var h=jQuery("#dd"+d).parents("li");if(h.length>0&&jQuery(h.get(0)).find("#dd"+f).length==0){c=true}}}return c}function treeAcceptDrop_portfolio(b){var d=false;var c=jQuery(b);var f=c.attr("id");if(treeNode_isDragNode(f)){var h=jQuery(this);var n=h.attr("id");var e=f.substring(2,f.length);var g=n.substring(2,n.length);var m=f.indexOf("ds")==0||f.indexOf("dt")==0;if(e!=g){var j=treeNode_portfolioType(c);var a=treeNode_portfolioType(h);if(j=="artefact"){if(a=="page"||a=="struct"||a=="artefact"){d=true}}else{if(j=="struct"){if(a=="page"||a=="struct"){d=true}}else{if(j=="page"){if(a=="map"||a=="page"){d=true}}}}}}return d}function treeNode_portfolioType(e){var c=jQuery(e.get(0));var d=treeNode_portfolioTypes(c);if(d==null){var a=c.parent("a");if(a.length>0){d=treeNode_portfolioTypes(jQuery(a.get(0)))}else{if(c.attr("id").indexOf("ds")==0){var b=c.prev("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}else{if(c.attr("id").indexOf("dt")==0){var b=c.next("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}}}}return d}function treeNode_portfolioTypes(a){if(a.find===undefined){return null}else{if(a.find(".o_ep_icon_struct").length>0||a.hasClass("o_ep_icon_struct")){return"struct"}else{if(a.find(".o_ep_icon_page").length>0||a.hasClass("o_ep_icon_page")){return"page"}else{if(a.find(".o_ep_icon_map").length>0||a.hasClass("o_ep_icon_map")){return"map"}else{if(a.find(".o_ep_artefact").length>0||a.hasClass("o_ep_artefact")){return"artefact"}}}}}return null}function treeNode_isDragNode(a){if(a!=undefined&&(a.indexOf("dd")==0||a.indexOf("ds")==0||a.indexOf("dt")==0||a.indexOf("da")==0||a.indexOf("row")==0)){return true}return false}function o_choice_toggleCheck(c,b){var d=document.forms[c].elements;len=d.length;if(typeof(len)=="undefined"){d.checked=b}else{var a;for(a=0;a<len;a++){if(d[a].type=="checkbox"&&d[a].getAttribute("class")=="o_checkbox"){d[a].checked=b}}}}function b_briefcase_isChecked(c,e){var b;var a=document.getElementById(c);var d=0;for(b=0;a.elements[b];b++){if(a.elements[b].type=="checkbox"&&a.elements[b].name=="paths"&&a.elements[b].checked){d++}}if(d<1){alert(e);return false}return true}function b_briefcase_toggleCheck(d,c){var a=document.getElementById(d);len=a.elements.length;var b;for(b=0;b<len;b++){if(a.elements[b].name=="paths"){a.elements[b].checked=c}}}function o_doPrint(){var d=jQuery("div.o_iframedisplay iframe");if(d.length>0){try{var a=d[0];frames[a.name].focus();frames[a.name].print();return}catch(c){for(i=0;frames.length>i;i++){a=frames[i];if(a.name=="oaa0"){continue}var b=document.getElementsByName(a.name)[0];if(b&&b.getAttribute("class")=="ext-shim"){continue}if(a.name!=""){try{frames[a.name].focus();frames[a.name].print()}catch(c){window.print()}return}}window.print()}}else{window.print()}}function b_attach_i18n_inline_editing(){jQuery("span.o_translation_i18nitem").hover(function(){jQuery(this.firstChild).show();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Entered i18nitem::"+this.firstChild,"functions.js:b_attach_i18n_inline_editing()")}},function(){jQuery("a.o_translation_i18nitem_launcher").hide();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Leaving i18nitem::"+this,"functions.js:b_attach_i18n_inline_editing()")}});jQuery("a.o_translation_i18nitem_launcher").hover(function(){var a=jQuery(this).parent("span.o_translation_i18nitem");a.effect("highlight")});b_AddOnDomReplacementFinishedCallback(b_attach_i18n_inline_editing)}function b_hideExtMessageBox(){}var BDebugger={_lastDOMCount:0,_lastObjCount:0,_knownGlobalOLATObjects:["o_afterserver","o_onc","o_getMainWin","o_ainvoke","o_info","o_beforeserver","o_ffEvent","o_openPopUp","o_debu_show","o_logwarn","o_dbg_unmark","o_ffRegisterSubmit","o_clearConsole","o_init","o_log","o_allowNextClick","o_dbg_mark","o_debu_hide","o_logerr","o_debu_oldcn","o_debu_oldtt","o_openUriInMainWindow","o_debug_trid","o_log_all"],_countDOMElements:function(){return document.getElementsByTagName("*").length},_countGlobalObjects:function(){var a=0;for(prop in window){a++}return a},logDOMCount:function(){var b=BDebugger;var a=b._countDOMElements();var c=a-b._lastDOMCount;console.log((c>0?"+":"")+c+" \t"+a+" \tDOM element count after DOM replacement");b._lastDOMCount=a;a=null},logGlobalObjCount:function(){var b=BDebugger;var a=b._countGlobalObjects();var c=a-b._lastObjCount;console.log((c>0?"+":"")+c+" \t"+a+" \tGlobal object count after DOM replacement");b._lastObjCount=a;a=null},logGlobalOLATObjects:function(){var b=BDebugger;var a=new Array();for(prop in window){if(prop.indexOf("o_")==0&&b._knownGlobalOLATObjects.indexOf(prop)==-1){a.push(prop)}}if(a.length>0){console.log(a.length+" global OLAT objects found:");a.each(function(c){console.log("\t"+typeof window[c]+" \t"+c)})}},logManagedOLATObjects:function(){var a=BDebugger;if(o_info.objectMap.length>0){console.log(o_info.objectMap.length+" managed OLAT objects found:");o_info.objectMap.eachKey(function(b){var c=o_info.objectMap.get(b);console.log("\t"+typeof c+" \t"+b);return true})}}};/*!
  * jQuery Transit - CSS3 transitions and transformations
  * (c) 2011-2014 Rico Sta. Cruz
  * MIT Licensed.
diff --git a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java
index 8f0f97db9a2daf4467f334ea034f7e831d061999..0505275d38538fe477ea8873e532ec7d3f0f9ac8 100644
--- a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java
+++ b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java
@@ -145,6 +145,32 @@ public class UserCourseInformationsManagerTest extends OlatTestCase {
 		Assert.assertNotNull(launchDates.get(user2.getKey()));
 	}
 	
+	@Test
+	public void getInitialLaunchDates_noIdentites() {
+		Identity user1 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-7-");
+		Identity user2 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-8-");
+		Identity user3 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-9-");
+		ICourse course = CoursesWebService.createEmptyCourse(user1, "course-launch-dates", "course long name", null);
+		dbInstance.commitAndCloseSession();
+		
+		userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user1, true);
+		userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user2, true);
+		userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user3, true);
+		dbInstance.commitAndCloseSession();
+		
+		//get all launch dates
+		Map<Long,Date> launchDates = userCourseInformationsManager.getInitialLaunchDates(course.getResourceableId());
+		Assert.assertNotNull(launchDates);
+		Assert.assertEquals(3, launchDates.size());
+		Assert.assertTrue(launchDates.containsKey(user1.getKey()));
+		Assert.assertNotNull(launchDates.get(user1.getKey()));
+		Assert.assertTrue(launchDates.containsKey(user2.getKey()));
+		Assert.assertNotNull(launchDates.get(user2.getKey()));
+		Assert.assertTrue(launchDates.containsKey(user3.getKey()));
+		Assert.assertNotNull(launchDates.get(user3.getKey()));
+	}
+	
+	
 	
 	/**
 	 * This test is to analyze a red screen
diff --git a/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java b/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java
index 534029e7f6ab10bd6184763a4b33bc40c3931f51..349c75300a9bf2703b6c06c7da1e90a54cc2f2ce 100644
--- a/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java
+++ b/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java
@@ -224,6 +224,106 @@ public class GTAManagerTest extends OlatTestCase {
 		Assert.assertTrue(assigned.contains("work_2.txt"));
 	}
 	
+	@Test
+	public void deleteTaskList() {
+		Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-9");
+		Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-10");
+		BusinessGroup businessGroup = businessGroupDao.createAndPersist(coach, "gdao", "gdao-desc", -1, -1, false, false, false, false, false);
+		businessGroupRelationDao.addRole(participant, businessGroup, GroupRole.participant.name());
+		dbInstance.commit();
+		RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false);
+		GTACourseNode node = new GTACourseNode();
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.group.name());
+		TaskList tasks = gtaManager.createIfNotExists(re, node);
+		File taskFile = new File("bg.txt");
+		Assert.assertNotNull(tasks);
+		dbInstance.commit();
+		
+		//select
+		AssignmentResponse response = gtaManager.selectTask(businessGroup, tasks, node, taskFile);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(response);
+		
+		//check that there is tasks
+		List<Task> assignedTasks = gtaManager.getTasks(participant, re, node);
+		Assert.assertNotNull(assignedTasks);
+		Assert.assertEquals(1, assignedTasks.size());
+		
+		//delete
+		int numOfDeletedObjects = gtaManager.deleteTaskList(re, node);
+		Assert.assertEquals(2, numOfDeletedObjects);
+		dbInstance.commitAndCloseSession();
+		
+		//check that there isn't any tasks
+		List<Task> deletedAssignedTasks = gtaManager.getTasks(participant, re, node);
+		Assert.assertNotNull(deletedAssignedTasks);
+		Assert.assertEquals(0, deletedAssignedTasks.size());
+	}
+	
+	/**
+	 * Create 2 pseudo nodes in a course, and delete the task of the first node
+	 * and check that the task of second are always there.
+	 * 
+	 */
+	@Test
+	public void deleteTaskList_parano() {
+		Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-9");
+		Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-10");
+		BusinessGroup businessGroup = businessGroupDao.createAndPersist(coach, "gdao", "gdao-desc", -1, -1, false, false, false, false, false);
+		businessGroupRelationDao.addRole(participant, businessGroup, GroupRole.participant.name());
+		dbInstance.commit();
+		RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false);
+		
+		//node 1
+		GTACourseNode node1 = new GTACourseNode();
+		node1.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.group.name());
+		TaskList tasks1 = gtaManager.createIfNotExists(re, node1);
+		File taskFile = new File("bg.txt");
+		Assert.assertNotNull(tasks1);
+		dbInstance.commit();
+		
+		//node 2
+		GTACourseNode node2 = new GTACourseNode();
+		node2.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.group.name());
+		TaskList tasks2 = gtaManager.createIfNotExists(re, node2);
+		Assert.assertNotNull(tasks2);
+		dbInstance.commit();
+		
+		//select node 1
+		AssignmentResponse response1 = gtaManager.selectTask(businessGroup, tasks1, node1, taskFile);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(response1);
+		
+		//select node 2
+		AssignmentResponse response2 = gtaManager.selectTask(businessGroup, tasks2, node2, taskFile);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(response2);
+		
+		//check that there is tasks
+		List<Task> assignedTasks1 = gtaManager.getTasks(participant, re, node1);
+		Assert.assertNotNull(assignedTasks1);
+		Assert.assertEquals(1, assignedTasks1.size());
+		
+		List<Task> assignedTasks2 = gtaManager.getTasks(participant, re, node2);
+		Assert.assertNotNull(assignedTasks2);
+		Assert.assertEquals(1, assignedTasks2.size());
+		
+		//delete
+		int numOfDeletedObjects = gtaManager.deleteTaskList(re, node1);
+		Assert.assertEquals(2, numOfDeletedObjects);
+		dbInstance.commitAndCloseSession();
+		
+		//check that there isn't any tasks in node 1
+		List<Task> deletedAssignedTasks = gtaManager.getTasks(participant, re, node1);
+		Assert.assertNotNull(deletedAssignedTasks);
+		Assert.assertEquals(0, deletedAssignedTasks.size());
+		
+		//but always in node 2
+		List<Task> notDeletedAssignedTasks2 = gtaManager.getTasks(participant, re, node2);
+		Assert.assertNotNull(notDeletedAssignedTasks2);
+		Assert.assertEquals(1, notDeletedAssignedTasks2.size());
+	}
+	
 	@Test
 	public void roundRobin() {
 		String[] slots = new String[]{ "A", "B", "C" };
diff --git a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java
index 27de702b6b9fef27b7b3ffe6d32ae5af22075549..f482bfe454ad6afdd713ed23b7d0140c391855c9 100644
--- a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java
+++ b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java
@@ -22,15 +22,22 @@ package org.olat.course.nodes.gta.rule;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.olat.basesecurity.GroupRoles;
+import org.olat.basesecurity.model.GroupMembershipImpl;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.manager.UserCourseInformationsManager;
+import org.olat.course.assessment.model.UserCourseInfosImpl;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.AssignmentResponse;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.TaskList;
 import org.olat.course.nodes.gta.manager.GTAManagerImpl;
@@ -41,7 +48,11 @@ import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.modules.reminder.rule.LaunchUnit;
 import org.olat.modules.vitero.model.GroupRole;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.manager.RepositoryEntryLifecycleDAO;
 import org.olat.repository.manager.RepositoryEntryRelationDAO;
+import org.olat.repository.model.RepositoryEntryLifecycle;
+import org.olat.repository.model.RepositoryEntryToGroupRelation;
+import org.olat.restapi.repository.course.CoursesWebService;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatTestCase;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -61,9 +72,13 @@ public class GTAReminderRuleTest extends OlatTestCase {
 	@Autowired
 	private BusinessGroupDAO businessGroupDao;
 	@Autowired
+	private RepositoryEntryLifecycleDAO reLifeCycleDao;
+	@Autowired
 	private BusinessGroupRelationDAO businessGroupRelationDao;
 	@Autowired
 	private RepositoryEntryRelationDAO repositoryEntryRelationDao;
+	@Autowired
+	private UserCourseInformationsManager userCourseInformationsManager;
 	
 
 	@Autowired
@@ -211,6 +226,179 @@ public class GTAReminderRuleTest extends OlatTestCase {
 		Assert.assertTrue(toRemind.contains(participant4));
 	}
 	
+	@Test
+	public void assignTask_relativeToDateEnrollment() {
+		//prepare a course with a volatile task
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2");
+		RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false);
+		addEnrollmentDate(re, participant1, GroupRoles.participant, -12, Calendar.DATE);
+		addEnrollmentDate(re, participant2, GroupRoles.participant, -5, Calendar.DATE);
+		dbInstance.commit();
+		
+		// create a fake node
+		GTACourseNode node = new GTACourseNode();
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name());
+		node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true);
+		node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 15);
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.enrollment.name());
+		
+		// need the task list
+		TaskList tasks = gtaManager.createIfNotExists(re, node);
+		Assert.assertNotNull(tasks);
+		dbInstance.commit();
+		
+		// participant 1 has still 3 days to choose a task
+		// participant 2 has still 10 days to choose a task
+		
+		{ // check before 1 day
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(0, all.size());
+		}
+
+		{ // check before 5 days
+			ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(1, all.size());
+			Assert.assertTrue(all.contains(participant1));
+		}
+		
+		{ // check before 1 week
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(1, all.size());
+			Assert.assertTrue(all.contains(participant1));
+		}
+		
+		{ // check before 1 month
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+	}
+	
+	private void addEnrollmentDate(RepositoryEntry entry, Identity id, GroupRoles role, int amount, int field) {
+		RepositoryEntryToGroupRelation rel = entry.getGroups().iterator().next();
+		rel.getGroup();
+		
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(new Date());
+		cal.add(field, amount);
+		
+		GroupMembershipImpl membership = new GroupMembershipImpl();
+		membership.setCreationDate(cal.getTime());
+		membership.setLastModified(cal.getTime());
+		membership.setGroup(rel.getGroup());
+		membership.setIdentity(id);
+		membership.setRole(role.name());
+		dbInstance.getCurrentEntityManager().persist(membership);
+		dbInstance.commit();
+	}
+	
+	@Test
+	public void assignTask_relativeToInitialLaunchDate() {
+		//create a course with 3 members
+		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-1");
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-2");
+		Identity id3 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-3");
+
+		ICourse course = CoursesWebService.createEmptyCourse(null, "initial-launch-dates", "course long name", null);
+		RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		repositoryEntryRelationDao.addRole(id1, re, GroupRoles.participant.name());
+		repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name());
+		repositoryEntryRelationDao.addRole(id3, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		//create user course infos
+		Long courseResId = course.getCourseEnvironment().getCourseResourceableId();
+		userCourseInformationsManager.updateUserCourseInformations(courseResId, id1, true);
+		userCourseInformationsManager.updateUserCourseInformations(courseResId, id2, true);
+		userCourseInformationsManager.updateUserCourseInformations(courseResId, id3, true);
+		dbInstance.commit();
+		
+		//fake the date
+		updateInitialLaunchDate(courseResId, id1, -5, Calendar.DATE);
+		updateInitialLaunchDate(courseResId, id2, -35, Calendar.DATE);
+		updateInitialLaunchDate(courseResId, id3, -75, Calendar.DATE);
+		dbInstance.commitAndCloseSession();
+		
+		// create a fake node
+		GTACourseNode node = new GTACourseNode();
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name());
+		node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true);
+		node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 40);
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseLaunch.name());
+		
+		// need the task list
+		TaskList tasks = gtaManager.createIfNotExists(re, node);
+		Assert.assertNotNull(tasks);
+		dbInstance.commit();		
+
+		{ // check 3 days
+			ReminderRuleImpl rule = getAssignedTaskRules(3, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(1, all.size());
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 5 days
+			ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 1 week
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 1 month
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 2 month
+			ReminderRuleImpl rule = getAssignedTaskRules(2, LaunchUnit.month);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(3, all.size());
+			Assert.assertTrue(all.contains(id1));
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+
+	}
+	
+	private void updateInitialLaunchDate(Long courseResId, Identity id, int amount, int field) {
+		UserCourseInfosImpl userCourseInfos = (UserCourseInfosImpl)userCourseInformationsManager.getUserCourseInformations(courseResId, id);
+		Date initialLaunch = userCourseInfos.getInitialLaunch();
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(initialLaunch);
+		cal.add(field, amount);
+		userCourseInfos.setInitialLaunch(cal.getTime());
+		dbInstance.getCurrentEntityManager().merge(userCourseInfos);
+		dbInstance.commit();
+	}
+	
 	@Test
 	public void submitTask_individual() {
 		//prepare a course with a volatile task
@@ -297,6 +485,112 @@ public class GTAReminderRuleTest extends OlatTestCase {
 		}
 	}
 	
+	@Test
+	public void submitTask_relativeLifecycle() {
+		//prepare a course with a volatile task
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2");
+		RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false);
+		repositoryEntryRelationDao.addRole(participant1, re, GroupRoles.participant.name());
+		repositoryEntryRelationDao.addRole(participant2, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		String label = "Life cycle for relative date";
+		String softKey = UUID.randomUUID().toString();
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(new Date());
+		cal.add(Calendar.DATE, -5);
+		Date from = cal.getTime();
+		cal.add(Calendar.DATE, 20);
+		Date to = cal.getTime();
+		RepositoryEntryLifecycle lifecycle = reLifeCycleDao.create(label, softKey, true, from, to);
+		re.setLifecycle(lifecycle);
+		re = dbInstance.getCurrentEntityManager().merge(re);
+		dbInstance.commit();
+		
+		//create a fake node with a relative submit deadline 15 days after the start of the course
+		GTACourseNode node = new GTACourseNode();
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name());
+		node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true);
+		node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, 15);
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseStart.name());
+
+		TaskList tasks = gtaManager.createIfNotExists(re, node);
+		Assert.assertNotNull(tasks);
+		dbInstance.commitAndCloseSession();
+		
+		//the course has start 5 days before, deadline is 15 days after it
+		//conclusion the deadline is 10 days from now
+		
+		{ // check before 5 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.day);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(0, all.size());
+		}
+		
+		{ // check before 1 week 
+			ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.week);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(0, all.size());
+		}
+		
+		{ // check before 10 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.day);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 2 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.week);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 30 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(30, LaunchUnit.day);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 1 months 
+			ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.month);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 5 months 
+			ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.month);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 1 year 
+			ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.year);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+	}
+	
 	private ReminderRuleImpl getSubmitTaskRules(int amount, LaunchUnit unit) {
 		ReminderRuleImpl rule = new ReminderRuleImpl();
 		rule.setType(SubmissionTaskRuleSPI.class.getSimpleName());
diff --git a/src/test/java/org/olat/modules/reminder/ReminderModuleTest.java b/src/test/java/org/olat/modules/reminder/ReminderModuleTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a0b4372ff24604a9505ddd96cedb35ae01014b9
--- /dev/null
+++ b/src/test/java/org/olat/modules/reminder/ReminderModuleTest.java
@@ -0,0 +1,89 @@
+/**
+ * <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.modules.reminder;
+
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.test.OlatTestCase;
+import org.quartz.CronExpression;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 11.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ReminderModuleTest extends OlatTestCase {
+	
+	@Autowired
+	private ReminderModule reminderModule;
+	
+	
+	@Test
+	public void testCronJob_everyTwoHours() throws ParseException {
+		reminderModule.setScheduler("2", "9:30");
+		String cron = reminderModule.getCronExpression();
+		
+		Calendar cal = Calendar.getInstance();
+		cal.set(Calendar.HOUR_OF_DAY, 0);
+		cal.set(Calendar.MINUTE, 5);
+		
+		CronExpression cronExpression = new CronExpression(cron);
+		Date d1 = cronExpression.getNextValidTimeAfter(cal.getTime());
+		
+		Calendar cal1 = Calendar.getInstance();
+		cal1.setTime(d1);
+		Assert.assertEquals(1, cal1.get(Calendar.HOUR_OF_DAY));
+		Assert.assertEquals(30, cal1.get(Calendar.MINUTE));	
+	}
+	
+	@Test
+	public void testCronJob_everyHeightHours() throws ParseException {
+		reminderModule.setScheduler("8", "6:30");
+		String cron = reminderModule.getCronExpression();
+		
+		Calendar cal = Calendar.getInstance();
+		cal.set(Calendar.HOUR_OF_DAY, 0);
+		cal.set(Calendar.MINUTE, 5);
+		
+		CronExpression cronExpression = new CronExpression(cron);
+		Date d1 = cronExpression.getNextValidTimeAfter(cal.getTime());
+		
+		Calendar triggerCal = Calendar.getInstance();
+		triggerCal.setTime(d1);
+		Assert.assertEquals(6, triggerCal.get(Calendar.HOUR_OF_DAY));
+		Assert.assertEquals(30, triggerCal.get(Calendar.MINUTE));
+
+		Date d2 = cronExpression.getNextValidTimeAfter(d1);
+		triggerCal.setTime(d2);
+		Assert.assertEquals(14, triggerCal.get(Calendar.HOUR_OF_DAY));
+		Assert.assertEquals(30, triggerCal.get(Calendar.MINUTE));
+
+		Date d3 = cronExpression.getNextValidTimeAfter(d2);
+		triggerCal.setTime(d3);
+		Assert.assertEquals(22, triggerCal.get(Calendar.HOUR_OF_DAY));
+		Assert.assertEquals(30, triggerCal.get(Calendar.MINUTE));
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java
index a37d6dcaa89455edda31e358b39143c490e10bcd..c4b576c8f6c9998cffae266d0f322cfc0e984eb7 100644
--- a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java
+++ b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java
@@ -21,7 +21,9 @@ package org.olat.repository.manager;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.junit.Assert;
@@ -181,6 +183,65 @@ public class RepositoryEntryRelationDAOTest extends OlatTestCase {
 		Assert.assertEquals(4, numOfMembers);
 	}
 	
+	@Test
+	public void getEnrollmentDate() {
+		Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-1-");
+		Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-1-");
+		RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null);
+		dbInstance.commit();
+		repositoryEntryRelationDao.addRole(id, re, GroupRoles.owner.name());
+		repositoryEntryRelationDao.addRole(id, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		//enrollment date
+		Date enrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id);
+		Assert.assertNotNull(enrollmentDate);
+		
+		//this user isn't enrolled
+		Date withoutEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, wid);
+		Assert.assertNull(withoutEnrollmentDate);
+		
+		//as participant
+		Date participantEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.participant.name());
+		Assert.assertNotNull(participantEnrollmentDate);
+		//as owner
+		Date ownerEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.owner.name());
+		Assert.assertNotNull(ownerEnrollmentDate);
+		//is not enrolled as coached
+		Date coachEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.coach.name());
+		Assert.assertNull(coachEnrollmentDate);
+	}
+	
+	@Test
+	public void getEnrollmentDates() {
+		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-2-");
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-3-");
+		Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-2-");
+		RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null);
+		dbInstance.commit();
+		repositoryEntryRelationDao.addRole(id1, re, GroupRoles.owner.name());
+		repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		//enrollment date
+		Map<Long,Date> enrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(re);
+		Assert.assertNotNull(enrollmentDates);
+		Assert.assertEquals(2, enrollmentDates.size());
+		Assert.assertTrue(enrollmentDates.containsKey(id1.getKey()));
+		Assert.assertTrue(enrollmentDates.containsKey(id2.getKey()));
+		Assert.assertFalse(enrollmentDates.containsKey(wid.getKey()));
+	}
+
+	@Test
+	public void getEnrollmentDates_emptyCourse() {
+		//enrollment of an empty course
+		RepositoryEntry notEnrolledRe = repositoryService.create("Rei Ayanami", "rel", "rel", null, null);
+		dbInstance.commit();
+		Map<Long,Date> notEnrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(notEnrolledRe);
+		Assert.assertNotNull(notEnrollmentDates);
+		Assert.assertEquals(0, notEnrollmentDates.size());
+	}
+	
 	@Test
 	public void getAuthorKeys() {
 		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("auth-1-");
diff --git a/src/test/java/org/olat/selenium/UserTest.java b/src/test/java/org/olat/selenium/UserTest.java
index 079a8455b5a7f520bb4117daa48ed4ffff7c384a..5403933e349e503e87b71c24cae12b3569f639fd 100644
--- a/src/test/java/org/olat/selenium/UserTest.java
+++ b/src/test/java/org/olat/selenium/UserTest.java
@@ -35,12 +35,12 @@ import org.jboss.arquillian.test.api.ArquillianResource;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.Assert;
 import org.junit.Assume;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.olat.restapi.support.vo.CourseVO;
 import org.olat.selenium.page.LoginPage;
 import org.olat.selenium.page.NavigationPage;
+import org.olat.selenium.page.Student;
 import org.olat.selenium.page.User;
 import org.olat.selenium.page.course.CoursePageFragment;
 import org.olat.selenium.page.graphene.OOGraphene;
@@ -498,7 +498,7 @@ public class UserTest {
 	 * @throws IOException
 	 * @throws URISyntaxException
 	 */
-	@Test @Ignore
+	@Test 
 	@RunAsClient
 	public void importUsers(@InitialPage LoginPage loginPage,
 			@Drone @User WebDriver userBrowser)
@@ -540,4 +540,70 @@ public class UserTest {
 			.resume()
 			.assertLoggedIn(user1);
 	}
+	
+	/**
+	 * Import 1 new user and 1 existing, change the password and the last name
+	 * of the existing user.
+	 * 
+	 * @param loginPage
+	 * @param existingUserBrowser
+	 * @throws IOException
+	 * @throws URISyntaxException
+	 */
+	@Test 
+	@RunAsClient
+	public void importExistingUsers(@InitialPage LoginPage loginPage,
+			@Drone @User WebDriver existingUserBrowser,
+			@Drone @Student WebDriver newUserBrowser)
+	throws IOException, URISyntaxException {
+
+		UserVO user1 = new UserRestClient(deploymentUrl)
+			.createRandomUser("tsukune");
+		
+		//login
+		loginPage
+			.assertOnLoginPage()
+			.loginAs("administrator", "openolat")
+			.resume();
+		
+		UserAdminPage userAdminPage = navBar
+			.openUserManagement()
+			.openImportUsers();
+		//start import wizard
+		ImportUserPage importWizard = userAdminPage.startImport();
+		
+		String uuid = UUID.randomUUID().toString();
+		String username1 = "moka-" + uuid;
+
+		StringBuilder csv = new StringBuilder();
+		UserVO newUser = importWizard.append(username1, "rosario02", "Moka", "Akashiya", csv);
+		user1 = importWizard.append(user1, "Aono", "openolat2", csv);
+		importWizard
+			.fill(csv.toString())
+			.next() // -> preview
+			.assertGreen(1)
+			.assertWarn(1)
+			.changePassword()
+			.next() // -> groups
+			.next() // -> emails
+			.finish();
+		
+		OOGraphene.waitAndCloseBlueMessageWindow(browser);
+		
+		//existing user log in with its new password and check if its name was updated
+		LoginPage userLoginPage = LoginPage.getLoginPage(existingUserBrowser, deploymentUrl);
+		//tools
+		userLoginPage
+			.loginAs(user1.getLogin(), "openolat2")
+			.resume()
+			.assertLoggedInByLastName("Aono");
+		
+		//new user log in
+		LoginPage newLoginPage = LoginPage.getLoginPage(newUserBrowser, deploymentUrl);
+		//tools
+		newLoginPage
+			.loginAs(newUser.getLogin(), "rosario02")
+			.resume()
+			.assertLoggedInByLastName("Akashiya");
+	}
 }
diff --git a/src/test/java/org/olat/selenium/page/LoginPage.java b/src/test/java/org/olat/selenium/page/LoginPage.java
index c82430671da9255fe8d1f681531a7dde3ed3ac0f..fa61508fba1364f6eb093c01c0a129953ad87851 100644
--- a/src/test/java/org/olat/selenium/page/LoginPage.java
+++ b/src/test/java/org/olat/selenium/page/LoginPage.java
@@ -79,6 +79,14 @@ public class LoginPage {
 		Assert.assertTrue(name.contains(user.getLastName()));
 	}
 	
+	public void assertLoggedInByLastName(String lastName) {
+		WebElement username = browser.findElement(usernameFooterBy);
+		Assert.assertNotNull(username);
+		Assert.assertTrue(username.isDisplayed());
+		String name = username.getText();
+		Assert.assertTrue(name.contains(lastName));
+	}
+	
 	/**
 	 * Login and accept the disclaimer if there is one.
 	 * 
diff --git a/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java b/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java
index 6ead43e3c6d18d57ab3778d48d6056efcd406136..b44d2040e8ff1fa2eacead2b3a3e715bcddffcdf 100644
--- a/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java
+++ b/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java
@@ -69,6 +69,11 @@ public class OOGraphene {
 		((JavascriptExecutor)browser).executeScript("top.tinymce.activeEditor.setContent('" + content + "')");
 	}
 	
+	public static final void textarea(WebElement textareaEl, String content, WebDriver browser) {
+		String id = textareaEl.getAttribute("id");
+		((JavascriptExecutor)browser).executeScript("document.getElementById('" + id + "').value = '" + content + "'");
+	}
+	
 	public static final void date(Date date, String seleniumCssClass, WebDriver browser) {
 		Locale locale = getLocale(browser);
 		String dateText = DateFormat.getDateInstance(DateFormat.SHORT, locale).format(date);
diff --git a/src/test/java/org/olat/selenium/page/user/ImportUserPage.java b/src/test/java/org/olat/selenium/page/user/ImportUserPage.java
index acac1fc0ae260bb738bf1203b1be53f3aef178c3..e4b99bfa2bca9081798ebaf531b8c9c5c67eebf1 100644
--- a/src/test/java/org/olat/selenium/page/user/ImportUserPage.java
+++ b/src/test/java/org/olat/selenium/page/user/ImportUserPage.java
@@ -19,16 +19,12 @@
  */
 package org.olat.selenium.page.user;
 
-import java.awt.Toolkit;
-import java.awt.datatransfer.StringSelection;
 import java.util.List;
 
 import org.junit.Assert;
 import org.olat.selenium.page.graphene.OOGraphene;
 import org.olat.user.restapi.UserVO;
 import org.openqa.selenium.By;
-import org.openqa.selenium.Keys;
-import org.openqa.selenium.Platform;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
 
@@ -54,12 +50,16 @@ public class ImportUserPage {
 	public ImportUserPage fill(String csv) {
 		By importTextareaBy = By.cssSelector("div.o_wizard_steps_current_content textarea");
 		WebElement importTextareaEl = browser.findElement(importTextareaBy);
-		Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(csv), null);
-		if(Platform.MAC.equals(Platform.getCurrent())) {
-			importTextareaEl.sendKeys(Keys.COMMAND + "v");
-		} else {
-			importTextareaEl.sendKeys(Keys.CONTROL + "v");
-		}
+		//focus
+		importTextareaEl.sendKeys("");
+		OOGraphene.textarea(importTextareaEl, csv, browser);
+		return this;
+	}
+	
+	public ImportUserPage changePassword() {
+		By updatePassword = By.cssSelector("input[name='update.password'][type='checkbox']");
+		browser.findElement(updatePassword).click();
+		OOGraphene.waitBusy(browser);
 		return this;
 	}
 	
@@ -77,7 +77,10 @@ public class ImportUserPage {
 	 * @param sb
 	 */
 	public UserVO append(String username, String password, String firstName, String lastName, StringBuilder sb) {
-
+		if(sb.length() > 0) {
+			sb.append("\\n");
+		}
+		
 		String email = username.replace("-", "") + "@frentix.com";
 		String institution = "frentix GmbH";
 		String institutionNumber = "034-" + System.currentTimeMillis();
@@ -91,7 +94,7 @@ public class ImportUserPage {
 		  .append(email).append("	")
 		  .append(institution).append("	")
 		  .append(institutionNumber).append("	")
-		  .append(institutionEmail).append('\n');
+		  .append(institutionEmail);
 		
 		UserVO userVo = new UserVO();
 		userVo.setLogin(username);
@@ -101,6 +104,27 @@ public class ImportUserPage {
 		return userVo;
 	}
 	
+	public UserVO append(UserVO userVo, String newLastName, String password, StringBuilder sb) {
+		if(sb.length() > 0) {
+			sb.append("\\n");
+		}
+		sb.append(userVo.getLogin()).append("	")
+		  .append(password).append("	")
+		  .append("de").append("	")
+		  .append(userVo.getFirstName()).append("	");
+		if(newLastName != null) {
+			sb.append(newLastName).append("	");
+			userVo.setLastName(newLastName);
+		} else {
+			sb.append(userVo.getLastName()).append("	");
+		}
+		sb.append(userVo.getEmail()).append("	")
+		  .append("").append("	")
+		  .append("").append("	")
+		  .append("");
+		return userVo;
+	}
+	
 	public ImportUserPage assertGreen(int numOfGreen) {
 		By greenBy = By.cssSelector(".o_dnd_label i.o_icon_new");
 		List<WebElement> greenEls = browser.findElements(greenBy);
@@ -108,6 +132,14 @@ public class ImportUserPage {
 		return this;
 	}
 	
+	public ImportUserPage assertWarn(int numOfWarns) {
+		By warnBy = By.cssSelector(".o_dnd_label i.o_icon_warn");
+		List<WebElement> warnEls = browser.findElements(warnBy);
+		Assert.assertEquals(numOfWarns, warnEls.size());
+		return this;
+		
+	}
+	
 	/**
 	 * Next
 	 * @return this
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 22ba3c1efe2b803bddf8251d919ffe2caae20f74..5cf77c00e057cd5e9b8fdeaf5f9923b48507ffa8 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -141,6 +141,7 @@ import org.junit.runners.Suite;
 	org.olat.modules.wiki.gui.components.wikiToHtml.FilterUtilTest.class,
 	org.olat.modules.coach.manager.CoachingDAOTest.class,
 	org.olat.modules.coach.CoachingLargeTest.class,
+	org.olat.modules.reminder.ReminderModuleTest.class,
 	org.olat.modules.reminder.manager.ReminderDAOTest.class,
 	org.olat.modules.reminder.manager.ReminderRuleEngineTest.class,
 	org.olat.properties.PropertyTest.class,