diff --git a/src/main/java/org/olat/admin/user/tools/UserToolExtension.java b/src/main/java/org/olat/admin/user/tools/UserToolExtension.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c4f4648685e08ca83d8e92679435bb0fbd21938
--- /dev/null
+++ b/src/main/java/org/olat/admin/user/tools/UserToolExtension.java
@@ -0,0 +1,49 @@
+/**
+ * <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.admin.user.tools;
+
+import org.olat.core.extensions.action.GenericActionExtension;
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 22.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class UserToolExtension extends GenericActionExtension {
+	
+	private String uniqueExtensionID;
+
+	@Override
+	public String getUniqueExtensionID() {
+		String id;
+		if(StringHelper.containsNonWhitespace(uniqueExtensionID)) {
+			id = uniqueExtensionID;
+		} else {
+			id = super.getUniqueExtensionID();
+		}
+		return id;
+	}
+
+	public void setUniqueExtensionID(String uniqueExtensionID) {
+		this.uniqueExtensionID = uniqueExtensionID;
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties
index bb05644df9e5be147edb388cbfca0c32d4e164d2..eefa93270e9e9dd5bceaba0c07068802c6e34ead 100644
--- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties
@@ -5,6 +5,7 @@ CalendarManager=Kalender
 CalendarManager.course=Kurs-Kalender
 CalendarManager.group=Gruppen-Kalender
 CatalogEntry=Katalogeintrag
+CertificatesManager=Zertifikate
 CourseBb=Kursbaustein
 CourseModule=Kurs
 DENCourseNode=Terminvergabe
diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
index c63ca562c0c79ffece9635b4d0c3ccb293322d5a..4cabd9a4240b1233ad0129c8710f945b02ffb36b 100644
--- a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
+++ b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
@@ -102,6 +102,9 @@
 		<class>org.olat.course.assessment.model.UserCourseInfosImpl</class>
 		<class>org.olat.course.nodes.cl.model.DBCheckbox</class>
 		<class>org.olat.course.nodes.cl.model.DBCheck</class>
+		<class>org.olat.course.certificate.model.CertificateImpl</class>
+		<class>org.olat.course.certificate.model.CertificateLightImpl</class>
+		<class>org.olat.course.certificate.model.CertificateTemplateImpl</class>
 		<class>org.olat.group.model.ContactView</class>
 		<class>org.olat.group.model.ContactViewExtended</class>
 		<class>org.olat.group.model.BusinessGroupMembershipViewImpl</class>
diff --git a/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml b/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml
index 0b9a2db5ff0ff0a88d437b1372c6ab651e180181..0c57882adf38d8a4ba0a66205f21f3028cee4ea3 100644
--- a/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml
+++ b/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml
@@ -52,6 +52,9 @@
 				<entry key="/blog/">
 					<ref bean="blogMediaBean" />
 				</entry>
+				<entry key="/certificate/">
+					<ref bean="certificateBean" />
+				</entry>
 			</map>
 		</property>
 	</bean>
@@ -135,7 +138,11 @@
 		<constructor-arg index="0">
 			<value>false</value>
 		</constructor-arg>
-	</bean>	
+	</bean>
+	
+	<bean id="certificateBean" class="org.olat.course.certificate.CertificateDispatcher">
+		<property name="certificatesManager" ref="certificatesManager" />
+	</bean>
 	
 	<!--  provides the possibility to log in using a remote loginform, e.g. from a portal.
 		The follwing requirements are given:
diff --git a/src/main/java/org/olat/core/extensions/action/GenericActionExtension.java b/src/main/java/org/olat/core/extensions/action/GenericActionExtension.java
index 435e02a33f45adf5c5156df996b0da3513751801..31aa6bb46f203319617a8895f4e65c66db1e3c74 100644
--- a/src/main/java/org/olat/core/extensions/action/GenericActionExtension.java
+++ b/src/main/java/org/olat/core/extensions/action/GenericActionExtension.java
@@ -97,7 +97,7 @@ public class GenericActionExtension extends AbstractExtension implements ActionE
 		return node;
 	}
 	
-	
+	@Override
 	public String getUniqueExtensionID(){
 		StringBuilder sb = new StringBuilder();
 		if (extensionPoints != null){
diff --git a/src/main/java/org/olat/core/gui/components/table/MultiSelectColumnDescriptor.java b/src/main/java/org/olat/core/gui/components/table/MultiSelectColumnDescriptor.java
index 735457dca9075d3686f71b44c536bc8c1de08d59..d70d6663428b4dc27dc74fc3bf87e13d40b662f6 100644
--- a/src/main/java/org/olat/core/gui/components/table/MultiSelectColumnDescriptor.java
+++ b/src/main/java/org/olat/core/gui/components/table/MultiSelectColumnDescriptor.java
@@ -128,7 +128,7 @@ class MultiSelectColumnDescriptor implements ColumnDescriptor {
 	}
 
 	public int getAlignment() {
-		return ColumnDescriptor.ALIGNMENT_CENTER;
+		return ColumnDescriptor.ALIGNMENT_LEFT;
 	}
 
 	public String getAction(final int row) {
diff --git a/src/main/java/org/olat/course/_spring/courseContext.xml b/src/main/java/org/olat/course/_spring/courseContext.xml
index 787cb666cdfb9690647c0723261755b9de18dc41..5f1c0a997bc1daefb4009821e2e6ef1ecab74bbf 100644
--- a/src/main/java/org/olat/course/_spring/courseContext.xml
+++ b/src/main/java/org/olat/course/_spring/courseContext.xml
@@ -1,9 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:context="http://www.springframework.org/schema/context" 
 	xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
-  http://www.springframework.org/schema/beans/spring-beans.xsd">
+  http://www.springframework.org/schema/beans/spring-beans.xsd
+  http://www.springframework.org/schema/context 
+  http://www.springframework.org/schema/context/spring-context.xsd">
+	
+	<context:component-scan base-package="org.olat.course.certificate.manager" />
 
 	<import resource="classpath:/org/olat/course/assessment/_spring/assessmentContext.xml"/>
 	<import resource="classpath:/org/olat/course/config/ui/courselayout/_spring/courseLayoutContext.xml"/>
@@ -18,60 +23,59 @@
 	<import resource="classpath:/org/olat/course/statistic/_spring/statisticContext.xml"/>
 	<import resource="classpath:/org/olat/course/statistic/_spring/statisticsJobContext.xml"/>
 
-<bean id="courseModule" class="org.olat.course.CourseModule" 
-	depends-on="userModule, triggerUserModuleInit, org.olat.core.util.WebappHelper, metaInfoFactory, fileresourceManager">
-		
-	<constructor-arg index="0" ref="coordinatorManager" />
-	<constructor-arg index="1" ref="propertyManager" />
-	<constructor-arg index="2" ref="courseFactory" />
-	<constructor-arg index="3" ref="repositoryService" />
-	<constructor-arg index="4" ref="resourceManager" />
-	
-	<property name="persistedProperties">
-	  <bean class="org.olat.core.configuration.PersistedProperties" scope="prototype" init-method="init" destroy-method="destroy">
-	    <constructor-arg index="0" ref="coordinatorManager"/>
-	    <constructor-arg index="1" ref="courseModule" />
-	  </bean>
-	</property>
-
-	<!-- Select exported courses to be deployed automatically.  See property (DeployCourseExportsEnabled) below.
-		Default access is 4:
-			value=1 : Access only for owners (B)
-			value=2 : Access only for owners and authors (BA)
-			value=3 : Access for all registered users (BAR)
-			value=4 : Access for all registered users and anonymous guests
+	<bean id="courseModule" class="org.olat.course.CourseModule" 
+		depends-on="userModule, triggerUserModuleInit, org.olat.core.util.WebappHelper, metaInfoFactory, fileresourceManager">
 		
-		Be increasing the version with the same identifier, the repo entry gets deleted and the new version gets deployed
-		If you changing the identifier there will be a new course deployed and the old one will stay.
-	-->
-	<property name="courseExportFiles">
-		<ref bean="deployedCourseList"/>
-	</property>
+		<constructor-arg index="0" ref="coordinatorManager" />
+		<constructor-arg index="1" ref="propertyManager" />
+		<constructor-arg index="2" ref="courseFactory" />
+		<constructor-arg index="3" ref="repositoryService" />
+		<constructor-arg index="4" ref="resourceManager" />
 	
+		<property name="persistedProperties">
+			<bean class="org.olat.core.configuration.PersistedProperties" scope="prototype" init-method="init" destroy-method="destroy">
+				<constructor-arg index="0" ref="coordinatorManager"/>
+				<constructor-arg index="1" ref="courseModule" />
+			</bean>
+		</property>
 	
-	<!-- Three Logs are available within a course:
-		- Adminlog, logs administrative activity in the course
-		- Userlog, logs the course user activity, where the user is identifiable
-		- Statisticlog, logs the course user activity, where the user is anonymized
-		The following switches define which of the above mentioned log files are
-		visible to the course author. Hence she can configure log levels or delete the
-		respective log file(s).
-		
-		It is clear, that disabling the course logging overrides these settings.
-		
-		Possible values are VISIBLE, INVISIBLE
-		
-		In the case of absent configuration values default to INVISIBLE.
+		<!-- Select exported courses to be deployed automatically.  See property (DeployCourseExportsEnabled) below.
+			Default access is 4:
+				value=1 : Access only for owners (B)
+				value=2 : Access only for owners and authors (BA)
+				value=3 : Access for all registered users (BAR)
+				value=4 : Access for all registered users and anonymous guests
+			
+			Be increasing the version with the same identifier, the repo entry gets deleted and the new version gets deployed
+			If you changing the identifier there will be a new course deployed and the old one will stay.
 		-->
-	<property name="logVisibilityForCourseAuthor">
-		<map >
-			<entry key="AdminLog" value="VISIBLE" />
-			<entry key="UserLog" value="INVISIBLE" />
-			<entry key="StatisticLog" value="VISIBLE" />
-		</map>
-	</property>
+		<property name="courseExportFiles">
+			<ref bean="deployedCourseList"/>
+		</property>
+
 	
-</bean>
+		<!-- Three Logs are available within a course:
+			- Adminlog, logs administrative activity in the course
+			- Userlog, logs the course user activity, where the user is identifiable
+			- Statisticlog, logs the course user activity, where the user is anonymized
+			The following switches define which of the above mentioned log files are
+			visible to the course author. Hence she can configure log levels or delete the
+			respective log file(s).
+			
+			It is clear, that disabling the course logging overrides these settings.
+			
+			Possible values are VISIBLE, INVISIBLE
+			
+			In the case of absent configuration values default to INVISIBLE.
+			-->
+		<property name="logVisibilityForCourseAuthor">
+			<map >
+				<entry key="AdminLog" value="VISIBLE" />
+				<entry key="UserLog" value="INVISIBLE" />
+				<entry key="StatisticLog" value="VISIBLE" />
+			</map>
+		</property>
+	</bean>
 				
 	<bean id="courseFactory" class="org.olat.course.CourseFactory"
 		depends-on="businessGroupService,resourceManager,baseSecurityManager,glossaryManager">
@@ -80,34 +84,28 @@
 		<constructor-arg index="2" ref="repositoryService" />
 		<constructor-arg index="3" ref="referenceManager" />
 	</bean>
-
                 
-<bean  class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
-	<property name="targetObject" ref="courseModule" />
-	<property name="targetMethod" value="init" />
-	<property name="arguments">
+	<bean  class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+		<property name="targetObject" ref="courseModule" />
+		<property name="targetMethod" value="init" />
+		<property name="arguments">
 		<value>
-		<!--
-			If the Instant Messaging is enabled you can switch here the availability of the course chat.
-			So chatting can be used generally and in groups but disabled in the course.
-		-->
-		enableCourseChat=true
-		
-		<!-- Select exported demo courses to be deployed automatically. -->
-		deployCourseExportsEnabled=${deploy.course.exports}
-		
-		<!-- whether the toolbox should display the current participant count or not -->
-		displayParticipantsCount=${course.display.participants.count}
-
-		<!-- Show or hide the help course in the top navigation -->		
-		helpCourseEnabled = ${help.course.enabled}
-
-		<!-- The soft-key of the repository entry of the help course, empty to disable help course -->
-		helpCourseSoftKey = ${help.course.softkey}
-		
-		</value>
-	</property>
-</bean>
+			<!--
+				If the Instant Messaging is enabled you can switch here the availability of the course chat.
+				So chatting can be used generally and in groups but disabled in the course.
+			-->
+			enableCourseChat=true
+			<!-- Select exported demo courses to be deployed automatically. -->
+			deployCourseExportsEnabled=${deploy.course.exports}
+			<!-- whether the toolbox should display the current participant count or not -->
+			displayParticipantsCount=${course.display.participants.count}
+			<!-- Show or hide the help course in the top navigation -->		
+			helpCourseEnabled = ${help.course.enabled}
+			<!-- The soft-key of the repository entry of the help course, empty to disable help course -->
+			helpCourseSoftKey = ${help.course.softkey}
+			</value>
+		</property>
+	</bean>
 
 	<bean id="deployedCourseList" class="java.util.ArrayList">
 	    <constructor-arg>
@@ -123,6 +121,7 @@
 		<property name="identifier" value="Demo Kurs" />
 		<property name="version" value="7.1" />
 	</bean>
+	
 	<!-- this one is the help course which will server as the online help, helpAsser flag is TRUE! -->
 	<bean id="deployedCourse_OpenOLATHelp" class="org.olat.course.DeployableCourseExport">
 		<property name="courseUrl" value="http://nexus.openolat.org/nexus/content/repositories/openolat/org/openolat/help/OpenOLAT-Help/8.1/OpenOLAT-Help-8.1.zip" />
@@ -137,5 +136,25 @@
 			Have also a look at the ContextHelpModule configuration.
 		--> 
 	</bean>
+	
+	<!-- Business group admin. panel -->
+	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
+		<property name="order" value="8206" />
+		<property name="actionController">	
+			<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
+				<property name="className" value="org.olat.course.certificate.ui.CertificatesAdminController"/>
+			</bean>
+		</property>
+		<property name="navigationKey" value="certificates" />
+		<property name="i18nActionKey" value="admin.menu.title"/>
+		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
+		<property name="translationPackage" value="org.olat.course.certificate.ui"/>
+		<property name="parentTreeNodeIdentifier" value="modulesParent" /> 
+		<property name="extensionPoints">
+			<list>	
+				<value>org.olat.admin.SystemAdminMainController</value>		
+			</list>
+		</property>
+	</bean>
 
 </beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java b/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java
index d65451b0976e2f83dfb6844c91456e6be766b76d..598271d4292a7cd8793b452af768253e36490be4 100644
--- a/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java
+++ b/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 import org.olat.core.gui.components.table.BooleanColumnDescriptor;
 import org.olat.core.gui.components.table.ColumnDescriptor;
@@ -39,6 +40,8 @@ import org.olat.core.gui.components.table.TableController;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
 import org.olat.core.util.Util;
+import org.olat.course.certificate.CertificateLight;
+import org.olat.course.certificate.ui.DownloadCertificateCellRenderer;
 import org.olat.course.nodes.AssessableCourseNode;
 import org.olat.course.nodes.STCourseNode;
 import org.olat.course.nodes.ta.StatusForm;
@@ -69,6 +72,7 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 	private static final String COL_STATUS = "status";
 	private static final String COL_INITIAL_LAUNCH = "initialLaunch";
 	private static final String COL_LAST_SCORE_DATE = "lastScoreDate";
+	private static final String COL_CERTIFICATE = "certificate";
 
 	private List<String> colMapping;	
 	private List<String> userPropertyNameList;
@@ -76,6 +80,8 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 	private final List<UserPropertyHandler> userPropertyHandlers;
 	private static final String usageIdentifyer = AssessedIdentitiesTableDataModel.class.getCanonicalName();
 	private final Translator translator;
+	
+	private Map<Long, CertificateLight> certificates;
 
 
 	/**
@@ -85,11 +91,13 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 	 * @param locale
 	 * @param isAdministrativeUser
 	 */
-	public AssessedIdentitiesTableDataModel(List<AssessedIdentityWrapper> objects, AssessableCourseNode courseNode, Locale locale, boolean isAdministrativeUser) {
+	public AssessedIdentitiesTableDataModel(List<AssessedIdentityWrapper> objects, Map<Long, CertificateLight> certificates,
+			AssessableCourseNode courseNode, Locale locale, boolean isAdministrativeUser) {
 		super(objects);
 		this.courseNode = courseNode;		
 		this.setLocale(locale);
 		this.translator = Util.createPackageTranslator(this.getClass(), locale);
+		this.certificates = certificates;
 		
 		this.isAdministrativeUser = isAdministrativeUser;
 		userPropertyHandlers = UserManager.getInstance().getUserPropertyHandlersFor(usageIdentifyer, isAdministrativeUser);
@@ -118,7 +126,6 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 			}
 			if (courseNode.hasScoreConfigured()) {
 				colMapping.add(colCount++, COL_SCORE);
-				//fxdiff VCRP-4: assessment overview with max score
 				colMapping.add(colCount++, COL_MINSCORE);
 				colMapping.add(colCount++, COL_MAXSCORE);
 			}
@@ -128,9 +135,15 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 			if (courseNode.hasPassedConfigured()) {
 				colMapping.add(colCount++, COL_PASSED);			
 			}
-			colMapping.add(colCount++, COL_INITIAL_LAUNCH);		
-			colMapping.add(colCount++, COL_LAST_SCORE_DATE);		
+			colMapping.add(colCount++, COL_INITIAL_LAUNCH);
+			colMapping.add(colCount++, COL_LAST_SCORE_DATE);
 		}
+
+		colMapping.add(colCount++, COL_CERTIFICATE);
+	}
+	
+	public void setCertificates(Map<Long, CertificateLight> certificates) {
+		this.certificates = certificates;
 	}
 
 	/**
@@ -152,28 +165,29 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 		// lookup the column name first and 
 		// deliver value based on the column name
 		String colName = colMapping.get(col);
-		if (colName.equals(COL_NAME)) return identity.getName();
-		else if (userPropertyNameList.contains(colName)) return identity.getUser().getProperty(colName, getLocale());		
-		else if (colName.equals(COL_DETAILS)) return wrappedIdentity.getDetailsListView();
-		else if (colName.equals(COL_ATTEMPTS)) return wrappedIdentity.getNodeAttempts();
-  	else if (colName.equals(COL_SCORE)) {
+		if (colName.equals(COL_NAME)) {
+			return identity.getName();
+		} else if (userPropertyNameList.contains(colName)) {
+			return identity.getUser().getProperty(colName, getLocale());
+		} else if (colName.equals(COL_DETAILS)) {
+			return wrappedIdentity.getDetailsListView();
+		} else if (colName.equals(COL_ATTEMPTS)) {
+			return wrappedIdentity.getNodeAttempts();
+		} else if (colName.equals(COL_SCORE)) {
 			ScoreEvaluation scoreEval = wrappedIdentity.getUserCourseEnvironment().getScoreAccounting().evalCourseNode(courseNode);
 			if (scoreEval == null) scoreEval = new ScoreEvaluation(null, null);
-		//fxdiff VCRP-4: assessment overview with max score
 			return scoreEval.getScore();
-  	} else if (colName.equals(COL_MINSCORE)) {
-  	//fxdiff VCRP-4: assessment overview with max score
+		} else if (colName.equals(COL_MINSCORE)) {
 			if(!(courseNode instanceof STCourseNode)) {
 				return courseNode.getMinScoreConfiguration();
 			}
 			return "";
-  	} else if (colName.equals(COL_MAXSCORE)) {
-  	//fxdiff VCRP-4: assessment overview with max score
+		} else if (colName.equals(COL_MAXSCORE)) {
 			if(!(courseNode instanceof STCourseNode)) {
 				return courseNode.getMaxScoreConfiguration();
 			}
 			return "";
-  	}	else if (colName.equals(COL_STATUS)) {
+		}	else if (colName.equals(COL_STATUS)) {
 			return getStatusFor(courseNode, wrappedIdentity);
 		} else if (colName.equals(COL_PASSED)) {
 			ScoreEvaluation scoreEval = wrappedIdentity.getUserCourseEnvironment().getScoreAccounting().evalCourseNode(courseNode);
@@ -183,7 +197,15 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 			return wrappedIdentity.getInitialLaunchDate();
 		} else if (colName.equals(COL_LAST_SCORE_DATE)) {
 			return wrappedIdentity.getLastModified();
-		} else return "error";
+		} else if(colName.equals(COL_CERTIFICATE)) {
+			CertificateLight certificate = null;
+			if(certificates != null) {
+				certificate = certificates.get(identity.getKey());
+			}
+			return certificate;
+		} else {
+			return "error";
+		}
 	}
 
 	/**
@@ -213,7 +235,7 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 	 * @param actionCommand
 	 * @param isNodeOrGroupFocus
 	 */
-	public void addColumnDescriptors(TableController userListCtr, String actionCommand, boolean isNodeOrGroupFocus) {
+	public void addColumnDescriptors(TableController userListCtr, String actionCommand, boolean isNodeOrGroupFocus, boolean certificate) {
 		String editCmd = null;
 		if (courseNode == null || courseNode.isEditableConfigured()) {
 			editCmd = actionCommand; // only selectable if editable
@@ -226,13 +248,11 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 		
 		for (int i = 0; i < userPropertyHandlers.size(); i++) {
 			UserPropertyHandler userPropertyHandler	= userPropertyHandlers.get(i);
-			//fxdiff FXOLAT-108: improve results table of tests
 			boolean visible = UserManager.getInstance().isMandatoryUserProperty(usageIdentifyer , userPropertyHandler);
 			userListCtr.addColumnDescriptor(visible, userPropertyHandler.getColumnDescriptor(colCount++, editCmd, getLocale()));	
 		}		
 		if ( (courseNode != null) && isNodeOrGroupFocus) {			
 			if (courseNode.hasDetails()) {
-			//fxdiff VCRP-4: assessment overview with max score
 				String headerKey = courseNode.getDetailsListViewHeaderKey();
 				userListCtr.addColumnDescriptor((headerKey == null ? false : true), 
 						new DefaultColumnDescriptor(headerKey == null ? "table.header.details" : headerKey, colCount++, null, getLocale()));
@@ -243,7 +263,6 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 			if (courseNode.hasScoreConfigured()) {				
 				userListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT,
 						new ScoreCellRenderer()));
-			//fxdiff VCRP-4: assessment overview with max score
 				userListCtr.addColumnDescriptor(false, new CustomRenderColumnDescriptor("table.header.min", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT,
 						new ScoreCellRenderer()));
 				userListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.max", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT,
@@ -259,5 +278,11 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse
 			userListCtr.addColumnDescriptor(false, new DefaultColumnDescriptor("table.header.initialLaunchDate", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT));
 			userListCtr.addColumnDescriptor(false, new DefaultColumnDescriptor("table.header.lastScoreDate", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT));
 		}
+		
+		if(certificate) {
+			CustomRenderColumnDescriptor statColdesc = new CustomRenderColumnDescriptor("table.header.certificate", colCount++, null, getLocale(),
+					ColumnDescriptor.ALIGNMENT_LEFT, new DownloadCertificateCellRenderer());
+			userListCtr.addColumnDescriptor(statColdesc);
+		}
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/assessment/AssessmentMainController.java b/src/main/java/org/olat/course/assessment/AssessmentMainController.java
index c749cc74653ec5203c9c1363949d6df84b6cc551..759bde6d6dc83e843b217b017eddfb68f9f1a139 100644
--- a/src/main/java/org/olat/course/assessment/AssessmentMainController.java
+++ b/src/main/java/org/olat/course/assessment/AssessmentMainController.java
@@ -26,7 +26,10 @@
 package org.olat.course.assessment;
 
 import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -43,6 +46,9 @@ import org.olat.basesecurity.GroupRoles;
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
 import org.olat.core.commons.persistence.DBFactory;
+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.ShortName;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
@@ -60,6 +66,7 @@ import org.olat.core.gui.components.table.TableController;
 import org.olat.core.gui.components.table.TableDataModel;
 import org.olat.core.gui.components.table.TableEvent;
 import org.olat.core.gui.components.table.TableGuiConfiguration;
+import org.olat.core.gui.components.table.TableMultiSelectEvent;
 import org.olat.core.gui.components.tree.GenericTreeModel;
 import org.olat.core.gui.components.tree.GenericTreeNode;
 import org.olat.core.gui.components.tree.MenuTree;
@@ -72,6 +79,8 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.MainLayoutBasicController;
 import org.olat.core.gui.control.generic.dtabs.Activateable2;
 import org.olat.core.gui.control.generic.messages.MessageUIFactory;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
 import org.olat.core.id.IdentityEnvironment;
@@ -84,6 +93,7 @@ import org.olat.core.logging.OLATSecurityException;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.logging.activity.ActionType;
+import org.olat.core.util.StringHelper;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.core.util.tree.TreeHelper;
@@ -92,6 +102,11 @@ import org.olat.course.ICourse;
 import org.olat.course.assessment.NodeTableDataModel.Cols;
 import org.olat.course.assessment.bulk.BulkAssessmentOverviewController;
 import org.olat.course.assessment.manager.UserCourseInformationsManager;
+import org.olat.course.certificate.CertificateLight;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.PDFCertificatesOptions;
+import org.olat.course.certificate.model.CertificateInfos;
 import org.olat.course.condition.Condition;
 import org.olat.course.condition.interpreter.ConditionExpression;
 import org.olat.course.condition.interpreter.OnlyGroupConditionInterpreter;
@@ -104,6 +119,7 @@ import org.olat.course.nodes.ProjectBrokerCourseNode;
 import org.olat.course.nodes.STCourseNode;
 import org.olat.course.properties.CoursePropertyManager;
 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.group.BusinessGroup;
@@ -111,7 +127,9 @@ import org.olat.group.BusinessGroupService;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
 import org.olat.repository.RepositoryService;
+import org.olat.resource.OLATResource;
 import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
 
 import de.bps.onyx.plugin.OnyxModule;
 import de.bps.webservices.clients.onyxreporter.OnyxReporterConnector;
@@ -167,6 +185,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 
 	// Course assessment notification support fields	
 	private Controller csc;
+	private Controller certificateSubscriptionCtrl;
 	
 	// Hash map to keep references to already created user course environments
 	// Serves as a local cache to reduce database access - not shared by multiple threads
@@ -201,7 +220,8 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 	private boolean isFiltering = true;
 	private Link showAllCourseNodesButton;
 	private Link filterCourseNodesButton;
-	
+
+	private DialogBoxController confirmCertificateCtrl;
 	private EfficiencyStatementAssessmentController esac;
 	private BulkAssessmentOverviewController bulkAssOverviewCtrl;
 	private final TooledStackedPanel stackPanel;
@@ -209,9 +229,16 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 	private RepositoryEntry re;
 	private OLATResourceable ores;
 	
-	private final OnyxModule onyxModule;
-	private final RepositoryService repositoryService;
-	private final BusinessGroupService businessGroupService;
+	@Autowired
+	private OnyxModule onyxModule;
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private RepositoryService repositoryService;
+	@Autowired
+	private BusinessGroupService businessGroupService;
+	@Autowired
+	private CertificatesManager certificatesManager;
 	
 	/**
 	 * Constructor for the assessment tool controller. 
@@ -223,11 +250,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 	public AssessmentMainController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
 			OLATResourceable ores, IAssessmentCallback assessmentCallback) {
 		super(ureq, wControl);	
-		
-		onyxModule = CoreSpringFactory.getImpl(OnyxModule.class);
-		businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class);
-		repositoryService = CoreSpringFactory.getImpl(RepositoryService.class);
-		
+
 		getUserActivityLogger().setStickyActionType(ActionType.admin);
 		this.stackPanel = stackPanel;
 		this.ores = ores;
@@ -270,7 +293,18 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 				listenTo(csc); // cleanup on dispose
 				index.put("assessmentSubscription", csc.getInitialComponent());
 			}
+		}
+		
+		SubscriptionContext subsContext = certificatesManager.getSubscriptionContext(course);
+		if (subsContext != null) {
+			String businessPath = wControl.getBusinessControl().getAsString();
+			PublisherData pData = certificatesManager.getPublisherData(course, businessPath);
+			certificateSubscriptionCtrl = new ContextualSubscriptionController(ureq, wControl, subsContext, pData);
+			listenTo(certificateSubscriptionCtrl);
+			index.put("certificationSubscription", certificateSubscriptionCtrl.getInitialComponent());
+		}
 			
+		if (hasAssessableNodes) {
 			// Wrapper container: adds header
 			wrapper = createVelocityContainer("wrapper");
 			
@@ -328,7 +362,6 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 			//fill the user list for the 
 			this.mode = MODE_USERFOCUS;
 			this.identitiesList = getAllAssessableIdentities();
-			//fxdiff FXOLAT-108: improve results table of tests
 			doUserChooseWithData(ureq, identitiesList, null, null);
 			
 			GenericTreeModel menuTreeModel = (GenericTreeModel)menuTree.getTreeModel();
@@ -527,6 +560,27 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 					currentCourseNode = null;
 				}
 				doUserChooseWithData(ureq, identitiesList, currentGroup, currentCourseNode);
+			} else if (event instanceof  TableMultiSelectEvent) {
+				TableMultiSelectEvent tms = (TableMultiSelectEvent)event;
+				BitSet selectedUsers = tms.getSelection();
+				if(selectedUsers.isEmpty()) {
+					showWarning("select.one.user.warning");
+				} else {
+					ICourse course = CourseFactory.loadCourse(ores);
+					CourseNode rootNode = course.getRunStructure().getRootNode();
+					List<CertificateInfos> futureCertififedList = new ArrayList<>();
+					for (int i=selectedUsers.nextSetBit(0); i >= 0; i=selectedUsers.nextSetBit(i+1)) {
+						Object row = userListCtr.getTableDataModel().getObject(i);
+						if(row instanceof AssessedIdentityWrapper) {
+							AssessedIdentityWrapper wrapper = (AssessedIdentityWrapper)row;
+							ScoreEvaluation scoreEval = wrapper.getUserCourseEnvironment().getScoreAccounting().getScoreEvaluation(rootNode);
+							Float score = scoreEval == null ? null : scoreEval.getScore();
+							Boolean passed = scoreEval == null ? null : scoreEval.getPassed();
+							futureCertififedList.add(new CertificateInfos(wrapper.getIdentity(), score, passed));
+						}
+					}
+					doGenerateCertificate(ureq, futureCertififedList);
+				}
 			}
 		}
 		else if (source == nodeListCtr) {
@@ -585,12 +639,20 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 				}
 			} // else nothing special to do
 			setContent(userChoose);
-		}
-		else if (source == identityAssessmentController) {
+		} else if (source == identityAssessmentController) {
 			if (event.equals(Event.CANCELLED_EVENT)) {
 				setContent(userChoose);
 			}
-	  }
+		} else if(source == confirmCertificateCtrl) {
+			if(DialogBoxUIFactory.isYesEvent(event)) {
+				@SuppressWarnings("unchecked")
+				List<CertificateInfos> assessedIdentitiesInfos = (List<CertificateInfos>)confirmCertificateCtrl.getUserObject();
+				doGenerateCertificates(assessedIdentitiesInfos);
+				Map<Long, CertificateLight> certificates = getCertificates(CourseFactory.loadCourse(ores));
+				((AssessedIdentitiesTableDataModel)userListCtr.getTableDataModel()).setCertificates(certificates);
+				userListCtr.modelChanged();
+			}
+		}
 	}
 
 	/**
@@ -844,9 +906,18 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 			return;
 		}
 		
+		Map<Long, CertificateLight> certificates;
+		boolean certificateEnabled = PDFCertificatesOptions.none != course.getCourseConfig().getPdfCertificateOption();
+		boolean showCertificate = certificateEnabled && mode == MODE_USERFOCUS;
+		if(showCertificate) {
+			certificates = getCertificates(course);
+		} else {
+			certificates = Collections.emptyMap();
+		}
+		
 		// Add the wrapped identities to the table data model
-		AssessedIdentitiesTableDataModel tdm = new AssessedIdentitiesTableDataModel(wrappedIdentities, courseNode, getLocale(), isAdministrativeUser);
-		tdm.addColumnDescriptors(userListCtr, CMD_CHOOSE_USER, mode == MODE_NODEFOCUS || mode == MODE_GROUPFOCUS || mode == MODE_USERFOCUS);
+		AssessedIdentitiesTableDataModel tdm = new AssessedIdentitiesTableDataModel(wrappedIdentities, certificates,  courseNode, getLocale(), isAdministrativeUser);
+		tdm.addColumnDescriptors(userListCtr, CMD_CHOOSE_USER, mode == MODE_NODEFOCUS || mode == MODE_GROUPFOCUS || mode == MODE_USERFOCUS, showCertificate);
 		userListCtr.setTableDataModel(tdm);
 		
 		List<String> toolCmpNames = new ArrayList<>(3);
@@ -871,6 +942,12 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 				}
 			}
 		}
+		if(certificateEnabled) {
+			if(courseNode == null || courseNode == course.getRunStructure().getRootNode()) {
+				userListCtr.addMultiSelectAction("create.certificate", "create.certificate");
+				userListCtr.setMultiSelect(true);
+			}
+		}
 		userChoose.contextPut("toolCmpNames", toolCmpNames);
 
 		if (mode == MODE_USERFOCUS) {
@@ -905,6 +982,19 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 		// set main vc to userchoose
 		setContent(userChoose);
 	}
+	
+	private Map<Long, CertificateLight> getCertificates(ICourse course) {
+		Map<Long, CertificateLight> certificates = new HashMap<>();
+		OLATResource resource = course.getCourseEnvironment().getCourseGroupManager().getCourseResource();
+		List<CertificateLight> certificateList = certificatesManager.getCertificates(resource);
+		for(CertificateLight certificate:certificateList) {
+			CertificateLight currentCertificate = certificates.get(certificate.getIdentityKey());
+			if(currentCertificate == null || currentCertificate.getCreationDate().before(certificate.getCreationDate())) {
+				certificates.put(certificate.getIdentityKey(), certificate);
+			}
+		}
+		return certificates;
+	}
 
 	private void doNodeChoose(UserRequest ureq) {
 		ICourse course = CourseFactory.loadCourse(ores);
@@ -984,6 +1074,30 @@ public class AssessmentMainController extends MainLayoutBasicController implemen
 		listenTo(esac);
 		main.setContent(esac.getInitialComponent());
 	}
+	
+	private void doGenerateCertificates(List<CertificateInfos> assessedIdentitiesInfos) {
+		ICourse course = CourseFactory.loadCourse(ores);
+		RepositoryEntry resource = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		String templateKey = course.getCourseConfig().getPdfCertificateTemplate();
+		if(StringHelper.isLong(templateKey)) {
+			CertificateTemplate template = certificatesManager.getTemplateById(new Long(templateKey));
+			certificatesManager.generateCertificates(assessedIdentitiesInfos, resource, template);
+		}
+	}
+	
+	private void doGenerateCertificate(UserRequest ureq, List<CertificateInfos> assessedIdentitiesInfos) {
+		StringBuilder sb = new StringBuilder();
+		for(CertificateInfos infos:assessedIdentitiesInfos) {
+			String name = userManager.getUserDisplayName(infos.getAssessedIdentity());
+			if(sb.length() > 0) sb.append(", ");
+			sb.append(name);
+		}
+		
+		String title = translate("confirm.certificate.title");
+		String text = translate("confirm.certificate.description", new String[]{ sb.toString() });
+		confirmCertificateCtrl = activateYesNoDialog(ureq, title, text, confirmCertificateCtrl);
+		confirmCertificateCtrl.setUserObject(assessedIdentitiesInfos);
+	}
 
 	/**
 	 * Recursive method that adds assessable nodes and all its parents to a list
diff --git a/src/main/java/org/olat/course/assessment/EfficiencyStatementController.java b/src/main/java/org/olat/course/assessment/EfficiencyStatementController.java
deleted file mode 100644
index 03ebeb365a22998d6bab9d0901cbea5a813ee25d..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/course/assessment/EfficiencyStatementController.java
+++ /dev/null
@@ -1,317 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <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
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <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>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-
-package org.olat.course.assessment;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import org.olat.NewControllerFactory;
-import org.olat.core.CoreSpringFactory;
-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.components.velocity.VelocityContainer;
-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.gui.control.generic.closablewrapper.CloseableModalController;
-import org.olat.core.gui.control.generic.messages.MessageUIFactory;
-import org.olat.core.id.Identity;
-import org.olat.core.id.OLATResourceable;
-import org.olat.core.id.Roles;
-import org.olat.core.id.context.BusinessControl;
-import org.olat.core.id.context.BusinessControlFactory;
-import org.olat.core.id.context.ContextEntry;
-import org.olat.core.util.StringHelper;
-import org.olat.core.util.mail.ContactList;
-import org.olat.core.util.mail.ContactMessage;
-import org.olat.core.util.resource.OresHelper;
-import org.olat.core.util.vfs.VFSContainer;
-import org.olat.course.CourseFactory;
-import org.olat.course.ICourse;
-import org.olat.course.assessment.portfolio.EfficiencyStatementArtefact;
-import org.olat.group.BusinessGroup;
-import org.olat.modules.co.ContactFormController;
-import org.olat.portfolio.EPArtefactHandler;
-import org.olat.portfolio.PortfolioModule;
-import org.olat.portfolio.model.artefacts.AbstractArtefact;
-import org.olat.portfolio.ui.artefacts.collect.ArtefactWizzardStepsController;
-import org.olat.repository.RepositoryEntry;
-import org.olat.repository.RepositoryManager;
-import org.olat.user.UserManager;
-import org.olat.user.propertyhandlers.UserPropertyHandler;
-
-/**
- * Description:<br>
- * Displays the users efficiency statement
- * 
- * <P>
- * Initial Date:  11.08.2005 <br>
- * @author gnaegi
- */
-public class EfficiencyStatementController extends BasicController {
-	
-	private VelocityContainer userDataVC;
-	private static final String usageIdentifyer = EfficiencyStatementController.class.getCanonicalName();
-	
-	private final EfficiencyStatement efficiencyStatement;
-	private final Identity statementOwner;
-	private final Long businessGroupKey;
-	private final Long courseRepoKey;
-	
-	//to collect the eff.Statement as artefact
-	private Link collectArtefactLink, homeLink, courseLink, groupLink, contactLink;
-	private PortfolioModule portfolioModule;
-	// the collect-artefact-wizard
-	private Controller ePFCollCtrl;
-	//contact
-	private ContactFormController contactCtrl;
-	private CloseableModalController cmc;
-	
-	/**
-	 * The constructor shows the efficiency statement given as parameter for the current user
-	 * @param wControl
-	 * @param ureq
-	 * @param courseId
-	 */
-	public EfficiencyStatementController(WindowControl wControl, UserRequest ureq, EfficiencyStatement efficiencyStatement) {
-		this(wControl, ureq, ureq.getIdentity(), null, null, efficiencyStatement, false);
-	}
-	
-	/**
-	 * This constructor show the efficiency statement for the course repository key and the current user
-	 * @param wControl
-	 * @param ureq
-	 * @param courseRepoEntryKey
-	 */
-	public EfficiencyStatementController(WindowControl wControl, UserRequest ureq, Long courseRepoEntryKey) {
-		this(wControl, ureq, 
-				ureq.getIdentity(), null, RepositoryManager.getInstance().lookupRepositoryEntry(courseRepoEntryKey, false),
-				EfficiencyStatementManager.getInstance().getUserEfficiencyStatement(courseRepoEntryKey, ureq.getIdentity()), false);
-	}
-	
-	public EfficiencyStatementController(WindowControl wControl, UserRequest ureq, Identity statementOwner,
-			BusinessGroup businessGroup, RepositoryEntry courseRepo, EfficiencyStatement efficiencyStatement, boolean links) {
-		super(ureq, wControl);
-		
-		this.courseRepoKey = courseRepo == null ? (efficiencyStatement == null ? null : efficiencyStatement.getCourseRepoEntryKey()) : courseRepo.getKey();
-		if(courseRepo == null && courseRepoKey != null) {
-			courseRepo = RepositoryManager.getInstance().lookupRepositoryEntry(courseRepoKey, false);
-		}
-		if(businessGroup == null && courseRepo != null) {
-			ICourse course = CourseFactory.loadCourse(courseRepo.getOlatResource());
-			List<BusinessGroup> groups = course.getCourseEnvironment().getCourseGroupManager().getParticipatingBusinessGroups(statementOwner);
-			if(groups.size() > 0) {
-				businessGroup = groups.get(0);
-			}
-		}
-		this.businessGroupKey = businessGroup == null ? null : businessGroup.getKey();
-		this.statementOwner = statementOwner;
-		this.efficiencyStatement = efficiencyStatement;
-		init(ureq, statementOwner, courseRepo, businessGroup, links);
-	}
-		
-	private void init(UserRequest ureq, Identity statementOwner, RepositoryEntry courseRepo, BusinessGroup group, boolean links) { 
-		//extract efficiency statement data
-		//fallback translation for user properties 
-		setTranslator(UserManager.getInstance().getPropertyHandlerTranslator(getTranslator()));		
-		userDataVC = createVelocityContainer("efficiencystatement");
-		if(efficiencyStatement != null) {
-			userDataVC.contextPut("courseTitle", StringHelper.escapeHtml(efficiencyStatement.getCourseTitle()));
-			userDataVC.contextPut("date", StringHelper.formatLocaleDateTime(efficiencyStatement.getLastUpdated(), ureq.getLocale()));
-		} else if(courseRepo != null) {
-			userDataVC.contextPut("courseTitle", StringHelper.escapeHtml(courseRepo.getDisplayname()));
-		}
-		
-		if(courseRepoKey != null && links) {
-			courseLink = LinkFactory.createButton("course.link", userDataVC, this);
-			courseLink.setIconLeftCSS("o_icon o_CourseModule_icon");
-			userDataVC.put("course.link", courseLink);
-		}
-		
-		userDataVC.contextPut("user", statementOwner.getUser());			
-		userDataVC.contextPut("username", statementOwner.getName());
-		
-		Roles roles = ureq.getUserSession().getRoles();
-		boolean isAdministrativeUser = (roles.isAuthor() || roles.isGroupManager() || roles.isUserManager() || roles.isOLATAdmin());	
-		List<UserPropertyHandler> userPropertyHandlers = UserManager.getInstance().getUserPropertyHandlersFor(usageIdentifyer, isAdministrativeUser);
-		userDataVC.contextPut("userPropertyHandlers", userPropertyHandlers);
-
-		if(!getIdentity().equals(statementOwner) && links) {
-			homeLink = LinkFactory.createButton("home.link", userDataVC, this);
-			homeLink.setIconLeftCSS("o_icon o_icon_home");
-			userDataVC.put("home.link", homeLink);
-			
-			contactLink = LinkFactory.createButton("contact.link", userDataVC, this);
-			contactLink.setIconLeftCSS("o_icon o_icon_mail");
-			userDataVC.put("contact.link", contactLink);
-		}
-
-		if(group != null) {
-			userDataVC.contextPut("groupName", StringHelper.escapeHtml(group.getName()));
-			if(links) {
-				groupLink = LinkFactory.createButton("group.link", userDataVC, this);
-				groupLink.setIconLeftCSS("o_icon o_icon_group");
-				userDataVC.put("group.link", groupLink);
-			}
-		}
-		
-		if (efficiencyStatement != null) {
-			Controller identityAssessmentCtr = new IdentityAssessmentOverviewController(ureq, getWindowControl(), efficiencyStatement.getAssessmentNodes());
-			listenTo(identityAssessmentCtr);//dispose it when this one is disposed
-			userDataVC.put("assessmentOverviewTable", identityAssessmentCtr.getInitialComponent());
-			
-			//add link to collect efficiencyStatement as artefact
-			if(statementOwner.equals(ureq.getIdentity())) {
-				portfolioModule = (PortfolioModule) CoreSpringFactory.getBean("portfolioModule");
-				EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
-				if(portfolioModule.isEnabled() && artHandler != null && artHandler.isEnabled()) {
-					collectArtefactLink = LinkFactory.createCustomLink("collectArtefactLink", "collectartefact", "", Link.NONTRANSLATED, userDataVC, this);
-					collectArtefactLink.setIconLeftCSS("o_icon o_icon-lg o_icon_eportfolio_add");
-				}
-			}
-		} else {
-			//message, that no data is available. This may happen in the case the "open efficiency" link is available, while in the meantime an author
-			//disabled the efficiency statement.
-			String text = translate("efficiencystatement.nodata");
-			Controller messageCtr = MessageUIFactory.createSimpleMessage(ureq, getWindowControl(), text);
-			listenTo(messageCtr);//gets disposed as this controller gets disposed.
-			userDataVC.put("assessmentOverviewTable",  messageCtr.getInitialComponent());
-		}
-		putInitialPanel(userDataVC);
-	}
-	
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
-	 */
-	@Override
-	public void event(UserRequest ureq, Component source, Event event) {
-		if(source.equals(collectArtefactLink)){
-			popupArtefactCollector(ureq);
-		} else if (source == homeLink) {
-			openHome(ureq);
-		} else if (source == courseLink) {
-			openCourse(ureq);
-		} else if (source == groupLink) {
-			openGroup(ureq);
-		} else if (source == contactLink) {
-			contact(ureq);
-		}
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		 if (source == cmc) {
-				removeAsListenerAndDispose(cmc);
-				removeAsListenerAndDispose(contactCtrl);
-				cmc = null;
-				contactCtrl = null;
-			} else if (source == contactCtrl) {
-				cmc.deactivate();
-				removeAsListenerAndDispose(cmc);
-				removeAsListenerAndDispose(contactCtrl);
-				cmc = null;
-				contactCtrl = null;
-			}
-	}
-
-	private void contact(UserRequest ureq) {
-		removeAsListenerAndDispose(cmc);
-
-		ContactMessage cmsg = new ContactMessage(getIdentity());
-		ContactList contactList = new ContactList("to");
-		contactList.add(statementOwner);
-		cmsg.addEmailTo(contactList);
-		contactCtrl = new ContactFormController(ureq, getWindowControl(), true, false, false, cmsg);
-		listenTo(contactCtrl);
-		cmc = new CloseableModalController(getWindowControl(), translate("close"), contactCtrl.getInitialComponent());
-		cmc.activate();
-		listenTo(cmc);
-	}
-	
-	private void openGroup(UserRequest ureq) {
-		List<ContextEntry> ces = new ArrayList<ContextEntry>(1);
-		OLATResourceable ores = OresHelper.createOLATResourceableInstance("BusinessGroup", businessGroupKey);
-		ces.add(BusinessControlFactory.getInstance().createContextEntry(ores));
-
-		BusinessControl bc = BusinessControlFactory.getInstance().createFromContextEntries(ces);
-	  WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
-		NewControllerFactory.getInstance().launch(ureq, bwControl);
-	}
-	
-	private void openCourse(UserRequest ureq) {
-		List<ContextEntry> ces = new ArrayList<ContextEntry>(1);
-		OLATResourceable ores = OresHelper.createOLATResourceableInstance("RepositoryEntry", courseRepoKey);
-		ces.add(BusinessControlFactory.getInstance().createContextEntry(ores));
-
-		BusinessControl bc = BusinessControlFactory.getInstance().createFromContextEntries(ces);
-	  WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
-		NewControllerFactory.getInstance().launch(ureq, bwControl);
-	}
-	
-	private void openHome(UserRequest ureq) {
-		List<ContextEntry> ces = new ArrayList<ContextEntry>(1);
-		ces.add(BusinessControlFactory.getInstance().createContextEntry(statementOwner));
-
-		BusinessControl bc = BusinessControlFactory.getInstance().createFromContextEntries(ces);
-	  WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
-		NewControllerFactory.getInstance().launch(ureq, bwControl);
-	}
-
-	/**
-	 * opens the collect-artefact wizard 
-	 * 
-	 * @param ureq
-	 */
-	private void popupArtefactCollector(UserRequest ureq) {
-		EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
-		if(artHandler != null && artHandler.isEnabled()) {
-			AbstractArtefact artefact = artHandler.createArtefact();
-			artefact.setAuthor(getIdentity());//only author can create artefact
-			//no business path becouse we cannot launch an efficiency statement
-			artefact.setCollectionDate(new Date());
-			artefact.setTitle(translate("artefact.title", new String[]{efficiencyStatement.getCourseTitle()}));
-			artHandler.prefillArtefactAccordingToSource(artefact, efficiencyStatement);
-			ePFCollCtrl = new ArtefactWizzardStepsController(ureq, getWindowControl(), artefact, (VFSContainer)null);
-			listenTo(ePFCollCtrl);
-			
-			//set flag for js-window-resizing (see velocity)
-			userDataVC.contextPut("collectwizard", true);
-		}
-	}
-	
-	
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
-	 */
-	protected void doDispose() {
-		//
-	}
-
-}
diff --git a/src/main/java/org/olat/course/assessment/EfficiencyStatementManager.java b/src/main/java/org/olat/course/assessment/EfficiencyStatementManager.java
index 704cbed2d7ac46085da0158d87752a1d1d35bf5c..cb2b9340fbd503ea18b1d252abd8067aaf5a2782 100644
--- a/src/main/java/org/olat/course/assessment/EfficiencyStatementManager.java
+++ b/src/main/java/org/olat/course/assessment/EfficiencyStatementManager.java
@@ -263,7 +263,7 @@ public class EfficiencyStatementManager extends BasicManager implements UserData
 	 * @return Map containing a list of maps that contain the nodeData for this user and course using the
 	 * keys defined in the AssessmentHelper and the title of the course
 	 */
-	public EfficiencyStatement getUserEfficiencyStatement(Long courseRepoEntryKey, Identity identity){
+	public EfficiencyStatement getUserEfficiencyStatementByCourseRepoKey(Long courseRepoEntryKey, Identity identity){
 		UserEfficiencyStatementImpl s = getUserEfficiencyStatementFull(courseRepoEntryKey, identity);
 		if(s == null || s.getStatementXml() == null) {
 			return null;
@@ -271,6 +271,14 @@ public class EfficiencyStatementManager extends BasicManager implements UserData
 		return (EfficiencyStatement)xstream.fromXML(s.getStatementXml());
 	}
 	
+	public EfficiencyStatement getUserEfficiencyStatementByResourceKey(Long resourceKey, Identity identity){
+		UserEfficiencyStatementImpl s = getUserEfficiencyStatementFull(resourceKey, identity);
+		if(s == null || s.getStatementXml() == null) {
+			return null;
+		}
+		return (EfficiencyStatement)xstream.fromXML(s.getStatementXml());
+	}
+	
 
 	public UserEfficiencyStatementImpl getUserEfficiencyStatementFull(Long courseRepoEntryKey, Identity identity) {
 		try {
@@ -293,6 +301,27 @@ public class EfficiencyStatementManager extends BasicManager implements UserData
 		}
 	}
 	
+	public UserEfficiencyStatementImpl getUserEfficiencyStatementFullByResourceKey(Long resourceKey, Identity identity) {
+		try {
+			StringBuilder sb = new StringBuilder();
+			sb.append("select statement from ").append(UserEfficiencyStatementImpl.class.getName()).append(" as statement ")
+			  .append(" where statement.identity.key=:identityKey and statement.archivedResourceKey=:resourceKey");
+
+			List<UserEfficiencyStatementImpl> statement = dbInstance.getCurrentEntityManager()
+					.createQuery(sb.toString(), UserEfficiencyStatementImpl.class)
+					.setParameter("identityKey", identity.getKey())
+					.setParameter("resourceKey", resourceKey)
+					.getResultList();
+			if(statement.isEmpty()) {
+				return null;
+			}
+			return statement.get(0);
+		} catch (Exception e) {
+			logError("Cannot retrieve efficiency statement: " + resourceKey + " from " + identity, e);
+			return null;
+		}
+	}
+	
 	public UserEfficiencyStatement getUserEfficiencyStatementLight(Long courseRepoEntryKey, Identity identity) {
 		try {
 			StringBuilder sb = new StringBuilder();
@@ -334,6 +363,20 @@ public class EfficiencyStatementManager extends BasicManager implements UserData
 		}
 	}
 	
+	public UserEfficiencyStatementLight getUserEfficiencyStatementLightByKey(Long key) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select statement from ").append(UserEfficiencyStatementLight.class.getName()).append(" as statement ")
+		  .append(" where statement.key=:key");
+
+		List<UserEfficiencyStatementLight> statement = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), UserEfficiencyStatementLight.class)
+				.setParameter("key", key)
+				.getResultList();
+		if(statement.isEmpty()) {
+			return null;
+		}
+		return statement.get(0);
+	}
 
 	/**
 	 * Get the passed value of a course node of a specific efficiency statment
@@ -445,7 +488,7 @@ public class EfficiencyStatementManager extends BasicManager implements UserData
 		return efficiencyStatements;
 	}
 	
-	protected List<UserEfficiencyStatementLight> findEfficiencyStatementsLight(Identity identity) {
+	public List<UserEfficiencyStatementLight> findEfficiencyStatementsLight(Identity identity) {
 		try {
 			StringBuilder sb = new StringBuilder();
 			sb.append("select statement from ").append(UserEfficiencyStatementLight.class.getName()).append(" as statement ")
@@ -528,7 +571,7 @@ public class EfficiencyStatementManager extends BasicManager implements UserData
 	protected void deleteEfficiencyStatement(Identity identity, EfficiencyStatement efficiencyStatement) {
 		UserEfficiencyStatement s = getUserEfficiencyStatementLight(efficiencyStatement.getCourseRepoEntryKey(), identity);
 		if(s != null) {
-			dbInstance.deleteObject(s);
+			dbInstance.getCurrentEntityManager().remove(s);
 		}
 	}
 	
@@ -537,8 +580,8 @@ public class EfficiencyStatementManager extends BasicManager implements UserData
 	 * @param identity
 	 * @param efficiencyStatement
 	 */
-	protected void deleteEfficiencyStatement(UserEfficiencyStatementLight efficiencyStatement) {
-		dbInstance.deleteObject(efficiencyStatement);
+	public void deleteEfficiencyStatement(UserEfficiencyStatementLight efficiencyStatement) {
+		dbInstance.getCurrentEntityManager().remove(efficiencyStatement);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/assessment/EfficiencyStatementsListController.java b/src/main/java/org/olat/course/assessment/EfficiencyStatementsListController.java
deleted file mode 100644
index 208b22e0925109bf82097ad2f3b29238b4d6ca7e..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/course/assessment/EfficiencyStatementsListController.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <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
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <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>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-
-package org.olat.course.assessment;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-
-import org.olat.NewControllerFactory;
-import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
-import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory;
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.table.BooleanColumnDescriptor;
-import org.olat.core.gui.components.table.ColumnDescriptor;
-import org.olat.core.gui.components.table.CustomCellRenderer;
-import org.olat.core.gui.components.table.CustomRenderColumnDescriptor;
-import org.olat.core.gui.components.table.DefaultColumnDescriptor;
-import org.olat.core.gui.components.table.StaticColumnDescriptor;
-import org.olat.core.gui.components.table.Table;
-import org.olat.core.gui.components.table.TableController;
-import org.olat.core.gui.components.table.TableEvent;
-import org.olat.core.gui.components.table.TableGuiConfiguration;
-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.gui.control.creator.ControllerCreator;
-import org.olat.core.gui.control.generic.modal.DialogBoxController;
-import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
-import org.olat.core.gui.control.generic.popup.PopupBrowserWindow;
-import org.olat.core.gui.render.Renderer;
-import org.olat.core.gui.render.StringOutput;
-import org.olat.core.id.context.BusinessControlFactory;
-import org.olat.core.util.vfs.VFSContainer;
-import org.olat.course.assessment.model.UserEfficiencyStatementLight;
-import org.olat.course.assessment.portfolio.EfficiencyStatementArtefact;
-import org.olat.portfolio.EPArtefactHandler;
-import org.olat.portfolio.PortfolioModule;
-import org.olat.portfolio.model.artefacts.AbstractArtefact;
-import org.olat.portfolio.ui.artefacts.collect.ArtefactWizzardStepsController;
-import org.olat.repository.RepositoryEntry;
-import org.olat.repository.RepositoryManager;
-import org.springframework.beans.factory.annotation.Autowired;
-
-/**
- * Description:<br>
- * Shows the list of all efficiency statements for this user
- * <P>
- * Initial Date:  11.07.2005 <br>
- * @author gnaegi
- */
-public class EfficiencyStatementsListController extends BasicController {
-	private static final String CMD_SHOW = "cmd.show";
-	private static final String CMD_LAUNCH_COURSE = "cmd.launch.course";
-	private static final String CMD_DELETE = "cmd.delete";
-	private static final String CMD_ARTEFACT = "cmd.artefact";
-
-	private TableController tableCtr;
-	private EfficiencyStatementsListModel efficiencyStatementsListModel;
-	private DialogBoxController confirmDeleteCtr;
-	private UserEfficiencyStatementLight efficiencyStatement;
-	private Controller ePFCollCtrl;
-	@Autowired
-	private PortfolioModule portfolioModule;
-	@Autowired
-	private EfficiencyStatementManager esm;
-	
-	/**
-	 * Constructor
-	 * @param wControl
-	 * @param ureq
-	 */
-	public EfficiencyStatementsListController(UserRequest ureq, WindowControl wControl) { 
-		super(ureq, wControl);
-		
-		TableGuiConfiguration tableConfig = new TableGuiConfiguration();
-		tableConfig.setTableEmptyMessage(translate("efficiencyStatementsPortlet.nostatements"));
-		tableCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator());
-		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.course", 0, null, getLocale()));
-		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.score", 1, null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT));
-		tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("table.header.passed", 2, translate("passed.true"), translate("passed.false")));
-		StaticColumnDescriptor cd3 = new StaticColumnDescriptor(CMD_SHOW, "table.header.show", translate("table.action.show"));
-		cd3.setIsPopUpWindowAction(true, "height=600, width=800, location=no, menubar=no, resizable=yes, status=no, scrollbars=yes, toolbar=no");
-		tableCtr.addColumnDescriptor(cd3);
-		tableCtr.addColumnDescriptor(new StaticColumnDescriptor(CMD_LAUNCH_COURSE, "table.header.launchcourse", translate("table.action.launchcourse")));
-		tableCtr.addColumnDescriptor(new StaticColumnDescriptor(CMD_DELETE, "table.header.delete", translate("table.action.delete")));
-
-		EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
-		if(portfolioModule.isEnabled() && artHandler != null && artHandler.isEnabled()) {
-			tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.artefact", 5, CMD_ARTEFACT, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_CENTER, new AsArtefactCellRenderer()));
-		}
-		listenTo(tableCtr);
-		
-		List<UserEfficiencyStatementLight> efficiencyStatementsList = esm.findEfficiencyStatementsLight(ureq.getIdentity());
-		efficiencyStatementsListModel = new EfficiencyStatementsListModel(efficiencyStatementsList);
-		tableCtr.setTableDataModel(efficiencyStatementsListModel);
-
-		putInitialPanel(tableCtr.getInitialComponent());
-	}
-
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
-	 */
-	public void event(UserRequest ureq, Component source, Event event) {
-		// nothing to catch
-	}
-
-	/**
-	 * @see org.olat.core.gui.control.ControllerEventListener#dispatchEvent(org.olat.core.gui.UserRequest, org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
-	 */
-	public void event(UserRequest ureq, Controller source, Event event) {
-		if (source == tableCtr) {
-			if (event.getCommand().equals(Table.COMMANDLINK_ROWACTION_CLICKED)) {
-				TableEvent te = (TableEvent) event;
-				String actionid = te.getActionId();
-				int rowid = te.getRowId();
-				efficiencyStatement = efficiencyStatementsListModel.getEfficiencyStatementAt(rowid);
-				if (actionid.equals(CMD_SHOW)) {					
-					// will not be disposed on course run dispose, popus up as new browserwindow
-					ControllerCreator ctrlCreator = new ControllerCreator() {
-						public Controller createController(UserRequest lureq, WindowControl lwControl) {
-							EfficiencyStatementController efficiencyCtrl = new EfficiencyStatementController(lwControl, lureq, efficiencyStatement.getCourseRepoKey());
-							return new LayoutMain3ColsController(lureq, getWindowControl(), efficiencyCtrl);
-						}					
-					};
-					//wrap the content controller into a full header layout
-					ControllerCreator layoutCtrlr = BaseFullWebappPopupLayoutFactory.createAuthMinimalPopupLayout(ureq, ctrlCreator);
-					//open in new browser window
-					PopupBrowserWindow pbw = getWindowControl().getWindowBackOffice().getWindowManager().createNewPopupBrowserWindowFor(ureq, layoutCtrlr);
-					pbw.open(ureq);
-					//
-				} else if (actionid.equals(CMD_LAUNCH_COURSE)) {
-					RepositoryManager rm = RepositoryManager.getInstance();
-					RepositoryEntry re = rm.lookupRepositoryEntry(efficiencyStatement.getCourseRepoKey(), false);
-					if (re == null) {
-						showWarning("efficiencyStatements.course.noexists");
-					} else if (!rm.isAllowedToLaunch(ureq, re)) {
-						showWarning("efficiencyStatements.course.noaccess");
-					} else {
-						WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(getWindowControl(), re);
-						NewControllerFactory.getInstance().launch(ureq, bwControl);
-					}
-				} else if (actionid.equals(CMD_DELETE)) {					
-					// show confirmation dialog
-					RepositoryManager rm = RepositoryManager.getInstance();
-					RepositoryEntry re = rm.lookupRepositoryEntry(efficiencyStatement.getCourseRepoKey(), false);
-					if(re == null) {
-						confirmDeleteCtr = activateYesNoDialog(ureq, null, translate("efficiencyStatements.delete.confirm", efficiencyStatement.getShortTitle()), confirmDeleteCtr);
-					} else {
-						showWarning("efficiencyStatements.cannot.delete");
-					}
-					return;
-				} else if (actionid.equals(CMD_ARTEFACT)) {
-					popupArtefactCollector(ureq);
-				}
-			}
-		} else if (source == confirmDeleteCtr) {
-			if (DialogBoxUIFactory.isYesEvent(event)) {
-				// delete efficiency statement manager
-				EfficiencyStatementManager esm = EfficiencyStatementManager.getInstance();
-				esm.deleteEfficiencyStatement(efficiencyStatement);
-				efficiencyStatementsListModel.getObjects().remove(efficiencyStatement);
-				efficiencyStatement = null;
-				tableCtr.modelChanged();
-				showInfo("info.efficiencyStatement.deleted");
-			}
-		}
-	}
-	
-	private void popupArtefactCollector(UserRequest ureq) {
-		EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
-		if(artHandler != null && artHandler.isEnabled()) {
-			AbstractArtefact artefact = artHandler.createArtefact();
-			artefact.setAuthor(getIdentity());//only author can create artefact
-			//no business path becouse we cannot launch an efficiency statement
-			artefact.setCollectionDate(new Date());
-			artefact.setTitle(translate("artefact.title", new String[]{efficiencyStatement.getShortTitle()}));
-			EfficiencyStatement fullStatement = EfficiencyStatementManager.getInstance().getUserEfficiencyStatementByKey(efficiencyStatement.getKey());
-			artHandler.prefillArtefactAccordingToSource(artefact, fullStatement);
-			ePFCollCtrl = new ArtefactWizzardStepsController(ureq, getWindowControl(), artefact, (VFSContainer)null);
-			listenTo(ePFCollCtrl);
-		}
-	}
-
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
-	 */
-	protected void doDispose() {
-		// DialogBoxController and TableController get disposed by BasicController
-	}
-	
-	public class AsArtefactCellRenderer implements CustomCellRenderer {
-		/**
-		 * @see org.olat.core.gui.components.table.CustomCellRenderer#render(org.olat.core.gui.render.StringOutput, org.olat.core.gui.render.Renderer, java.lang.Object, java.util.Locale, int, java.lang.String)
-		 */
-		@Override
-		public void render(StringOutput sb, Renderer renderer, Object val, Locale locale, int alignment, String action) {
-			sb.append("<i class='o_icon o_icon-lg o_icon_eportfolio_add'> </i> <span title=\"")
-				.append(translate("table.add.as.artefact"))
-				.append("\"> </span>");
-		}
-	}
-}
diff --git a/src/main/java/org/olat/course/assessment/EfficiencyStatementsListModel.java b/src/main/java/org/olat/course/assessment/EfficiencyStatementsListModel.java
deleted file mode 100644
index 08286ca36265eeb1d9953c4e833c5ca943a90d2a..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/course/assessment/EfficiencyStatementsListModel.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <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
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <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>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-
-package org.olat.course.assessment;
-
-import java.util.List;
-
-import org.olat.core.gui.components.table.DefaultTableDataModel;
-import org.olat.course.assessment.model.UserEfficiencyStatementLight;
-
-/**
- * Description:<br>
- * Table model to display a list of efficiency statements
- * <P>
- * Initial Date: 12.08.2005 <br>
- * 
- * @author gnaegi
- */
-public class EfficiencyStatementsListModel extends DefaultTableDataModel<UserEfficiencyStatementLight> {
-
-	/**
-	 * @param list of efficiencyStatements
-	 */
-	public EfficiencyStatementsListModel(List<UserEfficiencyStatementLight> efficiencyStatements) {
-		super(efficiencyStatements);
-	}
-
-	/**
-	 * @see org.olat.core.gui.components.table.TableDataModel#getColumnCount()
-	 */
-	public int getColumnCount() {
-		return 2;
-	}
-
-	/**
-	 * @see org.olat.core.gui.components.table.TableDataModel#getValueAt(int, int)
-	 */
-	public Object getValueAt(int row, int col) {
-		UserEfficiencyStatementLight efficiencyStatement = getEfficiencyStatementAt(row);
-		switch (col) {
-			case 0:
-				return efficiencyStatement.getShortTitle();
-			case 1:
-				Float score = efficiencyStatement.getScore();
-				return AssessmentHelper.getRoundedScore(score);
-			case 2:
-				return efficiencyStatement.getPassed();
-			default:
-				return "ERROR";
-		}
-	}
-
-	/**
-	 * @param row
-	 * @return the efficiencyStatement at the given row
-	 */
-	public UserEfficiencyStatementLight getEfficiencyStatementAt(int row) {
-		return objects.get(row);
-	}
-}
diff --git a/src/main/java/org/olat/course/assessment/EfficiencyStatementsPortletRunController.java b/src/main/java/org/olat/course/assessment/EfficiencyStatementsPortletRunController.java
index 39e81e2480db1dd73e1cf4575171b295e7372931..c25a05c424155878e39870c4432aba5ef40ace50 100644
--- a/src/main/java/org/olat/course/assessment/EfficiencyStatementsPortletRunController.java
+++ b/src/main/java/org/olat/course/assessment/EfficiencyStatementsPortletRunController.java
@@ -61,6 +61,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.course.CourseModule;
 import org.olat.course.assessment.model.UserEfficiencyStatementLight;
+import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementController;
 
 
 /**
@@ -205,11 +206,11 @@ public class EfficiencyStatementsPortletRunController extends AbstractPortletRun
 				String actionid = te.getActionId();
 				if (actionid.equals(CMD_LAUNCH)) {
 					int rowid = te.getRowId();
-					final UserEfficiencyStatementLight efficiencyStatement = efficiencyStatementsListModel.getEfficiencyStatementAt(rowid);
+					final UserEfficiencyStatementLight statement = efficiencyStatementsListModel.getEfficiencyStatementAt(rowid);
 					// will not be disposed on course run dispose, popus up as new browserwindow
 					ControllerCreator ctrlCreator = new ControllerCreator() {
 						public Controller createController(UserRequest lureq, WindowControl lwControl) {
-							EfficiencyStatementController efficiencyCtrl = new EfficiencyStatementController(lwControl, lureq, efficiencyStatement.getCourseRepoKey());
+							CertificateAndEfficiencyStatementController efficiencyCtrl = new CertificateAndEfficiencyStatementController(lwControl, lureq, statement.getArchivedResourceKey());
 							return new LayoutMain3ColsController(lureq, getWindowControl(), efficiencyCtrl);
 						}					
 					};
diff --git a/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java b/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java
index 5a5fc3b9c5ae9470c71e41ecaf514adbe6e356f2..d388c1c559a5b65d6881a2067603a8715318e526 100644
--- a/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java
+++ b/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java
@@ -49,6 +49,8 @@ import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.Structure;
 import org.olat.course.assessment.manager.UserCourseInformationsManager;
+import org.olat.course.certificate.PDFCertificatesOptions;
+import org.olat.course.certificate.ui.AssessedIdentityCertificatesController;
 import org.olat.course.nodes.AssessableCourseNode;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.run.userview.UserCourseEnvironment;
@@ -78,6 +80,7 @@ public class IdentityAssessmentEditController extends BasicController {
 	private Link backLink;
 	private final BreadcrumbPanel stackPanel;
 	private final AssessedIdentityInfosController identityInfosCtrl;
+	private AssessedIdentityCertificatesController certificateCtrl;
 	
 	private OLATResourceable ores;
 
@@ -114,6 +117,12 @@ public class IdentityAssessmentEditController extends BasicController {
 		identityAssessmentVC.put("identityInfos", identityInfosCtrl.getInitialComponent());
 		doIdentityAssessmentOverview(ureq, true);
 		
+		PDFCertificatesOptions certificateOption = course.getCourseConfig().getPdfCertificateOption();
+		if(certificateOption == PDFCertificatesOptions.manual || certificateOption == PDFCertificatesOptions.auto) {
+			certificateCtrl = new AssessedIdentityCertificatesController(ureq, wControl, assessedUserCourseEnvironment);
+			identityAssessmentVC.put("certificateInfos", certificateCtrl.getInitialComponent());
+		}
+		
 		BusinessControl bc = getWindowControl().getBusinessControl();
 		ContextEntry ce = bc.popLauncherContextEntry();
 		if (ce != null) {
diff --git a/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java b/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java
index 37c2c9580515ed7f576648f6c317bb663d44d57e..e03f536b966f1d368d3d105b203d15a4f6dceb5b 100644
--- a/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java
+++ b/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java
@@ -121,7 +121,7 @@ public class IdentityAssessmentOverviewController extends BasicController {
 	 * @param wControl
 	 * @param assessmentCourseNodes List of maps containing the node assessment data using the AssessmentManager keys
 	 */
-	protected IdentityAssessmentOverviewController(UserRequest ureq, WindowControl wControl, List<Map<String,Object>> assessmentCourseNodes) {
+	public IdentityAssessmentOverviewController(UserRequest ureq, WindowControl wControl, List<Map<String,Object>> assessmentCourseNodes) {
 		super(ureq, wControl);
 		this.runStructure = null;
 		this.nodesSelectable = false;
@@ -132,7 +132,7 @@ public class IdentityAssessmentOverviewController extends BasicController {
 		this.preloadedNodesList = assessmentCourseNodes;
 		
 		doIdentityAssessmentOverview(ureq);		
-    putInitialPanel(main);
+		putInitialPanel(main);
 	}
 	
 
@@ -211,7 +211,6 @@ public class IdentityAssessmentOverviewController extends BasicController {
 			}
 			listenTo(tableFilterCtr);
 			
-			//fxdiff VCRP-4: assessment overview with max score
 			final IndentedNodeRenderer nodeRenderer = new IndentedNodeRenderer() {
 				@Override
 				public boolean isIndentationEnabled() {
@@ -223,7 +222,6 @@ public class IdentityAssessmentOverviewController extends BasicController {
 			tableFilterCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.node", 0, null, 
 					ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, nodeRenderer){
 					@Override
-					//fxdiff VCRP-4: assessment overview with max score
 					public int compareTo(int rowa, int rowb) {
 						return rowa - rowb;
 					}
@@ -232,7 +230,6 @@ public class IdentityAssessmentOverviewController extends BasicController {
 			tableFilterCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.attempts", 2, null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT));
 			tableFilterCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", 3, null, ureq.getLocale(),
 					ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer()));
-		//fxdiff VCRP-4: assessment overview with max score
 			tableFilterCtr.addColumnDescriptor(false, new CustomRenderColumnDescriptor("table.header.min", 6, null, ureq.getLocale(), 
 					ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer()));
 			tableFilterCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.max", 7, null, ureq.getLocale(),
@@ -242,7 +239,6 @@ public class IdentityAssessmentOverviewController extends BasicController {
 			if (nodesSelectable) {
 				tableFilterCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.action.select",5 ,CMD_SELECT_NODE, ureq.getLocale()) {
 					@Override
-				//fxdiff VCRP-4: assessment overview with max score
 					public boolean isSortingAllowed() {
 						return false;
 					}
diff --git a/src/main/java/org/olat/course/assessment/NewCachePersistingAssessmentManager.java b/src/main/java/org/olat/course/assessment/NewCachePersistingAssessmentManager.java
index c928c588dc0f470e91183505f584e0f4dd8a43c6..c23a7d51c6e75142e5146063cac2122e6166eeeb 100644
--- a/src/main/java/org/olat/course/assessment/NewCachePersistingAssessmentManager.java
+++ b/src/main/java/org/olat/course/assessment/NewCachePersistingAssessmentManager.java
@@ -36,6 +36,7 @@ import java.util.Map;
 
 import javax.persistence.TypedQuery;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.commons.persistence.PersistenceHelper;
 import org.olat.core.id.Identity;
@@ -55,12 +56,17 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.auditing.UserNodeAuditManager;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.PDFCertificatesOptions;
+import org.olat.course.certificate.model.CertificateInfos;
 import org.olat.course.nodes.AssessableCourseNode;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.properties.Property;
+import org.olat.repository.RepositoryEntry;
 import org.olat.util.logging.activity.LoggingResourceable;
 
 
@@ -854,8 +860,9 @@ public class NewCachePersistingAssessmentManager extends BasicManager implements
 	 */
 	public void saveScoreEvaluation(final CourseNode courseNode, final Identity identity, final Identity assessedIdentity, final ScoreEvaluation scoreEvaluation, 
 			final UserCourseEnvironment userCourseEnv, final boolean incrementUserAttempts) {
-		ICourse course = CourseFactory.loadCourse(ores);
+		final ICourse course = CourseFactory.loadCourse(ores);
 		final CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager();
+		final RepositoryEntry courseEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
 		// o_clusterREVIEW we could sync on a element finer than course, e.g. the composite course+assessIdentity.
 		// +: concurrency would be higher
 		// -: many entries (num of courses * visitors of given course) in the locktable.
@@ -864,8 +871,11 @@ public class NewCachePersistingAssessmentManager extends BasicManager implements
 		Long attempts = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(createOLATResourceableForLocking(assessedIdentity), new SyncerCallback<Long>(){
 			public Long execute() {
 				Long attempts = null;
-				saveNodeScore(courseNode, assessedIdentity, scoreEvaluation.getScore(), cpm);
-				saveNodePassed(courseNode, assessedIdentity, scoreEvaluation.getPassed(), cpm);
+				Float score = scoreEvaluation.getScore();
+				Boolean passed = scoreEvaluation.getPassed();
+				
+				saveNodeScore(courseNode, assessedIdentity, score, cpm);
+				saveNodePassed(courseNode, assessedIdentity, passed, cpm);
 				saveAssessmentID(courseNode, assessedIdentity, scoreEvaluation.getAssessmentID(), cpm);				
 				if(incrementUserAttempts) {
 					attempts = incrementNodeAttemptsProperty(courseNode, assessedIdentity, cpm);
@@ -876,6 +886,21 @@ public class NewCachePersistingAssessmentManager extends BasicManager implements
 				  EfficiencyStatementManager esm =	EfficiencyStatementManager.getInstance();
 				  esm.updateUserEfficiencyStatement(userCourseEnv);
 				}
+				
+				if(scoreEvaluation.getPassed()) {
+					PDFCertificatesOptions options = course.getCourseConfig().getPdfCertificateOption();
+					if(options == PDFCertificatesOptions.auto) {
+						String templateId = course.getCourseConfig().getPdfCertificateTemplate();
+						CertificateTemplate template = null;
+						CertificatesManager certificatesManager = CoreSpringFactory.getImpl(CertificatesManager.class);
+						if(StringHelper.isLong(templateId)) {
+							template = certificatesManager.getTemplateById(new Long(templateId));
+						}
+						CertificateInfos certificateInfos = new CertificateInfos(assessedIdentity, score, passed);
+						certificatesManager.generateCertificate(certificateInfos, courseEntry, template);
+					}
+				}
+				
 				return attempts;
 			}});
 		
diff --git a/src/main/java/org/olat/course/assessment/_content/assessment_index.html b/src/main/java/org/olat/course/assessment/_content/assessment_index.html
index 2b9b7e97b5d62f737e1244af94bc2d6001557fcd..d17ece6767cb31afece07428b014249d30d6b0aa 100644
--- a/src/main/java/org/olat/course/assessment/_content/assessment_index.html
+++ b/src/main/java/org/olat/course/assessment/_content/assessment_index.html
@@ -1,15 +1,25 @@
 $r.contextHelpWithWrapper("org.olat.course.assessment","course-assess-tool.html","help.hover.assess")
 <h2><i class="o_icon o_icon_assessment_tool"> </i> $r.translate("index.title")</h2>
-#if($hasAssessableNodes)
 <p>$r.translate("index.intro")</p>
-<p>$r.translate("index.slow")</p>
-<div class="clearfix">
+<h4>$r.translate("index.notifications.title")</h4>
+<table class="table">
 	#if($r.available("assessmentSubscription"))
-		$r.render("assessmentSubscription")
+	<tr>
+		<th>$r.translate("index.notifications.assessment")</th>
+		<td>$r.render("assessmentSubscription")</td>
+	</tr>
+	#end
+	#if($r.available("certificationSubscription"))
+	<tr>
+		<th>$r.translate("index.notifications.certificate")</th>
+		<td>$r.render("certificationSubscription")</td>
+	</tr>
 	#end
-	<h5>$r.translate("index.notifications.title")</h5>
-	$r.translate("index.notifications.assessment")
-</div>
+</table>
+
+
+#if($hasAssessableNodes)
+
 #else
 	<p>$r.translate("index.noAssessableNodes")</p>
 #end
diff --git a/src/main/java/org/olat/course/assessment/_content/identityassessment.html b/src/main/java/org/olat/course/assessment/_content/identityassessment.html
index 86a28bcdb96d40dd828fcad25dedd60370b9bfa9..8ea9122c3668af00ab15e9527bf3d65d859243e5 100644
--- a/src/main/java/org/olat/course/assessment/_content/identityassessment.html
+++ b/src/main/java/org/olat/course/assessment/_content/identityassessment.html
@@ -6,5 +6,8 @@
 #if($r.available("identityInfos"))
 	$r.render("identityInfos")
 #end
+#if($r.available("certificateInfos"))
+	$r.render("certificateInfos")
+#end
 $r.render("assessmentOverviewTable")
 </div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties
index ebe96ab81799aaf6fc92981bd7cda0f9e00fede2..6c3650007c4bb156c34eb0572ef0278dbe3b8000 100644
--- a/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties
@@ -26,6 +26,9 @@ command.showlog=\u00C4nderungsverlauf anzeigen
 command.start.bulkwizard=Massenbewertung starten
 course=Kurs\:
 course.link=Kurs \u00F6ffnen
+create.certificate=Zertifikate erzeugen
+confirm.certificate.title=Zertifikate erzeugen
+confirm.certificate.description=Neue Zertifikate für diese Benutzer erzeugen: {0}
 group=Gruppe\:
 group.link=Gruppe \u00F6ffnen
 date=Datum\:
@@ -89,6 +92,7 @@ index.intro=Mit diesem Werkzeug k\u00F6nnen Sie Ihre Kursteilnehmer beurteilen u
 index.noAssessableNodes=Es k\u00F6nnen keine Bewertungen vorgenommen werden, da in diesem Kurs keine bewertbaren Kursbausteine verwendet werden. Starten Sie den Kurseditor und f\u00FCgen Sie der Kursstruktur einen bewertbaren Kursbaustein (z.B. Test, Bewertung) hinzu. 
 index.notifications.assessment=Benachrichtigung bei neuen Testresultaten dieses Kurses 
 index.notifications.title=Benachrichtigungsservice
+index.notifications.certificate=Erzeugte Zertifikate
 index.slow=Bitte beachten Sie, dass Anfragen im Bewertungswerkzeug evtl. etwas Zeit ben\u00F6tigen. Bitte dr\u00FCcken Sie nicht Back oder Reload.
 index.title=Bewertungswerkzeug
 info.efficiencyStatement.deleted=Der Leistungsnachweis wurde gel\u00F6scht.
@@ -122,6 +126,7 @@ participantgroups.noGroups=Dieser Benutzer nimmt in keiner Gruppe teil.
 participantgroups.title=Teilnehmer in Gruppen
 passed.false=<span class\="o_state o_failed"><i class\="o_icon o_icon_failed"> </i> Nicht bestanden</span>
 passed.true=<span class\="o_state o_passed"><i class\="o_icon o_icon_passed"> </i> Bestanden</span>
+select.one.user.warning=Mindestens ein Benutzer auswählen
 table.action.delete=L\u00F6schen
 table.action.launchcourse=Kurs starten
 table.action.select=W\u00E4hlen
@@ -133,6 +138,7 @@ table.header.action=Aktion
 table.header.artefact=ePortfolio
 table.add.as.artefact=als Artefakt
 table.header.attempts=Versuche
+table.header.certificate=Zertifikat
 table.header.course=Kursname
 table.header.delete=L\u00F6schen
 table.header.details=Details
diff --git a/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementImpl.hbm.xml b/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementImpl.hbm.xml
index b769269a674b78d57a0df5b808d9ee530c974144..4cd8c9a9471b259761f6adc7ff6d5d2421fef54e 100644
--- a/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementImpl.hbm.xml
+++ b/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementImpl.hbm.xml
@@ -61,7 +61,8 @@
 	
 	<property name="shortTitle" column="course_short_title" type="string" />
 	<property name="courseRepoKey" column="course_repo_key" type="long" />
-	
+	<property name="archivedResourceKey" column="fk_resource_id" type="long" insert="false" update="false" />
+
 	<many-to-one name="resource"
                  column="fk_resource_id"
                  foreign-key="none"
diff --git a/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementLight.java b/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementLight.java
index ed8f1fb2d0555749e34041861f5498e8a9b43a27..a238fd147096bfc06fb9e6218ae9b5ccce00162a 100644
--- a/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementLight.java
+++ b/src/main/java/org/olat/course/assessment/model/UserEfficiencyStatementLight.java
@@ -50,6 +50,7 @@ public class UserEfficiencyStatementLight extends PersistentObject implements Us
 
 	private String shortTitle;
 	private Long courseRepoKey;
+	private Long archivedResourceKey;
 	
 	private Date lastModified;
 		
@@ -125,6 +126,14 @@ public class UserEfficiencyStatementLight extends PersistentObject implements Us
 		this.shortTitle = shortTitle;
 	}
 
+	public Long getArchivedResourceKey() {
+		return archivedResourceKey;
+	}
+
+	public void setArchivedResourceKey(Long archivedResourceKey) {
+		this.archivedResourceKey = archivedResourceKey;
+	}
+
 	public Long getCourseRepoKey() {
 		return courseRepoKey;
 	}
diff --git a/src/main/java/org/olat/course/assessment/portfolio/EfficiencyStatementArtefactHandler.java b/src/main/java/org/olat/course/assessment/portfolio/EfficiencyStatementArtefactHandler.java
index 7fb535a8713f5018a91aa7268b543f00857c7b59..dad7a63d1aae26ee045fc77e3293af132f31c982 100644
--- a/src/main/java/org/olat/course/assessment/portfolio/EfficiencyStatementArtefactHandler.java
+++ b/src/main/java/org/olat/course/assessment/portfolio/EfficiencyStatementArtefactHandler.java
@@ -28,7 +28,7 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.xml.XStreamHelper;
 import org.olat.course.assessment.EfficiencyStatement;
-import org.olat.course.assessment.EfficiencyStatementController;
+import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementController;
 import org.olat.portfolio.EPAbstractHandler;
 import org.olat.portfolio.manager.EPFrontendManager;
 import org.olat.portfolio.model.artefacts.AbstractArtefact;
@@ -83,7 +83,7 @@ public class EfficiencyStatementArtefactHandler extends EPAbstractHandler<Effici
 		EPFrontendManager ePFMgr = (EPFrontendManager) CoreSpringFactory.getBean("epFrontendManager");
 		String statementXml = ePFMgr.getArtefactFullTextContent(artefact);
 		EfficiencyStatement statement = (EfficiencyStatement)myXStream.fromXML(statementXml); 
-		EfficiencyStatementController efficiencyCtrl = new EfficiencyStatementController(wControl, ureq, statement);
+		CertificateAndEfficiencyStatementController efficiencyCtrl = new CertificateAndEfficiencyStatementController(wControl, ureq, statement);
 		return new LayoutMain3ColsController(ureq, wControl, efficiencyCtrl);
 	}
 
diff --git a/src/main/java/org/olat/course/certificate/Certificate.java b/src/main/java/org/olat/course/certificate/Certificate.java
new file mode 100644
index 0000000000000000000000000000000000000000..1089c9b53912413e183c8a6a1f46efab4550fda9
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/Certificate.java
@@ -0,0 +1,45 @@
+/**
+ * <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.certificate;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+
+/**
+ * 
+ * Initial date: 21.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface Certificate extends CreateInfo {
+	
+	public Long getKey();
+	
+	public String getUuid();
+	
+	public String getName();
+	
+	public String getPath();
+	
+	public Long getArchivedResourceKey();
+	
+	public Identity getIdentity();
+
+}
diff --git a/src/main/java/org/olat/course/certificate/CertificateDispatcher.java b/src/main/java/org/olat/course/certificate/CertificateDispatcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ec373d94282c92c2b38d7509e86547909dac8be
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/CertificateDispatcher.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.course.certificate;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.olat.core.dispatcher.Dispatcher;
+import org.olat.core.dispatcher.DispatcherModule;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.gui.media.NotFoundMediaResource;
+import org.olat.core.gui.media.ServletUtil;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.core.util.vfs.VFSMediaResource;
+
+/**
+ * 
+ * Initial date: 22.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificateDispatcher implements Dispatcher {
+	
+	private CertificatesManager certificatesManager;
+	
+	/**
+	 * [used by Spring]
+	 * @param certificatesManager
+	 */
+	public void setCertificatesManager(CertificatesManager certificatesManager) {
+		this.certificatesManager = certificatesManager;
+	}
+
+	@Override
+	public void execute(HttpServletRequest request, HttpServletResponse response)
+	throws ServletException, IOException {
+		String uriPrefix = DispatcherModule.getLegacyUriPrefix(request);
+		final String origUri = request.getRequestURI();
+		String uuid = origUri.substring(uriPrefix.length());
+		int indexSuffix = uuid.lastIndexOf('.');
+		if(indexSuffix > 0) {
+			uuid = uuid.substring(0, indexSuffix);
+		}
+		MediaResource resource;
+		Certificate certificate = certificatesManager.getCertificateByUuid(uuid);
+		if(certificate == null) {
+			resource = new NotFoundMediaResource(origUri);
+		} else {
+			VFSLeaf certificateFile = certificatesManager.getCertificateLeaf(certificate);
+			resource = new VFSMediaResource(certificateFile);
+		}
+		ServletUtil.serveResource(request, response, resource);
+	}
+}
diff --git a/src/main/java/org/olat/course/certificate/CertificateLight.java b/src/main/java/org/olat/course/certificate/CertificateLight.java
new file mode 100644
index 0000000000000000000000000000000000000000..2abc4997446e850023814bd9bc76e03682f8ee7f
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/CertificateLight.java
@@ -0,0 +1,40 @@
+/**
+ * <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.certificate;
+
+import org.olat.core.id.CreateInfo;
+
+/**
+ * 
+ * Initial date: 22.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface CertificateLight extends CreateInfo {
+	
+	public String getName();
+	
+	public String getUuid();
+	
+	public Long getIdentityKey();
+	
+	public Long getOlatResourceKey();
+
+}
diff --git a/src/main/java/org/olat/course/certificate/CertificateTemplate.java b/src/main/java/org/olat/course/certificate/CertificateTemplate.java
new file mode 100644
index 0000000000000000000000000000000000000000..93304428ccc76e678d56575df626282af3054b4b
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/CertificateTemplate.java
@@ -0,0 +1,41 @@
+/**
+ * <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.certificate;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.ModifiedInfo;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface CertificateTemplate extends CreateInfo, ModifiedInfo {
+	
+	public Long getKey();
+	
+	public String getName();
+	
+	public String getPath();
+	
+	public boolean isPublicTemplate();
+
+}
diff --git a/src/main/java/org/olat/course/certificate/CertificatesManager.java b/src/main/java/org/olat/course/certificate/CertificatesManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..c50c6ba77d033e1b733de1824d0e825c0503943b
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/CertificatesManager.java
@@ -0,0 +1,87 @@
+/**
+ * <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.certificate;
+
+import java.io.File;
+import java.util.Date;
+import java.util.List;
+
+import org.olat.basesecurity.IdentityRef;
+import org.olat.core.commons.services.notifications.PublisherData;
+import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.id.Identity;
+import org.olat.core.util.resource.OresHelper;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.course.ICourse;
+import org.olat.course.certificate.model.CertificateInfos;
+import org.olat.repository.RepositoryEntry;
+import org.olat.resource.OLATResource;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface CertificatesManager {
+
+	public static final String ORES_CERTIFICATE =  OresHelper.calculateTypeName(CertificatesManager.class);
+	
+	//notifications
+	public SubscriptionContext getSubscriptionContext(ICourse course);
+	
+	public PublisherData getPublisherData(ICourse course, String businessPath);
+	
+	public void markPublisherNews(Identity ident, ICourse course);
+	
+	//templates management
+	public List<CertificateTemplate> getTemplates();
+	
+	public CertificateTemplate addTemplate(String name, File file, boolean publicTemplate);
+	
+	public CertificateTemplate getTemplateById(Long key);
+
+	public File getTemplateFile(CertificateTemplate template);
+	
+	public VFSLeaf getTemplateLeaf(CertificateTemplate template);
+	
+	//certificate
+	public Certificate getCertificateById(Long key);
+	
+	public Certificate getCertificateByUuid(String uuid);
+	
+	public VFSLeaf getCertificateLeaf(Certificate certificate);
+	
+
+	public List<CertificateLight> getLastCertificates(IdentityRef identity);
+	
+	public List<Certificate> getCertificatesForNotifications(Identity identity, RepositoryEntry entry, Date lastNews);
+
+	public List<CertificateLight> getCertificates(OLATResource resourceKey);
+	
+	public Certificate getLastCertificate(IdentityRef identity, Long resourceKey);
+	
+	public List<Certificate> getCertificates(IdentityRef identity, OLATResource resource);
+	
+	public void generateCertificates(List<CertificateInfos> identities, RepositoryEntry entry, CertificateTemplate template);
+
+	public Certificate generateCertificate(CertificateInfos identity, RepositoryEntry entry, CertificateTemplate template);
+
+}
diff --git a/src/main/java/org/olat/course/certificate/PDFCertificatesOptions.java b/src/main/java/org/olat/course/certificate/PDFCertificatesOptions.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab440926f6715142b3f4e6b44dd3324e21da69e7
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/PDFCertificatesOptions.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.certificate;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum PDFCertificatesOptions {
+	
+	none,
+	auto,
+	manual
+
+}
diff --git a/src/main/java/org/olat/course/certificate/RecertificationTimeUnit.java b/src/main/java/org/olat/course/certificate/RecertificationTimeUnit.java
new file mode 100644
index 0000000000000000000000000000000000000000..d956d1f2a0d279e0fa41904503e28923ff43ffde
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/RecertificationTimeUnit.java
@@ -0,0 +1,35 @@
+/**
+ * <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.certificate;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum RecertificationTimeUnit {
+	
+	day,
+	week,
+	month,
+	year
+
+}
diff --git a/src/main/java/org/olat/course/certificate/manager/CertificateTemplateWorker.java b/src/main/java/org/olat/course/certificate/manager/CertificateTemplateWorker.java
new file mode 100644
index 0000000000000000000000000000000000000000..c203287338426449c172bdb08604340e836c04be
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/manager/CertificateTemplateWorker.java
@@ -0,0 +1,182 @@
+/**
+ * <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.certificate.manager;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+import org.olat.core.id.Identity;
+import org.olat.core.id.User;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.Formatter;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.repository.RepositoryEntry;
+import org.olat.user.UserManager;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+
+/**
+ * Do the hard work of filling the certificate
+ * 
+ * Initial date: 23.10.2014<br>
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificateTemplateWorker {
+
+	private static final OLog log = Tracing
+			.createLoggerFor(CertificateTemplateWorker.class);
+	
+	private final Float score;
+	private final Boolean passed;
+	private final Identity identity;
+	private final RepositoryEntry entry;
+
+	private Date dateCertification;
+	private Date dateFirstCertification;
+
+	private final Locale locale;
+	private final List<UserPropertyHandler> userPropertyHandlers;
+
+	public CertificateTemplateWorker(Identity identity, RepositoryEntry entry,
+			Float score, Boolean passed, Date dateCertification,
+			Date dateFirstCertification, Locale locale, UserManager userManager) {
+		this.entry = entry;
+		this.score = score;
+		this.locale = locale;
+		this.passed = passed;
+		this.identity = identity;
+		this.dateCertification = dateCertification;
+		this.dateFirstCertification = dateFirstCertification;
+		userPropertyHandlers = userManager.getAllUserPropertyHandlers();
+	}
+
+	public void fill(InputStream in, File certificate) {
+		PDDocument document = null;
+		try {
+			document = PDDocument.load(in);
+
+			PDDocumentCatalog docCatalog = document.getDocumentCatalog();
+			PDAcroForm acroForm = docCatalog.getAcroForm();
+			if (acroForm != null) {
+				fillUserProperties(acroForm);
+				fillRepositoryEntry(acroForm);
+				fillCertificationInfos(acroForm);
+				fillAssessmentInfos(acroForm);
+			}
+
+			OutputStream out = new FileOutputStream(certificate);
+			document.save(out);
+			out.flush();
+			out.close();
+		} catch (Exception e) {
+			log.error("", e);
+		} finally {
+			IOUtils.closeQuietly(document);
+		}
+	}
+
+	private void fillUserProperties(PDAcroForm acroForm) throws IOException {
+		User user = identity.getUser();
+		for (UserPropertyHandler handler : userPropertyHandlers) {
+			String propertyName = handler.getName();
+			String value = handler.getUserProperty(user, null);
+			fillField(propertyName, value, acroForm);
+		}
+	}
+
+	private void fillRepositoryEntry(PDAcroForm acroForm) throws IOException {
+		String title = entry.getDisplayname();
+		fillField("title", title, acroForm);
+		String externalRef = entry.getExternalRef();
+		fillField("externalReference", externalRef, acroForm);
+		String authors = entry.getAuthors();
+		fillField("authors", authors, acroForm);
+		String expenditureOfWorks = entry.getExpenditureOfWork();
+		fillField("expenditureOfWorks", expenditureOfWorks, acroForm);
+		String mainLanguage = entry.getMainLanguage();
+		fillField("mainLanguage", mainLanguage, acroForm);
+		
+		if (entry.getLifecycle() != null) {
+			Formatter format = Formatter.getInstance(locale);
+
+			Date from = entry.getLifecycle().getValidFrom();
+			String formattedFrom = format.formatDate(from);
+			fillField("from", formattedFrom, acroForm);
+
+			Date to = entry.getLifecycle().getValidTo();
+			String formattedTo = format.formatDate(to);
+			fillField("to", formattedTo, acroForm);
+		}
+	}
+	
+	private void fillCertificationInfos(PDAcroForm acroForm) throws IOException {
+		Formatter format = Formatter.getInstance(locale);
+
+		if(dateCertification == null) {
+			fillField("dateCertification", "", acroForm);
+		} else {
+			String formattedDateCertification= format.formatDate(dateCertification);
+			fillField("dateCertification", formattedDateCertification, acroForm);
+		}
+		
+		if(dateFirstCertification == null) {
+			fillField("dateFirstCertification", "", acroForm);
+		} else {
+			String formattedDateFirstCertification = format.formatDate(dateFirstCertification);
+			fillField("dateFirstCertification", formattedDateFirstCertification, acroForm);
+		}
+	}
+	
+	private void fillAssessmentInfos(PDAcroForm acroForm) throws IOException {
+		String roundedScore = AssessmentHelper.getRoundedScore(score);
+		fillField("score", roundedScore, acroForm);
+
+		String status = (passed != null && passed.booleanValue()) ? "Passed" : "Failed";
+		fillField("status", status, acroForm);
+	}
+
+	private void fillField(String fieldName, String value, PDAcroForm acroForm)
+			throws IOException {
+		PDField field = acroForm.getField(fieldName);
+		if (field != null) {
+			if (value == null) {
+				field.setValue("");
+			} else {
+				field.setValue(value);
+			}
+
+			field.setReadonly(true);
+			field.setNoExport(true);
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java b/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f8575f99903ee08083ad966a7843bbb5bbbca33
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java
@@ -0,0 +1,505 @@
+/**
+ * <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.certificate.manager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.persistence.TypedQuery;
+
+import org.apache.commons.io.IOUtils;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.IdentityRef;
+import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.notifications.NotificationsManager;
+import org.olat.core.commons.services.notifications.PublisherData;
+import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Roles;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.i18n.I18nManager;
+import org.olat.core.util.vfs.FileStorage;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.core.util.vfs.VFSItem;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.core.util.vfs.VFSManager;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.certificate.Certificate;
+import org.olat.course.certificate.CertificateLight;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.model.CertificateImpl;
+import org.olat.course.certificate.model.CertificateInfos;
+import org.olat.course.certificate.model.CertificateTemplateImpl;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.run.environment.CourseEnvironment;
+import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupService;
+import org.olat.group.model.SearchBusinessGroupParams;
+import org.olat.modules.vitero.model.GroupRole;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.repository.model.RepositoryEntrySecurity;
+import org.olat.resource.OLATResource;
+import org.olat.user.UserManager;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service("certificatesManager")
+public class CertificatesManagerImpl implements CertificatesManager, InitializingBean {
+	
+	private static final OLog log = Tracing.createLoggerFor(CertificatesManagerImpl.class);
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private RepositoryManager repositoryManager;
+	@Autowired
+	private NotificationsManager notificationsManager;
+	@Autowired
+	private BusinessGroupService businessGroupService; 
+
+	private FileStorage usersStorage;
+	private FileStorage templatesStorage;
+	
+	@Override
+	public void afterPropertiesSet() {
+		//create the folders
+		getCertificateTemplatesRoot();
+		templatesStorage = new FileStorage(getCertificateTemplatesRootContainer());
+		getCertificateRoot();
+		usersStorage = new FileStorage(getCertificateRootContainer());
+	}
+
+	@Override
+	public SubscriptionContext getSubscriptionContext(ICourse course) {
+		CourseNode cn = course.getRunStructure().getRootNode();
+		CourseEnvironment ce = course.getCourseEnvironment();
+		SubscriptionContext ctxt = new SubscriptionContext(ORES_CERTIFICATE, ce.getCourseResourceableId(), cn.getIdent());
+		return ctxt;
+	}
+
+	@Override
+	public PublisherData getPublisherData(ICourse course, String businessPath) {
+		String data = String.valueOf(course.getCourseEnvironment().getCourseResourceableId());
+		PublisherData pData = new PublisherData(ORES_CERTIFICATE, data, businessPath);
+		return pData;
+	}
+
+	@Override
+	public void markPublisherNews(Identity ident, ICourse course) {
+		SubscriptionContext subsContext = getSubscriptionContext(course);
+		if (subsContext != null) {
+			NotificationsManager.getInstance().markPublisherNews(subsContext, ident, true);
+		}
+	}
+	
+	public void markPublisherNews(Identity ident, OLATResource courseResource) {
+		ICourse course = CourseFactory.loadCourse(courseResource);
+		SubscriptionContext subsContext = getSubscriptionContext(course);
+		if (subsContext != null) {
+			NotificationsManager.getInstance().markPublisherNews(subsContext, ident, true);
+		}
+	}
+	
+	@Override
+	public VFSLeaf getCertificateLeaf(Certificate certificate) {
+		VFSContainer cerContainer = this.getCertificateRootContainer();
+		VFSItem cerItem = cerContainer.resolve(certificate.getPath());
+		return cerItem instanceof VFSLeaf ? (VFSLeaf)cerItem : null;
+	}
+	
+	@Override
+	public Certificate getCertificateById(Long key) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer from certificate cer")
+		  .append(" where cer.key=:certificateKey");
+		List<Certificate> certificates = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Certificate.class)
+				.setParameter("certificateKey", key)
+				.getResultList();
+		return certificates.isEmpty() ? null : certificates.get(0);
+	}
+
+	@Override
+	public Certificate getCertificateByUuid(String uuid) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer from certificate cer")
+		  .append(" where cer.uuid=:uuid");
+		List<Certificate> certificates = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Certificate.class)
+				.setParameter("uuid", uuid)
+				.getResultList();
+		return certificates.isEmpty() ? null : certificates.get(0);
+	}
+
+	@Override
+	public List<CertificateLight> getLastCertificates(IdentityRef identity) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer from certificatelight cer")
+		  .append(" where cer.identityKey=:identityKey and cer.last=true");
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), CertificateLight.class)
+				.setParameter("identityKey", identity.getKey())
+				.getResultList();
+	}
+
+	@Override
+	public Certificate getLastCertificate(IdentityRef identity, Long resourceKey) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer from certificate cer")
+		  .append(" where cer.olatResource.key=:resourceKey and cer.identity.key=:identityKey and cer.last=true order by cer.creationDate");
+		List<Certificate> certififcates = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Certificate.class)
+				.setParameter("resourceKey", resourceKey)
+				.setParameter("identityKey", identity.getKey())
+				.setMaxResults(1)
+				.getResultList();
+		return certififcates.isEmpty() ? null : certififcates.get(0);
+	}
+
+	@Override
+	public List<Certificate> getCertificatesForNotifications(Identity identity, RepositoryEntry entry, Date lastNews) {
+		Roles roles = securityManager.getRoles(identity);
+		RepositoryEntrySecurity security = repositoryManager.isAllowed(identity, roles, entry);
+		if(!security.isEntryAdmin() && !security.isCourseCoach() && !security.isGroupCoach() && !security.isCourseParticipant() && !security.isGroupParticipant()) {
+			return Collections.emptyList();
+		}
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer from certificate cer")
+		  .append(" inner join fetch cer.identity ident")
+		  .append(" where cer.olatResource.key=:resourceKey and cer.last=true ");
+		//must be some kind of restrictions
+		boolean securityCheck = false;
+		List<Long> baseGroupKeys = null;
+		if(!security.isEntryAdmin()) {
+			sb.append(" and (");
+			boolean or = false;
+			if(security.isCourseCoach()) {
+				or = or(sb, or);
+				sb.append(" exists (select membership.identity.key from repoentrytogroup as rel, bgroup as reBaseGroup, bgroupmember membership ")
+				  .append("   where ident.key=membership.identity.key and rel.entry.key=:repoKey and rel.group=reBaseGroup and membership.group=reBaseGroup and membership.role='").append(GroupRole.participant).append("'")
+				  .append(" )");
+				securityCheck = true;
+			}
+			
+			if(security.isGroupCoach()) {
+				SearchBusinessGroupParams params = new SearchBusinessGroupParams(identity, true, false);
+				List<BusinessGroup> groups = businessGroupService.findBusinessGroups(params, entry, 0, -1);
+				if(groups.size() > 0) {
+					or = or(sb, or);
+					sb.append(" exists (select membership.identity.key from bgroupmember membership ")
+					  .append("   where ident.key=membership.identity.key and membership.group.key in (:groups) and membership.role='").append(GroupRole.participant).append("'")
+					  .append(" )");
+					
+					baseGroupKeys = new ArrayList<>(groups.size());
+					for(BusinessGroup group:groups) {
+						baseGroupKeys.add(group.getBaseGroup().getKey());
+					}
+					securityCheck = true;
+				}
+			}
+			
+			if(security.isCourseParticipant() || security.isGroupParticipant()) {
+				or = or(sb, or);
+				sb.append(" ident.key=:identityKey");
+				securityCheck = true;
+			}
+			sb.append(")");
+		} else {
+			securityCheck = true;
+		}
+		if(!securityCheck) {
+			return Collections.emptyList();
+		}
+		sb.append(" order by cer.creationDate");
+
+		TypedQuery<Certificate> certificates = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Certificate.class)
+				.setParameter("resourceKey", entry.getOlatResource().getKey());
+		
+		if(!security.isEntryAdmin()) {
+			if(security.isCourseCoach()) {
+				certificates.setParameter("repoKey", entry.getKey());
+			}
+			
+			if(security.isCourseParticipant() || security.isGroupParticipant()) {
+				certificates.setParameter("identityKey", identity.getKey());
+			}
+		}
+		
+		if(baseGroupKeys != null && !baseGroupKeys.isEmpty()) {
+			certificates.setParameter("groups", baseGroupKeys);
+		}
+		return certificates.getResultList();
+	}
+	
+	private final boolean or(StringBuilder sb, boolean or) {
+		if(or) sb.append(" or ");
+		else sb.append(" ");
+		return true;
+	}
+
+	@Override
+	public List<Certificate> getCertificates(IdentityRef identity, OLATResource resource) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer from certificate cer")
+		  .append(" where cer.olatResource.key=:resourceKey and cer.identity.key=:identityKey");
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Certificate.class)
+				.setParameter("resourceKey", resource.getKey())
+				.setParameter("identityKey", identity.getKey())
+				.getResultList();
+	}
+
+	@Override
+	public List<CertificateLight> getCertificates(OLATResource resource) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer from certificatelight cer")
+		  .append(" where cer.olatResourceKey=:resourceKey");
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), CertificateLight.class)
+				.setParameter("resourceKey", resource.getKey())
+				.getResultList();
+	}
+
+	@Override
+	public void generateCertificates(List<CertificateInfos> certificateInfos, RepositoryEntry entry, CertificateTemplate template) {
+		int count = 0;
+		for(CertificateInfos certificateInfo:certificateInfos) {
+			generateCertificate(certificateInfo, entry, template);
+			if(++count % 10 == 0) {
+				dbInstance.commitAndCloseSession();
+			}
+		}
+		markPublisherNews(null, entry.getOlatResource());
+	}
+
+	@Override
+	public Certificate generateCertificate(CertificateInfos certificateInfos, RepositoryEntry entry, CertificateTemplate template) {
+		Certificate certificate = peristCertificate(certificateInfos, entry, template);
+		markPublisherNews(null, entry.getOlatResource());
+		return certificate;
+	}
+
+	private Certificate peristCertificate(CertificateInfos certificateInfos, RepositoryEntry entry, CertificateTemplate template) {
+		OLATResource resource = entry.getOlatResource();
+		Identity identity = certificateInfos.getAssessedIdentity();
+		
+		CertificateImpl certificate = new CertificateImpl();
+		certificate.setOlatResource(resource);
+		certificate.setArchivedResourceKey(resource.getKey());
+		certificate.setCreationDate(new Date());
+		certificate.setLastModified(certificate.getCreationDate());
+		certificate.setIdentity(identity);
+		certificate.setUuid(UUID.randomUUID().toString());
+		certificate.setLast(true);
+		
+		String templateName;
+		InputStream templateStream = null;
+		String dir = usersStorage.generateDir();
+		try {
+			if(template == null) {
+				//default
+				templateName = "Certificate.pdf";
+				templateStream = CertificatesManager.class.getResourceAsStream("template.pdf");
+			} else {
+				templateName = template.getName();
+				File templateFile = getTemplateFile(template);
+				if(templateFile != null && templateFile.exists()) {
+					templateStream = new FileInputStream(templateFile);
+				} else {
+					templateStream = CertificatesManager.class.getResourceAsStream("template.pdf");
+				}
+			}
+			
+			File dirFile = new File(getCertificateRoot(), dir);
+			dirFile.mkdirs();
+			File certificateFile = new File(dirFile, templateName);
+			
+			Float score = certificateInfos.getScore();
+			Locale locale = I18nManager.getInstance().getLocaleOrDefault(null);
+			Boolean passed = certificateInfos.getPassed();
+			Date dateCertification = certificate.getCreationDate();
+			Date dateFirstCertification = getDateFirstCertification(identity, resource);
+
+			CertificateTemplateWorker worker = new CertificateTemplateWorker(identity, entry, score, passed, dateCertification, dateFirstCertification, locale, userManager);
+			worker.fill(templateStream, certificateFile);
+			certificate.setName(templateName);
+			certificate.setPath(dir + templateName);
+			
+			if(dateFirstCertification != null) {
+				//not the first certification, reset the last of the others certificates
+				removeLastFlag(identity, resource);
+			}
+			
+			dbInstance.getCurrentEntityManager().persist(certificate);
+		} catch (Exception e) {
+			log.error("", e);
+		} finally {
+			IOUtils.closeQuietly(templateStream);
+		}
+		return certificate;
+	}
+	
+	private Date getDateFirstCertification(Identity identity, OLATResource resource) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select cer.creationDate from certificate cer")
+		  .append(" where cer.olatResource.key=:resourceKey and cer.identity.key=:identityKey")
+		  .append(" order by cer.creationDate asc");
+		
+		List<Date> dates = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Date.class)
+				.setParameter("resourceKey", resource.getKey())
+				.setParameter("identityKey", identity.getKey())
+				.setMaxResults(1)
+				.getResultList();
+		return dates.isEmpty() ? null : dates.get(0);
+	}
+	
+	private void removeLastFlag(Identity identity, OLATResource resource) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("update certificate  cer set cer.last=false")
+		  .append(" where cer.olatResource.key=:resourceKey and cer.identity.key=:identityKey");
+		
+		dbInstance.getCurrentEntityManager().createQuery(sb.toString())
+				.setParameter("resourceKey", resource.getKey())
+				.setParameter("identityKey", identity.getKey())
+				.executeUpdate();
+	}
+
+	@Override
+	public List<CertificateTemplate> getTemplates() {
+		String sb = "select template from certificatetemplate template where template.publicTemplate=true";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb, CertificateTemplate.class)
+				.getResultList();
+	}
+	
+	public CertificateTemplate getTemplateById(Long key) {
+		String sb = "select template from certificatetemplate template where template.key=:templateKey";
+		List<CertificateTemplate> templates = dbInstance.getCurrentEntityManager()
+				.createQuery(sb, CertificateTemplate.class)
+				.setParameter("templateKey", key)
+				.getResultList();
+		return templates.isEmpty() ? null : templates.get(0);
+	}
+
+	@Override
+	public CertificateTemplate addTemplate(String name, File file, boolean publicTemplate) {
+		CertificateTemplateImpl template = new CertificateTemplateImpl();
+
+		template.setCreationDate(new Date());
+		template.setLastModified(template.getCreationDate());
+		template.setPublicTemplate(publicTemplate);
+
+		VFSLeaf templateLeaf;
+
+		String dir = templatesStorage.generateDir();
+		VFSContainer templateDir = templatesStorage.getContainer(dir);
+		
+		String renamedName = VFSManager.rename(templateDir, name);
+		if(renamedName != null) {
+			templateLeaf = templateDir.createChildLeaf(renamedName);
+		} else {
+			templateLeaf = templateDir.createChildLeaf(name);
+		}
+		
+		
+		try(InputStream inStream = Files.newInputStream(file.toPath())) {
+			if(VFSManager.copyContent(inStream, templateLeaf)) {
+				template.setName(name);
+				template.setPath(dir + "/" + templateLeaf.getName());
+				dbInstance.getCurrentEntityManager().persist(template);
+				return template;
+			}
+		} catch(IOException ex) {
+			log.error("", ex);
+		}
+		return null;
+	}
+	
+	@Override
+	public File getTemplateFile(CertificateTemplate template) {
+		String templatePath = template.getPath();
+		File root = getCertificateTemplatesRoot();
+		return new File(root, templatePath);
+	}
+	
+	@Override
+	public VFSLeaf getTemplateLeaf(CertificateTemplate template) {
+		String templatePath = template.getPath();
+		VFSContainer root = this.getCertificateTemplatesRootContainer();
+		VFSItem templateItem = root.resolve(templatePath);
+		return templateItem instanceof VFSLeaf ? (VFSLeaf)templateItem : null;
+	}
+	
+	public File getCertificateTemplatesRoot() {
+		Path path = Paths.get(FolderConfig.getCanonicalRoot(), "certificates", "templates");
+		File root = path.toFile();
+		if(!root.exists()) {
+			root.mkdirs();
+		}
+		return root;
+	}
+	
+	public VFSContainer getCertificateTemplatesRootContainer() {
+		return new OlatRootFolderImpl(File.separator + "certificates" + File.separator + "templates", null);
+	}
+	
+	public File getCertificateRoot() {
+		Path path = Paths.get(FolderConfig.getCanonicalRoot(), "certificates", "users");
+		File root = path.toFile();
+		if(!root.exists()) {
+			root.mkdirs();
+		}
+		return root;
+	}
+	
+	public VFSContainer getCertificateRootContainer() {
+		return new OlatRootFolderImpl(File.separator + "certificates" + File.separator + "users", null);
+	}
+}
diff --git a/src/main/java/org/olat/course/certificate/manager/CertificationNotificationHandler.java b/src/main/java/org/olat/course/certificate/manager/CertificationNotificationHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..13168dab49a40fc21268b49017df980b6938dbc8
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/manager/CertificationNotificationHandler.java
@@ -0,0 +1,139 @@
+/**
+ * <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.certificate.manager;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import org.olat.core.commons.services.notifications.NotificationsHandler;
+import org.olat.core.commons.services.notifications.NotificationsManager;
+import org.olat.core.commons.services.notifications.Publisher;
+import org.olat.core.commons.services.notifications.Subscriber;
+import org.olat.core.commons.services.notifications.SubscriptionInfo;
+import org.olat.core.commons.services.notifications.manager.NotificationsUpgradeHelper;
+import org.olat.core.commons.services.notifications.model.SubscriptionListItem;
+import org.olat.core.commons.services.notifications.model.TitleItem;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.id.Identity;
+import org.olat.core.id.context.BusinessControlFactory;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.Util;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.certificate.Certificate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.ui.CertificateController;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service("certificationNotificationHandler")
+public class CertificationNotificationHandler implements NotificationsHandler {
+	
+	private static final OLog log = Tracing.createLoggerFor(CertificationNotificationHandler.class);
+	
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private RepositoryManager repositoryManager;
+	@Autowired
+	private CertificatesManager certificatesManager;
+
+	@Override
+	public SubscriptionInfo createSubscriptionInfo(Subscriber subscriber, Locale locale, Date compareDate) {
+		SubscriptionInfo si = null;
+		Publisher p = subscriber.getPublisher();
+		if(!NotificationsUpgradeHelper.checkCourse(p)) {
+			//course don't exist anymore
+			NotificationsManager.getInstance().deactivate(p);
+			return NotificationsManager.getInstance().getNoSubscriptionInfo();
+		}
+		
+		try {
+			Date latestNews = p.getLatestNewsDate();
+			Identity identity = subscriber.getIdentity();
+			Translator trans = Util.createPackageTranslator(CertificateController.class, locale);
+
+			// do not try to create a subscription info if state is deleted - results in
+			// exceptions, course
+			// can't be loaded when already deleted
+			if (NotificationsManager.getInstance().isPublisherValid(p) && compareDate.before(latestNews)) {
+				Long courseId = new Long(p.getData());
+				final ICourse course = CourseFactory.loadCourse(courseId);
+				if (course != null) {
+					// course admins or users with the course right to have full access to
+					// the assessment tool will have full access to user tests
+					
+					RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+					List<Certificate> certificates = certificatesManager.getCertificatesForNotifications(identity, entry, latestNews);
+					for(Certificate certificate:certificates) {
+						Date modDate = certificate.getCreationDate();
+						Identity assessedIdentity = certificate.getIdentity();
+						String fullname = userManager.getUserDisplayName(assessedIdentity);
+						String desc = trans.translate("notifications.desc", new String[]{ fullname });
+
+						String urlToSend = null;
+						String businessPath = null;
+						if(p.getBusinessPath() != null) {
+							businessPath = p.getBusinessPath() + "[assessmentTool:0][Identity:" + assessedIdentity.getKey() + "]";
+							urlToSend = BusinessControlFactory.getInstance().getURLFromBusinessPathString(businessPath);
+						}
+						
+						SubscriptionListItem subListItem = new SubscriptionListItem(desc, urlToSend, businessPath, modDate, "o_icon_certificate");
+						if(si == null) {
+							String title = trans.translate("notifications.header", new String[]{ course.getCourseTitle() });
+							si = new SubscriptionInfo(subscriber.getKey(), p.getType(), new TitleItem(title, "o_icon_certificate"), null);
+						}
+						si.addSubscriptionListItem(subListItem);
+					}
+				}
+			} 
+			if(si == null) {
+				si = NotificationsManager.getInstance().getNoSubscriptionInfo();
+			}
+			return si;
+		} catch (Exception e) {
+			log.error("Error while creating assessment notifications", e);
+			return NotificationsManager.getInstance().getNoSubscriptionInfo();
+		}
+	}
+
+	@Override
+	public String createTitleInfo(Subscriber subscriber, Locale locale) {
+		try {
+			Long resId = subscriber.getPublisher().getResId();
+			String displayName = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(resId);
+			Translator trans = Util.createPackageTranslator(CertificateController.class, locale);
+			return trans.translate("notifications.title", new String[]{ displayName });
+		} catch (Exception e) {
+			log.error("Error while creating certificate notifications for subscriber: " + subscriber.getKey(), e);
+			return "-";
+		}
+	}
+
+	@Override
+	public String getType() {
+		return CertificatesManager.ORES_CERTIFICATE;
+	}
+}
diff --git a/src/main/java/org/olat/course/certificate/model/CertificateImpl.java b/src/main/java/org/olat/course/certificate/model/CertificateImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6e2800cf6eea7bbc98e3cd93a3e0eacf1493bd7
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/model/CertificateImpl.java
@@ -0,0 +1,193 @@
+/**
+ * <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.certificate.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Persistable;
+import org.olat.course.certificate.Certificate;
+import org.olat.resource.OLATResource;
+import org.olat.resource.OLATResourceImpl;
+
+/**
+ * 
+ * Initial date: 21.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="certificate")
+@Table(name="o_cer_certificate")
+public class CertificateImpl implements Certificate, Persistable {
+
+	private static final long serialVersionUID = 2360631986446191873L;
+
+	@Id
+	@GeneratedValue(generator = "system-uuid")
+	@GenericGenerator(name = "system-uuid", strategy = "hilo")
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@Column(name="c_uuid", nullable=false, insertable=true, updatable=false)
+	private String uuid;
+
+	@Column(name="c_name", nullable=false, insertable=true, updatable=false)
+	private String name;
+	@Column(name="c_path", nullable=false, insertable=true, updatable=false)
+	private String path;
+	@Column(name="c_last", nullable=false, insertable=true, updatable=true)
+	private boolean last;
+	@Column(name="c_archived_resource_id", nullable=false, insertable=true, updatable=false)
+	private Long archivedResourceKey;
+	
+	@ManyToOne(targetEntity=OLATResourceImpl.class,fetch=FetchType.LAZY, optional=true)
+	@JoinColumn(name="fk_olatresource", nullable=false, insertable=true, updatable=true)
+	private OLATResource olatResource;
+	
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY, optional=true)
+	@JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false)
+	private Identity identity;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+	
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+	
+	public String getUuid() {
+		return uuid;
+	}
+
+	public void setUuid(String uuid) {
+		this.uuid = uuid;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public boolean isLast() {
+		return last;
+	}
+
+	public void setLast(boolean last) {
+		this.last = last;
+	}
+
+	public String getPath() {
+		return path;
+	}
+
+	public void setPath(String path) {
+		this.path = path;
+	}
+
+	public Long getArchivedResourceKey() {
+		return archivedResourceKey;
+	}
+
+	public void setArchivedResourceKey(Long archivedResourceKey) {
+		this.archivedResourceKey = archivedResourceKey;
+	}
+
+	public OLATResource getOlatResource() {
+		return olatResource;
+	}
+
+	public void setOlatResource(OLATResource olatResource) {
+		this.olatResource = olatResource;
+	}
+
+	public Identity getIdentity() {
+		return identity;
+	}
+
+	public void setIdentity(Identity identity) {
+		this.identity = identity;
+	}
+
+	@Override
+	public int hashCode() {
+		return key == null ? -23984 : key.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof CertificateImpl) {
+			CertificateImpl prefs = (CertificateImpl)obj;
+			return key != null && key.equals(prefs.key);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+	
+
+}
diff --git a/src/main/java/org/olat/course/certificate/model/CertificateInfos.java b/src/main/java/org/olat/course/certificate/model/CertificateInfos.java
new file mode 100644
index 0000000000000000000000000000000000000000..10575711aa3b5e200e3cb7a3ffbb8bd7cff2b57e
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/model/CertificateInfos.java
@@ -0,0 +1,53 @@
+/**
+ * <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.certificate.model;
+
+import org.olat.core.id.Identity;
+
+/**
+ * 
+ * Initial date: 24.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificateInfos {
+	
+	private final Float score;
+	private final Boolean passed;
+	private final Identity assessedIdentity;
+	
+	public CertificateInfos(Identity assessedIdentity, Float score, Boolean passed) {
+		this.score = score;
+		this.passed = passed;
+		this.assessedIdentity = assessedIdentity;
+	}
+	
+	public Float getScore() {
+		return score;
+	}
+
+	public Boolean getPassed() {
+		return passed;
+	}
+	
+	public Identity getAssessedIdentity() {
+		return assessedIdentity;
+	}
+}
diff --git a/src/main/java/org/olat/course/certificate/model/CertificateLightImpl.java b/src/main/java/org/olat/course/certificate/model/CertificateLightImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f7584edebe8e881c06e3fc00fd9f693f10d1932
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/model/CertificateLightImpl.java
@@ -0,0 +1,145 @@
+/**
+ * <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.certificate.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.olat.core.id.Persistable;
+import org.olat.course.certificate.CertificateLight;
+
+/**
+ * 
+ * Initial date: 21.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="certificatelight")
+@Table(name="o_cer_certificate")
+public class CertificateLightImpl implements CertificateLight, Persistable {
+
+	private static final long serialVersionUID = 2360631986446191873L;
+
+	@Id
+	@GeneratedValue(generator = "system-uuid")
+	@GenericGenerator(name = "system-uuid", strategy = "hilo")
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+
+	@Column(name="c_uuid", nullable=false, insertable=true, updatable=false)
+	private String uuid;
+	@Column(name="c_name", nullable=false, insertable=true, updatable=false)
+	private String name;
+	@Column(name="c_last", nullable=false, insertable=true, updatable=true)
+	private boolean last;
+	@Column(name="c_archived_resource_id", nullable=false, insertable=true, updatable=false)
+	private Long olatResourceKey;
+	
+	@Column(name="fk_identity", nullable=false, insertable=true, updatable=false)
+	private Long identityKey;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public String getUuid() {
+		return uuid;
+	}
+
+	public void setUuid(String uuid) {
+		this.uuid = uuid;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Long getOlatResourceKey() {
+		return olatResourceKey;
+	}
+
+	public void setOlatResourceKey(Long olatResourceKey) {
+		this.olatResourceKey = olatResourceKey;
+	}
+
+	public Long getIdentityKey() {
+		return identityKey;
+	}
+
+	public void setIdentityKey(Long identityKey) {
+		this.identityKey = identityKey;
+	}
+
+	@Override
+	public int hashCode() {
+		return key == null ? -23984 : key.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof CertificateLightImpl) {
+			CertificateLightImpl prefs = (CertificateLightImpl)obj;
+			return key != null && key.equals(prefs.key);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+	
+
+}
diff --git a/src/main/java/org/olat/course/certificate/model/CertificateTemplateImpl.java b/src/main/java/org/olat/course/certificate/model/CertificateTemplateImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f14c1b83f418bfcec42f7bee6ccca3ae3d7d594
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/model/CertificateTemplateImpl.java
@@ -0,0 +1,141 @@
+/**
+ * <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.certificate.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.olat.core.id.Persistable;
+import org.olat.course.certificate.CertificateTemplate;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="certificatetemplate")
+@Table(name="o_cer_template")
+public class CertificateTemplateImpl implements CertificateTemplate, Persistable {
+
+	private static final long serialVersionUID = 2921992276818297249L;
+
+	@Id
+	@GeneratedValue(generator = "system-uuid")
+	@GenericGenerator(name = "system-uuid", strategy = "hilo")
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+
+	@Column(name="c_name", nullable=false, insertable=true, updatable=true)
+	private String name;
+	@Column(name="c_path", nullable=false, insertable=true, updatable=true)
+	private String path;
+	@Column(name="c_public", nullable=false, insertable=true, updatable=true)
+	private boolean publicTemplate;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+	
+	public String getPath() {
+		return path;
+	}
+
+	public void setPath(String path) {
+		this.path = path;
+	}
+
+	@Override
+	public boolean isPublicTemplate() {
+		return publicTemplate;
+	}
+
+	public void setPublicTemplate(boolean publicTemplate) {
+		this.publicTemplate = publicTemplate;
+	}
+
+	@Override
+	public int hashCode() {
+		return key == null ? -23984 : key.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof CertificateTemplateImpl) {
+			CertificateTemplateImpl prefs = (CertificateTemplateImpl)obj;
+			return key != null && key.equals(prefs.key);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/AssessedIdentityCertificatesController.java b/src/main/java/org/olat/course/certificate/ui/AssessedIdentityCertificatesController.java
new file mode 100644
index 0000000000000000000000000000000000000000..e632e4ae54f02f48fc21b6debd564993adcab92c
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/AssessedIdentityCertificatesController.java
@@ -0,0 +1,141 @@
+/**
+ * <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.certificate.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.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.gui.media.MediaResource;
+import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.core.util.vfs.VFSMediaResource;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.certificate.Certificate;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.model.CertificateInfos;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.run.scoring.ScoreEvaluation;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.repository.RepositoryEntry;
+import org.olat.resource.OLATResource;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 21.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AssessedIdentityCertificatesController extends BasicController {
+	
+	private final Link generateLink;
+	private final VelocityContainer mainVC;
+	
+	private final OLATResource resource;
+	private final UserCourseEnvironment assessedUserCourseEnv;
+	
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	public AssessedIdentityCertificatesController(UserRequest ureq, WindowControl wControl,
+			UserCourseEnvironment assessedUserCourseEnv) {
+		super(ureq, wControl);
+
+		this.assessedUserCourseEnv = assessedUserCourseEnv;
+		resource = assessedUserCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseResource();
+		
+		mainVC = createVelocityContainer("certificate_overview");
+		loadList();
+		generateLink = LinkFactory.createLink("generate.certificate", "generate", getTranslator(), mainVC, this, Link.BUTTON);
+		putInitialPanel(mainVC);
+	}
+	
+	private void loadList() {
+		Identity assessedIdentity = assessedUserCourseEnv.getIdentityEnvironment().getIdentity();
+		List<Certificate> certificates = certificatesManager.getCertificates(assessedIdentity, resource);
+		List<Link> certificatesLink = new ArrayList<>(certificates.size());
+		int count = 0;
+		for(Certificate certificate:certificates) {
+			Link link = LinkFactory.createLink("download." + count++, "download",
+					getTranslator(), mainVC, this, Link.NONTRANSLATED);
+			link.setCustomDisplayText(certificate.getName());
+			link.setIconLeftCSS("o_icon o_icon-lg o_filetype_pdf");
+			link.setUserObject(certificate);
+			link.setTarget("_blank");
+			certificatesLink.add(link);
+		}
+		mainVC.contextPut("certificates", certificatesLink);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if(generateLink == source) {
+			doGenerateCertificate() ;
+		} else if(source instanceof Link) {
+			Link link = (Link)source;
+			String cmd = link.getCommand();
+			if("download".equals(cmd)) {
+				Certificate certificate = (Certificate)link.getUserObject();
+				doDownload(ureq, certificate);
+			}
+		}
+	}
+	
+	private void doDownload(UserRequest ureq, Certificate certificate) {
+		VFSLeaf certificateLeaf = certificatesManager.getCertificateLeaf(certificate);
+		MediaResource resource = new VFSMediaResource(certificateLeaf);
+		ureq.getDispatchResult().setResultingMediaResource(resource);
+	}
+	
+	private void doGenerateCertificate() {
+		CertificateTemplate template = null;
+		ICourse course = CourseFactory.loadCourse(resource);
+		String templateKey = course.getCourseConfig().getPdfCertificateTemplate();
+		RepositoryEntry courseEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		if(StringHelper.isLong(templateKey)) {
+			template = certificatesManager.getTemplateById(new Long(templateKey));
+		}
+
+		CourseNode rootNode = course.getRunStructure().getRootNode();
+		Identity assessedIdentity = assessedUserCourseEnv.getIdentityEnvironment().getIdentity();
+		ScoreEvaluation scoreEval = assessedUserCourseEnv.getScoreAccounting().getScoreEvaluation(rootNode);
+		Float score = scoreEval == null ? null : scoreEval.getScore();
+		Boolean passed = scoreEval == null ? null : scoreEval.getPassed();
+		CertificateInfos certificateInfos = new CertificateInfos(assessedIdentity, score, passed);
+		certificatesManager.generateCertificate(certificateInfos, courseEntry, template);
+		loadList();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementController.java b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementController.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a669b7584f3fb3cf114fb073f80cf0bbf7d2930
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementController.java
@@ -0,0 +1,366 @@
+/**
+* OLAT - Online Learning and Training<br>
+* http://www.olat.org
+* <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
+* <p>
+* http://www.apache.org/licenses/LICENSE-2.0
+* <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>
+* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.
+*/
+
+package org.olat.course.certificate.ui;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.olat.NewControllerFactory;
+import org.olat.core.CoreSpringFactory;
+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.components.segmentedview.SegmentViewComponent;
+import org.olat.core.gui.components.segmentedview.SegmentViewEvent;
+import org.olat.core.gui.components.segmentedview.SegmentViewFactory;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+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.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.id.Identity;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.id.Roles;
+import org.olat.core.id.context.BusinessControl;
+import org.olat.core.id.context.BusinessControlFactory;
+import org.olat.core.id.context.ContextEntry;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.Util;
+import org.olat.core.util.mail.ContactList;
+import org.olat.core.util.mail.ContactMessage;
+import org.olat.core.util.resource.OresHelper;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.EfficiencyStatement;
+import org.olat.course.assessment.EfficiencyStatementManager;
+import org.olat.course.assessment.IdentityAssessmentEditController;
+import org.olat.course.assessment.IdentityAssessmentOverviewController;
+import org.olat.course.assessment.portfolio.EfficiencyStatementArtefact;
+import org.olat.course.certificate.Certificate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.co.ContactFormController;
+import org.olat.portfolio.EPArtefactHandler;
+import org.olat.portfolio.PortfolioModule;
+import org.olat.portfolio.model.artefacts.AbstractArtefact;
+import org.olat.portfolio.ui.artefacts.collect.ArtefactWizzardStepsController;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.olat.user.UserManager;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Description:<br>
+ * Displays the users efficiency statement
+ * 
+ * <P>
+ * Initial Date:  11.08.2005 <br>
+ * @author gnaegi
+ */
+public class CertificateAndEfficiencyStatementController extends BasicController {
+
+	private static final String usageIdentifyer = "org.olat.course.assessment.EfficiencyStatementController";
+	
+	private VelocityContainer mainVC;
+	private SegmentViewComponent segmentView;
+	private Link certificateLink, courseDetailsLink;
+	private Link collectArtefactLink, homeLink, courseLink, groupLink, contactLink;
+	
+	private final Certificate certificate;
+	private final EfficiencyStatement efficiencyStatement;
+	private final Identity statementOwner;
+	private final BusinessGroup businessGroup;
+	private final RepositoryEntry courseRepoEntry;
+	
+	
+	private Controller ePFCollCtrl;
+	private CloseableModalController cmc;
+	private ContactFormController contactCtrl;
+	private CertificateController certificateCtrl;
+	private IdentityAssessmentOverviewController courseDetailsCtrl;
+	
+	@Autowired
+	private PortfolioModule portfolioModule;
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	/**
+	 * The constructor shows the efficiency statement given as parameter for the current user
+	 * @param wControl
+	 * @param ureq
+	 * @param courseId
+	 */
+	public CertificateAndEfficiencyStatementController(WindowControl wControl, UserRequest ureq, EfficiencyStatement efficiencyStatement) {
+		this(wControl, ureq, ureq.getIdentity(), null, null, null, efficiencyStatement, false);
+	}
+	
+	/**
+	 * This constructor show the efficiency statement for the course repository key and the current user
+	 * @param wControl
+	 * @param ureq
+	 * @param courseRepoEntryKey
+	 */
+	public CertificateAndEfficiencyStatementController(WindowControl wControl, UserRequest ureq, Long resourceKey) {
+		this(wControl, ureq, 
+				ureq.getIdentity(), null, resourceKey, CoreSpringFactory.getImpl(RepositoryService.class).loadByResourceKey(resourceKey),
+				EfficiencyStatementManager.getInstance().getUserEfficiencyStatementByResourceKey(resourceKey, ureq.getIdentity()),
+				false);
+	}
+	
+	public CertificateAndEfficiencyStatementController(WindowControl wControl, UserRequest ureq, Identity statementOwner,
+			BusinessGroup businessGroup, Long resourceKey, RepositoryEntry courseRepo, EfficiencyStatement efficiencyStatement, boolean links) {
+		super(ureq, wControl);
+		setTranslator(Util.createPackageTranslator(IdentityAssessmentEditController.class, getLocale(), getTranslator()));
+		setTranslator(UserManager.getInstance().getPropertyHandlerTranslator(getTranslator()));
+		
+		this.courseRepoEntry = courseRepo;
+		this.businessGroup = businessGroup;
+
+		if(businessGroup == null && courseRepo != null) {
+			ICourse course = CourseFactory.loadCourse(courseRepo.getOlatResource());
+			List<BusinessGroup> groups = course.getCourseEnvironment().getCourseGroupManager().getParticipatingBusinessGroups(statementOwner);
+			if(groups.size() > 0) {
+				businessGroup = groups.get(0);
+			}
+		}
+
+		this.statementOwner = statementOwner;
+		this.efficiencyStatement = efficiencyStatement;
+		certificate = certificatesManager.getLastCertificate(statementOwner, resourceKey);
+		
+		mainVC = createVelocityContainer("certificate_efficiencystatement");
+		populateAssessedIdentityInfos(ureq, statementOwner, courseRepo, businessGroup, links);
+		
+		if(efficiencyStatement != null && certificate != null) {
+			segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
+			certificateLink = LinkFactory.createLink("details.certificate", mainVC, this);
+			segmentView.addSegment(certificateLink, true);
+			selectCertificate(ureq);
+			
+			courseDetailsLink = LinkFactory.createLink("details.course.infos", mainVC, this);
+			segmentView.addSegment(courseDetailsLink, false);
+		} else if(efficiencyStatement != null) {
+			selectCourseInfos(ureq);
+		} else if(certificate != null) {
+			selectCertificate(ureq);
+		}
+		
+		if(statementOwner.equals(ureq.getIdentity())) {
+			EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
+			if(portfolioModule.isEnabled() && artHandler != null && artHandler.isEnabled()) {
+				collectArtefactLink = LinkFactory.createCustomLink("collectArtefactLink", "collectartefact", "", Link.NONTRANSLATED, mainVC, this);
+				collectArtefactLink.setIconLeftCSS("o_icon o_icon-lg o_icon_eportfolio_add");
+			}
+		}
+
+		putInitialPanel(mainVC);
+		//message, that no data is available. This may happen in the case the "open efficiency" link is available, while in the meantime an author
+		//disabled the efficiency statement.
+		//String text = translate("efficiencystatement.nodata");
+		//Controller messageCtr = MessageUIFactory.createSimpleMessage(ureq, getWindowControl(), text);
+		//listenTo(messageCtr);//gets disposed as this controller gets disposed.
+		//mainVC.put("assessmentOverviewTable",  messageCtr.getInitialComponent());
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	private void populateAssessedIdentityInfos(UserRequest ureq, Identity statementOwner, RepositoryEntry courseRepo, BusinessGroup group, boolean links) { 
+		if(efficiencyStatement != null) {
+			mainVC.contextPut("courseTitle", StringHelper.escapeHtml(efficiencyStatement.getCourseTitle()));
+			mainVC.contextPut("date", StringHelper.formatLocaleDateTime(efficiencyStatement.getLastUpdated(), ureq.getLocale()));
+		} else if(courseRepo != null) {
+			mainVC.contextPut("courseTitle", StringHelper.escapeHtml(courseRepo.getDisplayname()));
+		}
+		
+		if(courseRepoEntry != null && links) {
+			courseLink = LinkFactory.createButton("course.link", mainVC, this);
+			courseLink.setIconLeftCSS("o_icon o_CourseModule_icon");
+			mainVC.put("course.link", courseLink);
+		}
+		
+		mainVC.contextPut("user", statementOwner.getUser());			
+		mainVC.contextPut("username", statementOwner.getName());
+		
+		Roles roles = ureq.getUserSession().getRoles();
+		boolean isAdministrativeUser = (roles.isAuthor() || roles.isGroupManager() || roles.isUserManager() || roles.isOLATAdmin());	
+		List<UserPropertyHandler> userPropertyHandlers = UserManager.getInstance().getUserPropertyHandlersFor(usageIdentifyer, isAdministrativeUser);
+		mainVC.contextPut("userPropertyHandlers", userPropertyHandlers);
+
+		if(!getIdentity().equals(statementOwner) && links) {
+			homeLink = LinkFactory.createButton("home.link", mainVC, this);
+			homeLink.setIconLeftCSS("o_icon o_icon_home");
+			mainVC.put("home.link", homeLink);
+			
+			contactLink = LinkFactory.createButton("contact.link", mainVC, this);
+			contactLink.setIconLeftCSS("o_icon o_icon_mail");
+			mainVC.put("contact.link", contactLink);
+		}
+
+		if(group != null) {
+			mainVC.contextPut("groupName", StringHelper.escapeHtml(group.getName()));
+			if(links) {
+				groupLink = LinkFactory.createButton("group.link", mainVC, this);
+				groupLink.setIconLeftCSS("o_icon o_icon_group");
+				mainVC.put("group.link", groupLink);
+			}
+		}
+	}
+	
+	/**
+	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
+	 */
+	@Override
+	public void event(UserRequest ureq, Component source, Event event) {
+		if(source.equals(collectArtefactLink)){
+			popupArtefactCollector(ureq);
+		} else if (source == homeLink) {
+			doOpenHome(ureq);
+		} else if (source == courseLink) {
+			doOpenCourse(ureq);
+		} else if (source == groupLink) {
+			doOpenGroup(ureq);
+		} else if (source == contactLink) {
+			contact(ureq);
+		} else if(source == segmentView && event instanceof SegmentViewEvent) {
+			SegmentViewEvent sve = (SegmentViewEvent)event;
+			if(certificateLink.getComponentName().equals(sve.getComponentName())) {
+				selectCertificate(ureq);
+			} else if(courseDetailsLink.getComponentName().equals(sve.getComponentName())) {
+				selectCourseInfos(ureq);
+			}
+		}
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if (source == cmc) {
+			cleanUp();
+		} else if (source == contactCtrl) {
+			cmc.deactivate();
+			cleanUp();
+		}
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(cmc);
+		removeAsListenerAndDispose(contactCtrl);
+		cmc = null;
+		contactCtrl = null;
+	}
+	
+	private void selectCertificate(UserRequest ureq) {
+		if(certificateCtrl == null) {
+			certificateCtrl = new CertificateController(ureq, getWindowControl(), certificate);
+			listenTo(certificateCtrl);
+		}
+		mainVC.put("segmentCmp", certificateCtrl.getInitialComponent());
+	}
+	
+	private void selectCourseInfos(UserRequest ureq) {
+		if(courseDetailsCtrl == null) {
+			courseDetailsCtrl = new IdentityAssessmentOverviewController(ureq, getWindowControl(), efficiencyStatement.getAssessmentNodes());
+			listenTo(courseDetailsCtrl);
+		}
+		mainVC.put("segmentCmp", courseDetailsCtrl.getInitialComponent());
+	}
+
+	private void contact(UserRequest ureq) {
+		removeAsListenerAndDispose(cmc);
+
+		ContactMessage cmsg = new ContactMessage(getIdentity());
+		ContactList contactList = new ContactList("to");
+		contactList.add(statementOwner);
+		cmsg.addEmailTo(contactList);
+		contactCtrl = new ContactFormController(ureq, getWindowControl(), true, false, false, cmsg);
+		listenTo(contactCtrl);
+		cmc = new CloseableModalController(getWindowControl(), translate("close"), contactCtrl.getInitialComponent());
+		cmc.activate();
+		listenTo(cmc);
+	}
+	
+	private void doOpenGroup(UserRequest ureq) {
+		if(businessGroup != null) {
+			List<ContextEntry> ces = new ArrayList<ContextEntry>(1);
+			OLATResourceable ores = OresHelper.createOLATResourceableInstance("BusinessGroup", businessGroup.getKey());
+			ces.add(BusinessControlFactory.getInstance().createContextEntry(ores));
+	
+			BusinessControl bc = BusinessControlFactory.getInstance().createFromContextEntries(ces);
+			WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
+			NewControllerFactory.getInstance().launch(ureq, bwControl);
+		}
+	}
+	
+	private void doOpenCourse(UserRequest ureq) {
+		if(courseRepoEntry != null) {
+			List<ContextEntry> ces = new ArrayList<ContextEntry>(1);
+			OLATResourceable ores = OresHelper.createOLATResourceableInstance("RepositoryEntry", courseRepoEntry.getKey());
+			ces.add(BusinessControlFactory.getInstance().createContextEntry(ores));
+	
+			BusinessControl bc = BusinessControlFactory.getInstance().createFromContextEntries(ces);
+			WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
+			NewControllerFactory.getInstance().launch(ureq, bwControl);
+		}
+	}
+	
+	private void doOpenHome(UserRequest ureq) {
+		List<ContextEntry> ces = new ArrayList<ContextEntry>(1);
+		ces.add(BusinessControlFactory.getInstance().createContextEntry(statementOwner));
+
+		BusinessControl bc = BusinessControlFactory.getInstance().createFromContextEntries(ces);
+		WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
+		NewControllerFactory.getInstance().launch(ureq, bwControl);
+	}
+
+	/**
+	 * opens the collect-artefact wizard 
+	 * 
+	 * @param ureq
+	 */
+	private void popupArtefactCollector(UserRequest ureq) {
+		EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
+		if(artHandler != null && artHandler.isEnabled()) {
+			AbstractArtefact artefact = artHandler.createArtefact();
+			artefact.setAuthor(getIdentity());//only author can create artefact
+			//no business path becouse we cannot launch an efficiency statement
+			artefact.setCollectionDate(new Date());
+			artefact.setTitle(translate("artefact.title", new String[]{efficiencyStatement.getCourseTitle()}));
+			artHandler.prefillArtefactAccordingToSource(artefact, efficiencyStatement);
+			ePFCollCtrl = new ArtefactWizzardStepsController(ureq, getWindowControl(), artefact, (VFSContainer)null);
+			listenTo(ePFCollCtrl);
+			
+			//set flag for js-window-resizing (see velocity)
+			mainVC.contextPut("collectwizard", true);
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListController.java b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListController.java
new file mode 100644
index 0000000000000000000000000000000000000000..4019eed5c0d6933e6bdb290820832063b84daf4f
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListController.java
@@ -0,0 +1,293 @@
+/**
+ * <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.certificate.ui;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.olat.NewControllerFactory;
+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.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiColumnModel;
+import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.components.stack.BreadcrumbPanelAware;
+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.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+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.Util;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.course.CorruptedCourseException;
+import org.olat.course.assessment.EfficiencyStatement;
+import org.olat.course.assessment.EfficiencyStatementManager;
+import org.olat.course.assessment.IdentityAssessmentEditController;
+import org.olat.course.assessment.bulk.PassedCellRenderer;
+import org.olat.course.assessment.model.UserEfficiencyStatementLight;
+import org.olat.course.assessment.portfolio.EfficiencyStatementArtefact;
+import org.olat.course.certificate.CertificateLight;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementListModel.CertificateAndEfficiencyStatement;
+import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementListModel.Cols;
+import org.olat.portfolio.EPArtefactHandler;
+import org.olat.portfolio.PortfolioModule;
+import org.olat.portfolio.model.artefacts.AbstractArtefact;
+import org.olat.portfolio.ui.artefacts.collect.ArtefactWizzardStepsController;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.repository.RepositoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 21.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificateAndEfficiencyStatementListController extends FormBasicController implements BreadcrumbPanelAware {
+	
+	private static final String CMD_SHOW = "cmd.show";
+	private static final String CMD_LAUNCH_COURSE = "cmd.launch.course";
+	private static final String CMD_DELETE = "cmd.delete";
+	private static final String CMD_ARTEFACT = "cmd.artefact";
+	private static final String CMD_CERTIFICATE = "cmd.certificate";
+	
+	private FlexiTableElement tableEl;
+	private BreadcrumbPanel stackPanel;
+	private CertificateAndEfficiencyStatementListModel tableModel;
+
+	
+	private DialogBoxController confirmDeleteCtr;
+	private ArtefactWizzardStepsController ePFCollCtrl;
+	
+	@Autowired
+	private EfficiencyStatementManager esm;
+	@Autowired
+	private PortfolioModule portfolioModule;
+	@Autowired
+	private RepositoryManager repositoryManager;
+	@Autowired
+	private RepositoryService repositoryService;
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	public CertificateAndEfficiencyStatementListController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, LAYOUT_BAREBONE);
+		setTranslator(Util.createPackageTranslator(IdentityAssessmentEditController.class, getLocale(), getTranslator()));
+		
+		initForm(ureq);
+	}
+
+	@Override
+	public void setBreadcrumbPanel(BreadcrumbPanel stackPanel) {
+		this.stackPanel = stackPanel;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		FlexiTableColumnModel tableColumnModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.displayName.i18n(), Cols.displayName.ordinal()));
+		tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.score.i18n(), Cols.score.ordinal()));
+		tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.passed.i18n(), Cols.passed.ordinal(), new PassedCellRenderer()));
+		tableColumnModel.addFlexiColumnModel(new StaticFlexiColumnModel("table.header.show",
+				translate("table.header.show"), CMD_SHOW));
+		tableColumnModel.addFlexiColumnModel(new StaticFlexiColumnModel(Cols.certificate.i18n(), Cols.certificate.ordinal(), CMD_CERTIFICATE,
+				true, Cols.certificate.name(), new StaticFlexiCellRenderer(CMD_CERTIFICATE, new DownloadCertificateCellRenderer())));
+		tableColumnModel.addFlexiColumnModel(new StaticFlexiColumnModel("table.header.launchcourse",
+				translate("table.header.launchcourse"), CMD_LAUNCH_COURSE));
+		tableColumnModel.addFlexiColumnModel(new StaticFlexiColumnModel("table.header.delete",
+				translate("table.action.delete"), CMD_DELETE));
+		
+		//delete
+		EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
+		if(portfolioModule.isEnabled() && artHandler != null && artHandler.isEnabled()) {
+			tableColumnModel.addFlexiColumnModel(new StaticFlexiColumnModel("table.header.artefact",
+					Cols.efficiencyStatement.ordinal(), CMD_ARTEFACT,
+					new StaticFlexiCellRenderer(CMD_ARTEFACT, new AsArtefactCellRenderer())));
+		}
+		
+		tableModel = new CertificateAndEfficiencyStatementListModel(tableColumnModel);
+		loadModel();
+		tableEl = uifactory.addTableElement(getWindowControl(), "certificates", tableModel, formLayout);
+	}
+	
+	private void loadModel() {
+		Map<Long, CertificateAndEfficiencyStatement> resourceKeyToStatments = new HashMap<>();
+		List<CertificateAndEfficiencyStatement> statments = new ArrayList<>();
+		List<UserEfficiencyStatementLight> efficiencyStatementsList = esm.findEfficiencyStatementsLight(getIdentity());
+		for(UserEfficiencyStatementLight efficiencyStatement:efficiencyStatementsList) {
+			CertificateAndEfficiencyStatement wrapper = new CertificateAndEfficiencyStatement();
+			wrapper.setDisplayName(efficiencyStatement.getShortTitle());
+			wrapper.setPassed(efficiencyStatement.getPassed());
+			wrapper.setScore(efficiencyStatement.getScore());
+			wrapper.setEfficiencyStatementKey(efficiencyStatement.getKey());
+			
+			statments.add(wrapper);
+			resourceKeyToStatments.put(efficiencyStatement.getArchivedResourceKey(), wrapper);
+		}
+		
+		List<CertificateLight> certificates = certificatesManager.getLastCertificates(getIdentity());
+		for(CertificateLight certificate:certificates) {
+			Long resourceKey = certificate.getOlatResourceKey();
+			CertificateAndEfficiencyStatement wrapper = resourceKeyToStatments.get(resourceKey);
+			if(wrapper == null) {
+				wrapper = new CertificateAndEfficiencyStatement();
+				resourceKeyToStatments.put(resourceKey, wrapper);
+				statments.add(wrapper);
+			} else {
+				wrapper.setResourceKey(resourceKey);
+			}
+			wrapper.setCertificate(certificate);
+		}
+		
+		tableModel.setObjects(statments);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent te = (SelectionEvent) event;
+				String cmd = te.getCommand();
+				CertificateAndEfficiencyStatement statement = tableModel.getObject(te.getIndex());
+				if(CMD_LAUNCH_COURSE.equals(cmd)) {
+					doLaunchCourse(ureq, statement.getResourceKey());
+				} else if(CMD_DELETE.equals(cmd)) {
+					doConfirmDelete(ureq, statement);
+				} else if(CMD_SHOW.equals(cmd)) {
+					doShowStatement(ureq, statement);
+				} else if(CMD_ARTEFACT.equals(cmd)) {
+					doCollectArtefact(ureq, statement.getDisplayName(), statement.getEfficiencyStatementKey());
+				}
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		 if (source == confirmDeleteCtr) {
+			if (DialogBoxUIFactory.isYesEvent(event)) {
+				CertificateAndEfficiencyStatement statement = (CertificateAndEfficiencyStatement)confirmDeleteCtr.getUserObject();
+				doDelete(statement.getEfficiencyStatementKey());
+			}
+		}
+	}
+	
+	private void doShowStatement(UserRequest ureq, CertificateAndEfficiencyStatement statement) {
+		RepositoryEntry entry = repositoryService.loadByResourceKey(statement.getResourceKey());
+		EfficiencyStatement efficiencyStatment = esm.getUserEfficiencyStatementByKey(statement.getEfficiencyStatementKey());
+		CertificateAndEfficiencyStatementController efficiencyCtrl = new CertificateAndEfficiencyStatementController(getWindowControl(), ureq,
+				getIdentity(), null, statement.getResourceKey(), entry, efficiencyStatment, false);
+		listenTo(efficiencyCtrl);
+		stackPanel.pushController(statement.getDisplayName(), efficiencyCtrl);
+	}
+
+	private void doConfirmDelete(UserRequest ureq, CertificateAndEfficiencyStatement statement) {
+		RepositoryEntry re = repositoryService.loadByResourceKey(statement.getResourceKey());
+		if(re == null) {
+			String text = translate("efficiencyStatements.delete.confirm", statement.getDisplayName());
+			confirmDeleteCtr = activateYesNoDialog(ureq, null, text, confirmDeleteCtr);
+			confirmDeleteCtr.setUserObject(statement);
+		} else {
+			showWarning("efficiencyStatements.cannot.delete");
+		}
+	}
+	
+	private void doDelete(Long efficiencyStatementKey) {
+		UserEfficiencyStatementLight efficiencyStatement = esm.getUserEfficiencyStatementLightByKey(efficiencyStatementKey);
+		esm.deleteEfficiencyStatement(efficiencyStatement);
+
+		loadModel();
+		tableEl.reset();
+		showInfo("info.efficiencyStatement.deleted");
+	}
+
+	private void doLaunchCourse(UserRequest ureq, Long resourceKey) {
+		RepositoryEntry entry = repositoryService.loadByResourceKey(resourceKey);
+		if(entry == null) {
+			showWarning("efficiencyStatements.course.noexists");
+		} else if (!repositoryManager.isAllowedToLaunch(ureq, entry)) {
+			showWarning("efficiencyStatements.course.noaccess");
+		} else {
+			try {
+				String businessPath = "[RepositoryEntry:" + entry.getKey() + "]";
+				NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
+			} catch (CorruptedCourseException e) {
+				logError("Course corrupted: " + entry.getKey() + " (" + entry.getResourceableId() + ")", e);
+				showError("cif.error.corrupted");
+			}
+		}
+	}
+	
+	private void doCollectArtefact(UserRequest ureq, String title, Long efficiencyStatementKey) {
+		EPArtefactHandler<?> artHandler = portfolioModule.getArtefactHandler(EfficiencyStatementArtefact.ARTEFACT_TYPE);
+		if(artHandler != null && artHandler.isEnabled()) {
+			AbstractArtefact artefact = artHandler.createArtefact();
+			artefact.setAuthor(getIdentity());//only author can create artefact
+			//no business path becouse we cannot launch an efficiency statement
+			artefact.setCollectionDate(new Date());
+			artefact.setTitle(translate("artefact.title", new String[]{ title }));
+			EfficiencyStatement fullStatement = EfficiencyStatementManager.getInstance().getUserEfficiencyStatementByKey(efficiencyStatementKey);
+			artHandler.prefillArtefactAccordingToSource(artefact, fullStatement);
+			ePFCollCtrl = new ArtefactWizzardStepsController(ureq, getWindowControl(), artefact, (VFSContainer)null);
+			listenTo(ePFCollCtrl);
+		}
+	}
+
+	public class AsArtefactCellRenderer implements FlexiCellRenderer {
+		
+		@Override
+		public void render(Renderer renderer, StringOutput sb, Object cellValue, int row,
+				FlexiTableComponent source, URLBuilder ubu, Translator translator) {
+			sb.append("<i class='o_icon o_icon-lg o_icon_eportfolio_add'> </i> <span title=\"")
+				.append(translate("table.add.as.artefact"))
+				.append("\"> </span>");
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListModel.java b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..01dc54944c9948274b9fe2051eb73f73e19a6f3c
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListModel.java
@@ -0,0 +1,138 @@
+/**
+ * <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.certificate.ui;
+
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.course.certificate.CertificateLight;
+import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementListModel.CertificateAndEfficiencyStatement;
+
+/**
+ * 
+ * Initial date: 22.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificateAndEfficiencyStatementListModel extends DefaultFlexiTableDataModel<CertificateAndEfficiencyStatement> {
+	
+	public CertificateAndEfficiencyStatementListModel(FlexiTableColumnModel columnModel) {
+		super(columnModel);
+	}
+
+	@Override
+	public DefaultFlexiTableDataModel<CertificateAndEfficiencyStatement> createCopyWithEmptyList() {
+		return new CertificateAndEfficiencyStatementListModel(getTableColumnModel());
+	}
+
+	@Override
+	public Object getValueAt(int row, int col) {
+		CertificateAndEfficiencyStatement statement = getObject(row);
+		switch(Cols.values()[col]) {
+			case displayName: return statement.getDisplayName();
+			case score:
+				Float score = statement.getScore();
+				return AssessmentHelper.getRoundedScore(score);
+			case passed: return statement.getPassed();
+			case certificate:
+				return statement.getCertificate();
+			case efficiencyStatement: return statement.getEfficiencyStatementKey();
+		}
+		return null;
+	}
+	
+	public static enum Cols {
+		
+		displayName("table.header.course"),
+		score("table.header.score"),
+		passed("table.header.passed"),
+		efficiencyStatement("table.header.certificate"),
+		certificate("table.header.certificate");
+		
+		private final String i18n;
+		
+		private Cols(String i18n) {
+			this.i18n = i18n;
+		}
+		
+		public String i18n() {
+			return i18n;
+		}
+	}
+	
+	public static class CertificateAndEfficiencyStatement {
+		
+		private Float score;
+		private Boolean passed;
+		private String displayName;
+		
+		private Long resourceKey;
+		private Long efficiencyStatementKey;
+		private CertificateLight certificate;
+
+		public String getDisplayName() {
+			return displayName;
+		}
+		
+		public void setDisplayName(String displayName) {
+			this.displayName = displayName;
+		}
+
+		public Float getScore() {
+			return score;
+		}
+
+		public void setScore(Float score) {
+			this.score = score;
+		}
+
+		public Boolean getPassed() {
+			return passed;
+		}
+
+		public void setPassed(Boolean passed) {
+			this.passed = passed;
+		}
+
+		public CertificateLight getCertificate() {
+			return certificate;
+		}
+		
+		public void setCertificate(CertificateLight certificate) {
+			this.certificate = certificate;
+		}
+
+		public Long getResourceKey() {
+			return resourceKey;
+		}
+
+		public void setResourceKey(Long resourceKey) {
+			this.resourceKey = resourceKey;
+		}
+
+		public Long getEfficiencyStatementKey() {
+			return efficiencyStatementKey;
+		}
+
+		public void setEfficiencyStatementKey(Long efficiencyStatementKey) {
+			this.efficiencyStatementKey = efficiencyStatementKey;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/course/certificate/ui/CertificateChooserController.java b/src/main/java/org/olat/course/certificate/ui/CertificateChooserController.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f00f88b0d1f60872daa3410f3c32bd3d1a760d6
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/CertificateChooserController.java
@@ -0,0 +1,195 @@
+/**
+ * <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.certificate.ui;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+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.FileElement;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+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.link.Link;
+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.util.StringHelper;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificateChooserController extends FormBasicController {
+	
+	private FileElement fileEl;
+	private FormLink selectLink, uploadLink;
+	private SingleSelection publicTemplatesEl;
+
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	private CertificateTemplate selectedTemplate;
+	
+	public CertificateChooserController(UserRequest ureq, WindowControl wControl, CertificateTemplate template) {
+		super(ureq, wControl);
+		
+		this.selectedTemplate = template;
+		
+		initForm(ureq);
+	}
+	
+	public CertificateTemplate getSelectedTemplate() {
+		return selectedTemplate;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		
+		List<CertificateTemplate> templates = certificatesManager.getTemplates();
+		String[] templatesKeys = new String[templates.size() + 1];
+		String[] templatesValues = new String[templates.size() + 1];
+		templatesKeys[0] = "def";
+		templatesValues[0] = "Default";
+		
+		int count = 1;
+		for(CertificateTemplate template:templates) {
+			templatesKeys[count] = template.getKey().toString();
+			templatesValues[count++] = template.getName();
+		}
+		
+		publicTemplatesEl = uifactory.addDropdownSingleselect("public.templates", formLayout, templatesKeys, templatesValues, null);
+		if(selectedTemplate != null) {
+			String selectedTemplateKey = selectedTemplate.getKey().toString();
+			for(String templateKey:templatesKeys) {
+				if(templateKey.equals(selectedTemplateKey)) {
+					publicTemplatesEl.select(templateKey, true);
+				}
+			}
+		}
+		
+		FormLayoutContainer selectButtonCont = FormLayoutContainer.createButtonLayout("selectButton", getTranslator());
+		selectButtonCont.setRootForm(mainForm);
+		formLayout.add(selectButtonCont);
+		selectLink = uifactory.addFormLink("select", selectButtonCont, Link.BUTTON);
+		
+		uifactory.addSpacerElement("spaceman", formLayout, false);
+		
+		fileEl = uifactory.addFileElement("template.file", formLayout);
+		fileEl.addActionListener(FormEvent.ONCHANGE);
+		
+		FormLayoutContainer buttonCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		buttonCont.setRootForm(mainForm);
+		formLayout.add(buttonCont);
+		uploadLink = uifactory.addFormLink("upload", buttonCont, Link.BUTTON);
+		uifactory.addFormCancelButton("cancel", buttonCont, ureq, getWindowControl());
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(selectLink == source) {
+			if(publicTemplatesEl.isOneSelected()) {
+				
+				String selectedkey = publicTemplatesEl.getSelectedKey();
+				if("def".equals(selectedkey)) {
+					
+				} else if(StringHelper.isLong(selectedkey)) {
+					try {
+						Long key = new Long(selectedkey);
+						selectedTemplate = certificatesManager.getTemplateById(key);
+					} catch (NumberFormatException e) {
+						logError("", e);
+					}
+				}
+			}
+			
+			if(selectedTemplate != null) {
+				fireEvent(ureq, Event.DONE_EVENT);
+			}
+		} else if(uploadLink == source) {
+			if(validatePdf()) {
+				doUpload(ureq);
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+	
+	private void doUpload(UserRequest ureq) {
+		File template = fileEl.getUploadFile();
+		if(template != null) {
+			String name = fileEl.getUploadFileName();
+			selectedTemplate = certificatesManager.addTemplate(name, template, false);
+			fireEvent(ureq, Event.DONE_EVENT);
+		}
+	}
+	
+	private boolean validatePdf() {
+		boolean allOk = true;
+		
+		File pdf = fileEl.getUploadFile();
+		fileEl.clearError();
+		if(pdf != null && pdf.exists()) {
+			PDDocument document = null;
+			try (InputStream in = Files.newInputStream(pdf.toPath())) {		
+				document = PDDocument.load(in);
+				if (document.isEncrypted()) {
+					fileEl.setErrorKey("upload.error.encrypted", null);
+					allOk &= false;
+				}
+			} catch(Exception ex) {
+				logError("", ex);
+				fileEl.setErrorKey("upload.unkown.error", null);
+				allOk &= false;
+			} finally {
+				IOUtils.closeQuietly(document);
+			}
+		}
+		
+		return allOk;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/CertificateController.java b/src/main/java/org/olat/course/certificate/ui/CertificateController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a6973531afb2e19663119a2e6652d3398bbdeb4
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/CertificateController.java
@@ -0,0 +1,68 @@
+/**
+ * <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.certificate.ui;
+
+import org.olat.core.commons.modules.singlepage.SinglePageController;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+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.util.vfs.VFSContainer;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.course.certificate.Certificate;
+import org.olat.course.certificate.CertificatesManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 22.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificateController extends BasicController {
+	
+	private final SinglePageController pageCtrl;
+	
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	public CertificateController(UserRequest ureq, WindowControl wControl, Certificate certificate) {
+		super(ureq, wControl);
+		
+		VFSLeaf certificateLeaf = certificatesManager.getCertificateLeaf(certificate);
+		VFSContainer container = certificateLeaf.getParentContainer();
+		String filename = certificateLeaf.getName();
+		pageCtrl = new SinglePageController(ureq, getWindowControl(), container, filename, false);
+		listenTo(pageCtrl);
+
+		putInitialPanel(pageCtrl.getInitialComponent());
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		//
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/CertificatesAdminController.java b/src/main/java/org/olat/course/certificate/ui/CertificatesAdminController.java
new file mode 100644
index 0000000000000000000000000000000000000000..35091eb2a5d527ac94831dc714b9ce533fad8be3
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/CertificatesAdminController.java
@@ -0,0 +1,174 @@
+/**
+ * <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.certificate.ui;
+
+import java.util.List;
+
+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.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificatesAdminController extends FormBasicController {
+	
+	private FormLink uploadLink;
+	private FlexiTableElement tableEl;
+	private TemplatesDataModel tableModel;
+	
+	private CloseableModalController cmc;
+	private UploadCertificateController uploadCtrl;
+
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	public CertificatesAdminController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, "admin_certificates");
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		
+		FlexiTableColumnModel tableColumnModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.name.i18n(), Cols.name.ordinal()));
+		
+		tableModel = new TemplatesDataModel(tableColumnModel);
+		tableEl = uifactory.addTableElement(getWindowControl(), "templates", tableModel, formLayout);
+
+		uploadLink = uifactory.addFormLink("upload", formLayout, Link.BUTTON);
+		updateDataModel();
+	}
+	
+	private void updateDataModel() {
+		List<CertificateTemplate> templates = certificatesManager.getTemplates();
+		tableModel.setObjects(templates);
+		tableEl.reset();
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(source == uploadLink) {
+			doUpload(ureq);
+		}
+	}
+
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(uploadCtrl == source) {
+			if(event == Event.DONE_EVENT) {
+				updateDataModel();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(uploadCtrl);
+		removeAsListenerAndDispose(cmc);
+		uploadCtrl = null;
+		cmc = null;
+	}
+
+	private void doUpload(UserRequest ureq) {
+		removeAsListenerAndDispose(uploadCtrl);
+		removeAsListenerAndDispose(cmc);
+		
+		uploadCtrl = new UploadCertificateController(ureq, getWindowControl());
+		listenTo(uploadCtrl);
+		
+		String title = translate("upload.title");
+		cmc = new CloseableModalController(getWindowControl(), "close", uploadCtrl.getInitialComponent(), true, title);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	public static enum Cols {
+
+		name("template.name");
+		
+		private final String i18n;
+		
+		private Cols(String i18n) {
+			this.i18n = i18n;
+		}
+		
+		public String i18n() {
+			return i18n;
+		}
+	}
+	
+	private static class TemplatesDataModel extends DefaultFlexiTableDataModel<CertificateTemplate> {
+		
+		public TemplatesDataModel(FlexiTableColumnModel columnModel) {
+			super(columnModel);
+		}
+
+		@Override
+		public DefaultFlexiTableDataModel<CertificateTemplate> createCopyWithEmptyList() {
+			return new TemplatesDataModel(getTableColumnModel());
+		}
+
+		@Override
+		public Object getValueAt(int row, int col) {
+			CertificateTemplate template = getObject(row);
+			switch(Cols.values()[col]) {
+				case name: return template.getName();
+			}
+			return null;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/course/certificate/ui/CertificatesOptionsController.java b/src/main/java/org/olat/course/certificate/ui/CertificatesOptionsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..99a255e869b97488061594de9328e8bfacfc4c58
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/CertificatesOptionsController.java
@@ -0,0 +1,416 @@
+/**
+ * <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.certificate.ui;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.olat.core.dispatcher.mapper.Mapper;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+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.IntegerElement;
+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.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.link.Link;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.gui.media.NotFoundMediaResource;
+import org.olat.core.id.Identity;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.logging.activity.ILoggingAction;
+import org.olat.core.logging.activity.LearningResourceLoggingAction;
+import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.Util;
+import org.olat.core.util.coordinate.CoordinatorManager;
+import org.olat.core.util.event.EventBus;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.core.util.vfs.VFSMediaResource;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.EfficiencyStatementManager;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.PDFCertificatesOptions;
+import org.olat.course.certificate.RecertificationTimeUnit;
+import org.olat.course.config.CourseConfig;
+import org.olat.course.config.CourseConfigEvent;
+import org.olat.course.config.CourseConfigEvent.CourseConfigType;
+import org.olat.course.config.ui.CourseOptionsController;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryEntryManagedFlag;
+import org.olat.repository.RepositoryManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificatesOptionsController extends FormBasicController {
+
+	private SingleSelection pdfCertificatesEl;
+	private MultipleSelectionElement efficencyEl;
+	private MultipleSelectionElement reCertificationEl;
+	private IntegerElement reCertificationTimelapseEl;
+	private SingleSelection reCertificationTimelapseUnitEl;
+	private FormLayoutContainer templateCont, recertificationCont;
+	private FormLink selectTemplateLink;
+
+	private final boolean editable;
+	private CourseConfig courseConfig;
+	private final RepositoryEntry entry;
+	private CertificateTemplate selectedTemplate;
+	
+	private CloseableModalController cmc;
+	private CertificateChooserController certificateChooserCtrl;
+	private DialogBoxController enableEfficiencyDC, disableEfficiencyDC;
+
+	private static final String[] pdfCertificatesOptionsKeys = new String[] {
+		PDFCertificatesOptions.none.name(),
+		PDFCertificatesOptions.auto.name(),
+		PDFCertificatesOptions.manual.name()
+	};
+	
+	private static final String[] timelapseUnitKeys = new String[] {
+		RecertificationTimeUnit.day.name(),
+		RecertificationTimeUnit.week.name(),
+		RecertificationTimeUnit.month.name(),
+		RecertificationTimeUnit.year.name()
+	};
+	
+	private final String mapperUrl;
+
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	/**
+	 * @param name
+	 * @param chatEnabled
+	 */
+	public CertificatesOptionsController(UserRequest ureq, WindowControl wControl,
+			RepositoryEntry entry, CourseConfig courseConfig, boolean editable) {
+		super(ureq, wControl);
+		setTranslator(Util.createPackageTranslator(CourseOptionsController.class, getLocale(), getTranslator()));
+		this.courseConfig = courseConfig;
+		this.entry = entry;
+		this.editable = editable;
+		
+		mapperUrl = registerMapper(ureq, new TemplateMapper());
+		
+		initForm (ureq);
+	}
+	
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormTitle("options.certificates.title");
+		setFormTitleIconCss("o_icon o_icon_certificate");
+		setFormContextHelp("org.olat.course.config.ui","course-efficiency.html","help.hover.course-eff");
+		
+		boolean effEnabled = courseConfig.isEfficencyStatementEnabled();
+		boolean managedEff = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.efficencystatement);
+		efficencyEl = uifactory.addCheckboxesHorizontal("effIsOn", "chkbx.efficency.onoff", formLayout, new String[] {"xx"}, new String[] {""});
+		efficencyEl.addActionListener(FormEvent.ONCHANGE);
+		efficencyEl.select("xx", effEnabled);
+		efficencyEl.setEnabled(editable && !managedEff);
+		
+		
+		PDFCertificatesOptions pdfCertificateOpt = courseConfig.getPdfCertificateOption();
+		String[] pdfCertificatesOptionsValues = new String[] {
+				translate("pdf.certificates.none"),
+				translate("pdf.certificates.auto"),
+				translate("pdf.certificates.manual")
+		};
+		pdfCertificatesEl = uifactory.addDropdownSingleselect("pdf.certificates", formLayout, pdfCertificatesOptionsKeys, pdfCertificatesOptionsValues, null);
+		pdfCertificatesEl.addActionListener(FormEvent.ONCHANGE);
+		if(pdfCertificateOpt != null) {
+			pdfCertificatesEl.select(pdfCertificateOpt.name(), true);
+		}
+		pdfCertificatesEl.setEnabled(editable && !managedEff);
+		
+		String templatePage = velocity_root + "/select_certificate.html";
+		templateCont = FormLayoutContainer.createCustomFormLayout("template.cont", getTranslator(), templatePage);
+		templateCont.setRootForm(mainForm);
+		templateCont.contextPut("mapperUrl", mapperUrl);
+		formLayout.add(templateCont);
+		templateCont.setLabel("pdf.certificates.template", null);
+
+		selectTemplateLink = uifactory.addFormLink("select", templateCont, Link.BUTTON);
+		String templateId = courseConfig.getPdfCertificateTemplate();
+		if(StringHelper.isLong(templateId)) {
+			selectedTemplate = certificatesManager.getTemplateById(new Long(templateId));
+			if(selectedTemplate != null) {
+				templateCont.contextPut("templateName", selectedTemplate.getName());
+			}
+		}
+		
+		boolean reCertificationEnabled = courseConfig.isRecertificationEnabled();
+		reCertificationEl = uifactory.addCheckboxesHorizontal("recertification", formLayout, new String[]{ "xx" }, new String[]{ "" });
+		reCertificationEl.addActionListener(FormEvent.ONCHANGE);
+		if(reCertificationEnabled) {
+			reCertificationEl.select("xx", true);
+		}
+		
+		String recertificationPage = velocity_root + "/recertification.html";
+		recertificationCont = FormLayoutContainer.createCustomFormLayout("timelapse.cont", getTranslator(), recertificationPage);
+		recertificationCont.setRootForm(mainForm);
+		formLayout.add(recertificationCont);
+		
+		int timelapse = courseConfig.getRecertificationTimelapse();
+		reCertificationTimelapseEl = uifactory.addIntegerElement("timelapse", timelapse, recertificationCont);
+		reCertificationTimelapseEl.setDomReplacementWrapperRequired(false);
+		reCertificationTimelapseEl.setDisplaySize(4);
+		
+		String[] timelapseUnitValues = new String[] {
+			translate("recertification.day"), translate("recertification.week"),
+			translate("recertification.month"), translate("recertification.year")
+		};
+		RecertificationTimeUnit timelapseUnit = courseConfig.getRecertificationTimelapseUnit();
+		reCertificationTimelapseUnitEl = uifactory.addDropdownSingleselect("timelapse.unit", recertificationCont, timelapseUnitKeys, timelapseUnitValues, null);
+		reCertificationTimelapseUnitEl.setDomReplacementWrapperRequired(false);
+		if(timelapseUnit != null) {
+			reCertificationTimelapseUnitEl.select(timelapseUnit.name(), true);
+		} else {
+			reCertificationTimelapseUnitEl.select(RecertificationTimeUnit.month.name(), true);
+		}
+
+		if(editable) {
+			FormLayoutContainer buttonCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+			buttonCont.setRootForm(mainForm);
+			formLayout.add(buttonCont);
+			uifactory.addFormSubmitButton("save", buttonCont);
+		}
+		
+		updateUI();
+	}
+	
+	private void updateUI() {
+		boolean none = pdfCertificatesEl.isSelected(PDFCertificatesOptions.none.ordinal());
+		
+		templateCont.setVisible(!none);
+		selectTemplateLink.setEnabled(!none);
+		if(none || selectedTemplate == null) {
+			templateCont.contextRemove("templateName");
+		} else {
+			templateCont.contextPut("templateName", selectedTemplate.getName());
+		}
+		
+		reCertificationEl.setVisible(!none);
+		
+		boolean enableRecertification = !none && reCertificationEl.isAtLeastSelected(1);
+		recertificationCont.setVisible(enableRecertification);
+		reCertificationTimelapseEl.setEnabled(enableRecertification);
+		reCertificationTimelapseUnitEl.setEnabled(enableRecertification);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	public void event(UserRequest ureq, Component source, Event event) {
+		super.event(ureq, source, event);
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if (source == disableEfficiencyDC) {
+			if (DialogBoxUIFactory.isOkEvent(event)) {
+				doChangeConfig(ureq);
+			}
+		} else if (source == enableEfficiencyDC) {
+			if (DialogBoxUIFactory.isOkEvent(event)) {				
+				doChangeConfig(ureq);
+			}
+		} else if(source == certificateChooserCtrl) {
+			if(event == Event.DONE_EVENT) {
+				CertificateTemplate template = certificateChooserCtrl.getSelectedTemplate();
+				if(template != null) {
+					doSetTemplate(template);
+				}
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		}
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(cmc);
+		cmc = null;
+	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(source == selectTemplateLink) {
+			doSelectTemplate(ureq);
+		} else if(source == reCertificationEl) {
+			updateUI();
+		} else if(source == pdfCertificatesEl) {
+			updateUI();
+		}
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		doSave(ureq);
+	}
+	
+	private void doSetTemplate(CertificateTemplate template) {
+		this.selectedTemplate = template;
+		if(selectedTemplate == null) {
+			templateCont.contextRemove("templateName");
+		} else {
+			templateCont.contextPut("templateName", template.getName());
+		}
+	}
+	
+	private void doSelectTemplate(UserRequest ureq) {
+		removeAsListenerAndDispose(certificateChooserCtrl);
+		removeAsListenerAndDispose(cmc);
+		
+		certificateChooserCtrl = new CertificateChooserController(ureq, getWindowControl(), selectedTemplate);
+		listenTo(certificateChooserCtrl);
+		
+		String title = translate("choose.title");
+		cmc = new CloseableModalController(getWindowControl(), "close", certificateChooserCtrl.getInitialComponent(), true, title);
+		listenTo(cmc);
+		cmc.activate();	
+	}
+	
+	private void doSave(UserRequest ureq) {
+		boolean confirmUpdateStatement = courseConfig.isEfficencyStatementEnabled() != efficencyEl.isSelected(0);
+		if(confirmUpdateStatement) {
+			if (courseConfig.isEfficencyStatementEnabled()) {
+				// a change from enabled Efficiency to disabled
+				disableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.todisabled"), disableEfficiencyDC);
+			} else {
+				// a change from disabled Efficiency
+				enableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.toenable"), enableEfficiencyDC);
+			}
+		} else {
+			doChangeConfig(ureq);
+		}
+	}
+	
+	private void doChangeConfig(UserRequest ureq) {
+		OLATResourceable courseOres = entry.getOlatResource();
+		ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId());
+		courseConfig = course.getCourseEnvironment().getCourseConfig();
+		
+		boolean enableEfficiencyStatment = efficencyEl.isSelected(0);
+		boolean updateStatement = courseConfig.isEfficencyStatementEnabled() != enableEfficiencyStatment;
+		courseConfig.setEfficencyStatementIsEnabled(enableEfficiencyStatment);
+		
+		
+		PDFCertificatesOptions pdfCertificateOption;
+		if(pdfCertificatesEl.isOneSelected()) {
+			pdfCertificateOption = PDFCertificatesOptions.valueOf(pdfCertificatesEl.getSelectedKey());
+		} else {
+			pdfCertificateOption = PDFCertificatesOptions.none;
+		}
+		courseConfig.setPdfCertificateOption(pdfCertificateOption);
+		
+		if(selectedTemplate != null) {
+			Long templateId = selectedTemplate.getKey();
+			courseConfig.setPdfCertificateTemplate(templateId.toString());
+		} else {
+			courseConfig.setPdfCertificateTemplate("");
+		}
+
+		boolean recertificationEnabled = reCertificationEl.isEnabled() && reCertificationEl.isAtLeastSelected(1);
+		courseConfig.setRecertificationEnabled(recertificationEnabled);
+
+		if(recertificationEnabled) {
+			int timelapse = reCertificationTimelapseEl.getIntValue();
+			courseConfig.setRecertificationTimelapse(timelapse);
+			
+			if(reCertificationTimelapseUnitEl.isOneSelected()) {
+				String selectedUnit = reCertificationTimelapseUnitEl.getSelectedKey();
+				RecertificationTimeUnit timeUnit = RecertificationTimeUnit.valueOf(selectedUnit);
+				courseConfig.setRecertificationTimelapseUnit(timeUnit);
+			} else {
+				courseConfig.setRecertificationTimelapseUnit(RecertificationTimeUnit.month);
+			}
+		} else {
+			courseConfig.setRecertificationTimelapse(0);
+			courseConfig.setRecertificationTimelapseUnit(null);
+		}
+
+		CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig);
+		CourseFactory.closeCourseEditSession(course.getResourceableId(), true);
+		
+		if(updateStatement) {
+			if(enableEfficiencyStatment) {
+	            // first create the efficiencies, send event to agency (all courses add link)
+				List<Identity> identitiesWithData = course.getCourseEnvironment().getCoursePropertyManager().getAllIdentitiesWithCourseAssessmentData(null);
+				EfficiencyStatementManager.getInstance().updateEfficiencyStatements(course, identitiesWithData);							
+			} else {
+	            // delete really the efficiencies of the users.
+				RepositoryEntry courseRepoEntry = RepositoryManager.getInstance().lookupRepositoryEntry(course, true);
+				EfficiencyStatementManager.getInstance().deleteEfficiencyStatementsFromCourse(courseRepoEntry.getKey());						
+			}
+			
+			//inform everybody else		
+			EventBus eventBus = CoordinatorManager.getInstance().getCoordinator().getEventBus();
+			CourseConfigEvent courseConfigEvent = new CourseConfigEvent(CourseConfigType.efficiencyStatement, course.getResourceableId());
+			eventBus.fireEventToListenersOf(courseConfigEvent, course);
+			
+			ILoggingAction loggingAction = enableEfficiencyStatment ?
+					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_ENABLED :
+					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_DISABLED;
+			ThreadLocalUserActivityLogger.log(loggingAction, getClass());
+		}
+		
+		fireEvent(ureq, Event.CHANGED_EVENT);
+	}
+	
+	public class TemplateMapper implements Mapper {
+
+		@Override
+		public MediaResource handle(String relPath, HttpServletRequest request) {
+			MediaResource resource;
+			if(selectedTemplate != null) {
+				VFSLeaf templateLeaf = certificatesManager.getTemplateLeaf(selectedTemplate);
+				resource = new VFSMediaResource(templateLeaf); 
+			} else {
+				resource = new NotFoundMediaResource(relPath);
+			}
+			return resource;
+		}
+		
+		
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java b/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..1991c420d70425da782491910222e422301319e6
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java
@@ -0,0 +1,65 @@
+/**
+ * <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.certificate.ui;
+
+import java.util.Locale;
+
+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.components.table.CustomCellRenderer;
+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.helpers.Settings;
+import org.olat.course.certificate.CertificateLight;
+
+/**
+ * 
+ * Initial date: 22.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class DownloadCertificateCellRenderer implements CustomCellRenderer, FlexiCellRenderer {
+
+	@Override
+	public void render(StringOutput sb, Renderer renderer, Object val, Locale locale, int alignment, String action) {
+		if(val instanceof CertificateLight) {
+			generateUrl(sb, (CertificateLight)val);
+		}
+	}
+
+	@Override
+	public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source,
+			URLBuilder ubu, Translator translator) {
+		if(cellValue instanceof CertificateLight) {
+			generateUrl(target, (CertificateLight)cellValue);
+		}
+	}
+	
+	private void generateUrl(StringOutput sb, CertificateLight certificate) {
+		sb.append("<a href='").append(Settings.getServerContextPath()).append("/certificate/")
+		  .append(certificate.getUuid()).append(".pdf")
+		  .append("' target='_blank'><i class='o_icon o_filetype_pdf'> </i> ")
+		  .append(certificate.getName()).append("</a>");
+	}
+	
+	
+}
diff --git a/src/main/java/org/olat/course/certificate/ui/UploadCertificateController.java b/src/main/java/org/olat/course/certificate/ui/UploadCertificateController.java
new file mode 100644
index 0000000000000000000000000000000000000000..c90141863e02982717902e4810429341d61f2630
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/UploadCertificateController.java
@@ -0,0 +1,130 @@
+/**
+ * <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.certificate.ui;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+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.FileElement;
+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.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.certificate.CertificatesManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 20.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class UploadCertificateController extends FormBasicController {
+	
+	private FileElement fileEl;
+	
+	@Autowired
+	private CertificatesManager certificatesManager;
+	
+	public UploadCertificateController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		fileEl = uifactory.addFileElement("template.file", formLayout);
+		fileEl.addActionListener(FormEvent.ONCHANGE);
+		
+		FormLayoutContainer buttonCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		buttonCont.setRootForm(mainForm);
+		formLayout.add(buttonCont);
+		uifactory.addFormSubmitButton("save", buttonCont);
+		uifactory.addFormCancelButton("cancel", buttonCont, ureq, getWindowControl());
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		File template = fileEl.getUploadFile();
+		if(template != null) {
+			String name = fileEl.getUploadFileName();
+			certificatesManager.addTemplate(name, template, true);
+		}
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+	
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(source == fileEl) {
+			validatePdf();
+		}
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		allOk &= validatePdf();
+		return allOk & super.validateFormLogic(ureq);
+	}
+
+	private boolean validatePdf() {
+		boolean allOk = true;
+		
+		File pdf = fileEl.getUploadFile();
+		fileEl.clearError();
+		if(pdf != null && pdf.exists()) {
+			PDDocument document = null;
+			try (InputStream in = Files.newInputStream(pdf.toPath())) {		
+				document = PDDocument.load(in);
+				if (document.isEncrypted()) {
+					fileEl.setErrorKey("upload.error.encrypted", null);
+					allOk &= false;
+				}
+			} catch(Exception ex) {
+				logError("", ex);
+				fileEl.setErrorKey("upload.unkown.error", null);
+				allOk &= false;
+			} finally {
+				IOUtils.closeQuietly(document);
+			}
+		}
+		
+		return allOk;
+	}
+}
diff --git a/src/main/java/org/olat/course/certificate/ui/_chelp/admin-certificates.html b/src/main/java/org/olat/course/certificate/ui/_chelp/admin-certificates.html
new file mode 100644
index 0000000000000000000000000000000000000000..1e3006c3f502d6c63d2edee0b6c4cb9c40ea48fc
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_chelp/admin-certificates.html
@@ -0,0 +1 @@
+<p>[placeholders]</p>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/_content/admin_certificates.html b/src/main/java/org/olat/course/certificate/ui/_content/admin_certificates.html
new file mode 100644
index 0000000000000000000000000000000000000000..e0fc82b7fb93e02ffeecd7862a8bedda77ebce31
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_content/admin_certificates.html
@@ -0,0 +1,11 @@
+<fieldset>
+	<legend>$r.contextHelpWithWrapper("org.olat.course.certificate.ui", "admin-certificates.html", "help.hover.admincertificates")
+	$r.translate("admin.certificates.title")</legend>
+	<div class="o_desc">$r.translate("admin.certificates.description")</div>
+	
+	$r.render("templates")
+	
+	<div class="o_button_group">
+		$r.render("upload")
+	</div>
+</fieldset>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/_content/certificate_efficiencystatement.html b/src/main/java/org/olat/course/certificate/ui/_content/certificate_efficiencystatement.html
new file mode 100644
index 0000000000000000000000000000000000000000..80fa9394263a57f8e1c3bba4943c05aede00c19b
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_content/certificate_efficiencystatement.html
@@ -0,0 +1,99 @@
+#if ($collectwizard)
+<script type="text/javascript">
+<!--
+## resize the window if collector-wizard is shown
+jQuery(function(){
+	window.resizeTo(1100,650);
+});
+//-->
+</script>
+#end
+
+<div class="o_efficiencystatement container-fluid">
+	#if($r.available("collectArtefactLink"))
+		<div class="pull-right">$r.render("collectArtefactLink")</div>
+	#end
+	<h3><i class="o_icon o_icon_certificate"> </i> $r.translate("efficiencystatement.title")</h3>
+	<div class="row form-horizontal">
+		<div class="col-md-6">
+    		<div class="form-group">
+				<label class="col-sm-3 control-label">$r.translate("username")</label>
+    			<div class="col-sm-9">
+					<div class="input-group">
+      					<p class="form-control-static">$username</p>
+				     	<div class="input-group-btn">
+				     	#if($r.available("home.link"))
+							$r.render("home.link")
+						#end
+						#if($r.available("contact.link"))
+							$r.render("contact.link")
+						#end
+						</div>
+				    </div>
+    			</div>
+    		</div>
+			#foreach( $propertyHandler in $userPropertyHandlers )
+				#if ($propertyHandler.getUserPropertyAsHTML($user, $locale) && $propertyHandler.getUserPropertyAsHTML($user, $locale) != "")
+					<div class="form-group">
+						<label class="col-sm-3 control-label">$r.translate($propertyHandler.i18nFormElementLabelKey())</label>
+						<div class="col-sm-9">
+							<p class="form-control-static">
+							#if($propertyHandler.name == "email")
+								$propertyHandler.getUserProperty($user, $locale)
+							#else
+								$propertyHandler.getUserPropertyAsHTML($user, $locale)
+							#end
+							</p>
+						</div>
+					</div>
+				#end
+			#end
+		</div>
+		<div class="col-md-6">
+			#if($courseTitle)
+				<div class="form-group">
+					<label class="col-sm-3 control-label">$r.translate("course")</label>
+					<div class="col-sm-9">
+						<div class="input-group">
+     							<p class="form-control-static">$courseTitle</p>
+							#if($r.available("course.link"))
+								<span class="input-group-btn">$r.render("course.link")</span>
+							#end
+						</div>
+					</div>
+				</div>
+			#end
+			#if($date)
+				<div class="form-group">
+					<label class="col-sm-3 control-label">$r.translate("date")</label>
+					<div class="col-sm-9">
+						<p class="form-control-static">$date</p>
+					</div>
+				</div>
+			#end
+			#if($groupName)
+				<div class="form-group">
+					<label class="col-sm-3 control-label">$r.translate("group")</label>
+					<div class="col-sm-9">
+						<div class="input-group">
+      						<p class="form-control-static">$groupName</p>
+				    		#if($r.available("group.link"))
+								<span class="input-group-btn">$r.render("group.link")</span>
+							#end
+						</div>
+					</div>
+				</div>
+			#end
+		</div>
+	</div>
+	<div class="row">
+	#if($r.available("segments"))
+		$r.render("segments")
+		#if($r.available("segmentCmp"))
+			<div class="o_block">$r.render("segmentCmp")</div>
+		#end
+	#elseif($r.available("segmentCmp"))
+		<div class="o_block">$r.render("segmentCmp")</div>
+	#end
+	</div>
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/_content/certificate_overview.html b/src/main/java/org/olat/course/certificate/ui/_content/certificate_overview.html
new file mode 100644
index 0000000000000000000000000000000000000000..937194557b9cdd8e69af3fdd758a7e0b92efbede
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_content/certificate_overview.html
@@ -0,0 +1,11 @@
+<div class="panel panel-default">
+	<ul class="list-group">
+		<li class="list-group-item"><span><i class="o_icon o_icon_certificate"> </i> $r.translate("certificate")</span></li>
+		<li class="list-group-item">
+			#foreach($certificate in $certificates)
+				$r.render($certificate.getComponentName())
+			#end
+			<div class="o_button_group">$r.render("generate.certificate")</div>
+		</li>
+	</ul>
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/_content/recertification.html b/src/main/java/org/olat/course/certificate/ui/_content/recertification.html
new file mode 100644
index 0000000000000000000000000000000000000000..8700f023fc14bc465c3dfc1002dca4312af1fff8
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_content/recertification.html
@@ -0,0 +1,5 @@
+<div class="o_form_element form-inline">
+	<span class="form-control-static">$r.translate("timelapse.after")</span>
+	<div class="o_form_element form-group">$r.render("timelapse")</div>
+	<div class="o_form_element form-group">$r.render("timelapse.unit")</div>
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/_content/select_certificate.html b/src/main/java/org/olat/course/certificate/ui/_content/select_certificate.html
new file mode 100644
index 0000000000000000000000000000000000000000..d08abe011a064a4c653045ef473ee895e92f9292
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_content/select_certificate.html
@@ -0,0 +1,8 @@
+<div class="o_form_element form-inline">
+#if($templateName)
+	<span class="form-control-static">
+		<a href="$mapperUrl/$templateName" target="_blank">$templateName</a>
+	</span>
+#end
+	<div class="o_form_element form-group">$r.render("select")</div>
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/certificate/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/certificate/ui/_i18n/LocalStrings_de.properties
new file mode 100644
index 0000000000000000000000000000000000000000..b8f01ca50028fde62190d9eb20c84bd191f0adf8
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_i18n/LocalStrings_de.properties
@@ -0,0 +1,34 @@
+admin.menu.title=Certificates
+admin.menu.title.alt=Certificates configuration
+admin.certificates.title=Certificates configuration
+admin.certificates.description=Hier können Sie Templates hochladen.
+certificate=Zertifikate
+table.header.certificate=Zertifikat
+choose.title=Template wählen
+details.certificate=Zertifikat
+details.course.infos=Kursdetails
+help.hover.admincertificates=Hilfe über Konfiguration von Zertifikaten
+chelp.admin-certificates.title=Certificates configuration
+options.certificates.title=Konfiguration Leistungnachweis
+generate.certificate=Zertifikat erzeugen
+pdf.certificates=PDF Zertifikat erzeugen
+pdf.certificates.none=Kein Zertifikat erzeugen
+pdf.certificates.auto=Automatisch wenn Kurs bestanden
+pdf.certificates.manual=Manuell durch Betreuer
+pdf.certificates.template=Zertifikatvorlage
+recertification=Rezertifizierung erlauben
+recertification.day=Tag
+recertification.week=Woche
+recertification.month=Monate
+recertification.year=Jahre
+timelapse.after=nach
+template.file=Datei
+template.name=Name
+public.templates=Templates
+upload=Template hochladen
+upload.title=Template hochladen
+upload.error.encrypted=Die Template darf nicht verschlüsselt sein.
+upload.unkown.error=Die Template kann nicht gelesen werden.
+notifications.title=Neue Zertifikate in Kurs "{0}"
+notifications.desc={0} hat eine neue Zertifikate bekommen.
+notifications.header=Neue Zertifikate in Kurs "{0}"
diff --git a/src/main/java/org/olat/course/certificate/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/certificate/ui/_i18n/LocalStrings_en.properties
new file mode 100644
index 0000000000000000000000000000000000000000..2702dbcc49103c099cb6c5d562a3d6ff5819dcfa
--- /dev/null
+++ b/src/main/java/org/olat/course/certificate/ui/_i18n/LocalStrings_en.properties
@@ -0,0 +1,14 @@
+admin.menu.title=Certificates
+admin.menu.title.alt=Certificates configuration
+options.certificates.title=Konfiguration Leistungnachweis
+pdf.certificates=PDF Zertifikat erzeugen
+pdf.certificates.none=Kein Zertifikat erzeugen
+pdf.certificates.auto=Automatisch wenn Kurs bestanden
+pdf.certificates.manual=Manuell durch Betreuer
+pdf.certificates.template=Zertifikatvorlage
+recertification=Rezertifizierung erlauben
+recertification.day=Tag
+recertification.week=Woche
+recertification.month=Monate
+recertification.year=Jahre
+timelapse.after=nach
diff --git a/src/main/java/org/olat/course/config/CourseConfig.java b/src/main/java/org/olat/course/config/CourseConfig.java
index 4aebe20520e4b0b9a36684e74b8cd4aed0e04a33..20e8bbaa144ab5e0f5ab1770939824de1ec6c222 100644
--- a/src/main/java/org/olat/course/config/CourseConfig.java
+++ b/src/main/java/org/olat/course/config/CourseConfig.java
@@ -29,6 +29,12 @@ import java.io.Serializable;
 import java.util.Hashtable;
 import java.util.Map;
 
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.olat.course.certificate.PDFCertificatesOptions;
+import org.olat.course.certificate.RecertificationTimeUnit;
+
 /**
  * Description: <br>
  * The CourseConfig class represents a configuration for a course. It is
@@ -55,6 +61,8 @@ import java.util.Map;
  * @author patrick
  */
 public class CourseConfig implements Serializable, Cloneable {
+	
+	private static final OLog log = Tracing.createLoggerFor(CourseConfig.class);
 
 	private static final long serialVersionUID = -1158707796830204185L;
 	/**
@@ -90,6 +98,12 @@ public class CourseConfig implements Serializable, Cloneable {
 	 * efficency statement
 	 */
 	transient public static final String KEY_EFFICENCY_ENABLED = "KEY_EFFICENCY_ENABLED";
+	transient public static final String PDF_CERTIFICATE_ENABLED = "PDF_CERTIFICATE_OPT";
+	transient public static final String PDF_CERTIFICATE_TEMPLATE = "PDF_CERTIFICATE_TEMPLATE";
+	transient public static final String RECERTIFICATION_ENABLED = "RECERTIFICATION_ENABLED";
+	transient public static final String RECERTIFICATION_TIMELAPSE = "RECERTIFICATION_TIMELAPSE";
+	transient public static final String RECERTIFICATION_TIMELAPSE_UNIT = "RECERTIFICATION_TIMELAPSE_UNIT";
+	
 	/**
 	 * course calendar
 	 */
@@ -222,6 +236,14 @@ public class CourseConfig implements Serializable, Cloneable {
 				if (configuration.containsKey(KEY_LOGLEVEL_STATISTIC)) configuration.remove(KEY_LOGLEVEL_STATISTIC);
 				this.version = 9;
 			}
+			
+			if (version == 9) {
+				if (!configuration.containsKey(PDF_CERTIFICATE_ENABLED)) configuration.put(PDF_CERTIFICATE_ENABLED, Boolean.FALSE);
+				if (!configuration.containsKey(PDF_CERTIFICATE_TEMPLATE)) configuration.put(PDF_CERTIFICATE_TEMPLATE, "");
+				if (!configuration.containsKey(RECERTIFICATION_ENABLED)) configuration.put(RECERTIFICATION_ENABLED, Boolean.FALSE);
+				if (!configuration.containsKey(RECERTIFICATION_TIMELAPSE)) configuration.put(RECERTIFICATION_TIMELAPSE, "");
+				this.version = 10;
+			}
 
 			/*
 			 * after resolving the issues, the version number is merged to the
@@ -361,6 +383,88 @@ public class CourseConfig implements Serializable, Cloneable {
 		Boolean bool = (Boolean) configuration.get(KEY_EFFICENCY_ENABLED);
 		return bool.booleanValue();
 	}
+	
+	
+	/**
+	 * @return true if the efficency statement is enabled
+	 */
+	public String getPdfCertificateTemplate() {
+		String templateId = (String) configuration.get(PDF_CERTIFICATE_TEMPLATE);
+		return templateId;
+	}
+	
+	/**
+	 * @param b
+	 */
+	public void setPdfCertificateTemplate(String templateId ) {
+		if(StringHelper.containsNonWhitespace(templateId)) {
+			configuration.put(PDF_CERTIFICATE_TEMPLATE, templateId);
+		} else {
+			configuration.remove(PDF_CERTIFICATE_TEMPLATE);
+		}
+	}
+	
+	public PDFCertificatesOptions getPdfCertificateOption() {
+		PDFCertificatesOptions option;
+		String opt = (String)configuration.get(PDF_CERTIFICATE_ENABLED);
+		if(StringHelper.containsNonWhitespace(opt)) {
+			try {
+				option = PDFCertificatesOptions.valueOf(opt);
+			} catch (Exception e) {
+				log.error("", e);
+				option = PDFCertificatesOptions.none;
+			}
+		} else {
+			option = PDFCertificatesOptions.none;
+		}
+
+		return option;
+	}
+	
+	public void setPdfCertificateOption(PDFCertificatesOptions option) {
+		configuration.put(PDF_CERTIFICATE_ENABLED, option.name());
+	}
+
+	/**
+	 * @return true if the efficency statement is enabled
+	 */
+	public boolean isRecertificationEnabled() {
+		Boolean bool = (Boolean) configuration.get(RECERTIFICATION_ENABLED);
+		return bool == null ? false : bool.booleanValue();
+	}
+	
+	/**
+	 * @param b
+	 */
+	public void setRecertificationEnabled(boolean b) {
+		configuration.put(RECERTIFICATION_ENABLED, new Boolean(b));
+	}
+	
+	public int getRecertificationTimelapse() {
+		Integer timelapse = (Integer)configuration.get(RECERTIFICATION_TIMELAPSE);
+		return timelapse == null ? 0 : timelapse.intValue();
+	}
+	
+	public void setRecertificationTimelapse(int timelapse) {
+		configuration.put(RECERTIFICATION_TIMELAPSE, new Integer(timelapse));
+	}
+	
+	public RecertificationTimeUnit getRecertificationTimelapseUnit() {
+		String timelapseUnit = (String)configuration.get(RECERTIFICATION_TIMELAPSE_UNIT);
+		RecertificationTimeUnit timeUnit = null;
+		if(StringHelper.containsNonWhitespace(timelapseUnit)) {
+			timeUnit = RecertificationTimeUnit.valueOf(timelapseUnit);
+		}
+		return timeUnit;
+	}
+	
+	public void setRecertificationTimelapseUnit(RecertificationTimeUnit timeUnit) {
+		if(timeUnit == null) {
+			configuration.remove(RECERTIFICATION_TIMELAPSE_UNIT);
+		} else {
+			configuration.put(RECERTIFICATION_TIMELAPSE_UNIT, timeUnit.name());
+		}
+	}
 
 	/**
 	 * @return true if calendar is enabled
@@ -382,6 +486,7 @@ public class CourseConfig implements Serializable, Cloneable {
 	 * 
 	 * @see java.lang.Object#clone()
 	 */
+	@Override
 	public CourseConfig clone() {
 		CourseConfig clone = new CourseConfig();
 		clone.setCalendarEnabled(((Boolean) configuration.get(KEY_CALENDAR_ENABLED)).booleanValue());
@@ -390,6 +495,11 @@ public class CourseConfig implements Serializable, Cloneable {
 		clone.setEfficencyStatementIsEnabled(isEfficencyStatementEnabled());
 		clone.setGlossarySoftKey(getGlossarySoftKey());
 		clone.setSharedFolderSoftkey(getSharedFolderSoftkey());
+		clone.setPdfCertificateOption(getPdfCertificateOption());
+		clone.setPdfCertificateTemplate(getPdfCertificateTemplate());
+		clone.setRecertificationEnabled(isRecertificationEnabled());
+		clone.setRecertificationTimelapse(getRecertificationTimelapse());
+		clone.setRecertificationTimelapseUnit(getRecertificationTimelapseUnit());
 		return clone;
 	}
 
@@ -397,6 +507,7 @@ public class CourseConfig implements Serializable, Cloneable {
 	 * 
 	 * @see java.lang.Object#equals(java.lang.Object)
 	 */
+	@Override
 	public boolean equals(Object obj) {
 		try {
 			CourseConfig aCourseConfig = (CourseConfig) obj;
diff --git a/src/main/java/org/olat/course/config/ui/CourseOptionsController.java b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java
index 773fa8858566d3840f5455fb99d6f344ae940370..d3e3a54f682c9ec19ee62cec8a59d404b36c6b14 100644
--- a/src/main/java/org/olat/course/config/ui/CourseOptionsController.java
+++ b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java
@@ -38,9 +38,6 @@ import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
-import org.olat.core.gui.control.generic.modal.DialogBoxController;
-import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
-import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
@@ -50,11 +47,9 @@ import org.olat.core.logging.activity.StringResourceableType;
 import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.coordinate.CoordinatorManager;
-import org.olat.core.util.event.EventBus;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
-import org.olat.course.assessment.EfficiencyStatementManager;
 import org.olat.course.config.CourseConfig;
 import org.olat.course.config.CourseConfigEvent;
 import org.olat.course.config.CourseConfigEvent.CourseConfigType;
@@ -82,7 +77,7 @@ public class CourseOptionsController extends FormBasicController {
 	private static final String COMMAND_REMOVE = "command.glossary.remove";
 	private static final String COMMAND_ADD = "command.glossary.add";
 
-	private SelectionElement efficencyEl, calendarEl, chatEl;
+	private SelectionElement calendarEl, chatEl;
 	private FormLink addGlossaryCommand, removeGlossaryCommand;
 	private StaticTextElement glossaryNameEl;
 	private FormLink saveButton;
@@ -98,7 +93,6 @@ public class CourseOptionsController extends FormBasicController {
 
 	private CloseableModalController cmc;
 	private ReferencableEntriesSearchController glossarySearchCtr, folderSearchCtr;
-	private DialogBoxController enableEfficiencyDC, disableEfficiencyDC;
 
 	@Autowired
 	private CalendarModule calendarModule;
@@ -161,19 +155,7 @@ public class CourseOptionsController extends FormBasicController {
 	
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		//efficiency statement
-		FormLayoutContainer effCont = FormLayoutContainer.createDefaultFormLayout("eff", getTranslator());
-		effCont.setRootForm(mainForm);
-		formLayout.add(effCont);
-		effCont.setFormContextHelp("org.olat.course.config.ui","course-efficiency.html","help.hover.course-eff");
 		
-		boolean effEnabled = courseConfig.isEfficencyStatementEnabled();
-		boolean managedEff = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.efficencystatement);
-		efficencyEl = uifactory.addCheckboxesHorizontal("effIsOn", "chkbx.efficency.onoff", effCont, new String[] {"xx"}, new String[] {""});
-		efficencyEl.addActionListener(FormEvent.ONCHANGE);
-		efficencyEl.select("xx", effEnabled);
-		efficencyEl.setEnabled(editable && !managedEff);
-
 		if(calendarModule.isEnabled() && calendarModule.isEnableCourseToolCalendar()) {
 			//calendar
 			FormLayoutContainer calCont = FormLayoutContainer.createDefaultFormLayout("cal", getTranslator());
@@ -252,17 +234,7 @@ public class CourseOptionsController extends FormBasicController {
 
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
-		if (source == disableEfficiencyDC) {
-			if (DialogBoxUIFactory.isOkEvent(event)) {
-				doChangeConfig(ureq);
-				saveButton.setCustomEnabledLinkCSS("btn btn-primary");
-			}
-		} else if (source == enableEfficiencyDC) {
-			if (DialogBoxUIFactory.isOkEvent(event)) {				
-				doChangeConfig(ureq);
-				saveButton.setCustomEnabledLinkCSS("btn btn-primary");
-			}
-		} else if (source == glossarySearchCtr) {
+		if (source == glossarySearchCtr) {
 			cmc.deactivate();
 			if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) {
 				RepositoryEntry repoEntry = glossarySearchCtr.getSelectedEntry();
@@ -332,19 +304,8 @@ public class CourseOptionsController extends FormBasicController {
 	}
 	
 	private void doSave(UserRequest ureq) {
-		boolean confirmUpdateStatement = courseConfig.isEfficencyStatementEnabled() != efficencyEl.isSelected(0);
-		if(confirmUpdateStatement) {
-			if (courseConfig.isEfficencyStatementEnabled()) {
-				// a change from enabled Efficiency to disabled
-				disableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.todisabled"), disableEfficiencyDC);
-			} else {
-				// a change from disabled Efficiency
-				enableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.toenable"), enableEfficiencyDC);
-			}
-		} else {
-			doChangeConfig(ureq);
-			saveButton.setCustomEnabledLinkCSS("btn btn-primary");
-		}
+		doChangeConfig(ureq);
+		saveButton.setCustomEnabledLinkCSS("btn btn-primary");
 	}
 	
 	private void doChangeConfig(UserRequest ureq) {
@@ -352,9 +313,7 @@ public class CourseOptionsController extends FormBasicController {
 		ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId());
 		courseConfig = course.getCourseEnvironment().getCourseConfig();
 		
-		boolean enableEfficiencyStatment = efficencyEl.isSelected(0);
-		boolean updateStatement = courseConfig.isEfficencyStatementEnabled() != enableEfficiencyStatment;
-		courseConfig.setEfficencyStatementIsEnabled(enableEfficiencyStatment);
+
 		
 		boolean enableChat = chatEl.isSelected(0);
 		boolean updateChat = courseConfig.isChatEnabled() != enableChat;
@@ -457,28 +416,6 @@ public class CourseOptionsController extends FormBasicController {
 			}
 		}
 		
-		if(updateStatement) {
-			if(enableEfficiencyStatment) {
-	            // first create the efficiencies, send event to agency (all courses add link)
-				List<Identity> identitiesWithData = course.getCourseEnvironment().getCoursePropertyManager().getAllIdentitiesWithCourseAssessmentData(null);
-				EfficiencyStatementManager.getInstance().updateEfficiencyStatements(course, identitiesWithData);							
-			} else {
-	            // delete really the efficiencies of the users.
-				RepositoryEntry courseRepoEntry = RepositoryManager.getInstance().lookupRepositoryEntry(course, true);
-				EfficiencyStatementManager.getInstance().deleteEfficiencyStatementsFromCourse(courseRepoEntry.getKey());						
-			}
-			
-			//inform everybody else		
-			EventBus eventBus = CoordinatorManager.getInstance().getCoordinator().getEventBus();
-			CourseConfigEvent courseConfigEvent = new CourseConfigEvent(CourseConfigType.efficiencyStatement, course.getResourceableId());
-			eventBus.fireEventToListenersOf(courseConfigEvent, course);
-			
-			ILoggingAction loggingAction = enableEfficiencyStatment ?
-					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_ENABLED :
-					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_DISABLED;
-			ThreadLocalUserActivityLogger.log(loggingAction, getClass());
-		}
-		
 		fireEvent(ureq, Event.CHANGED_EVENT);
 	}
 
diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java
index b81d0b66bf9e20ebdb2ab14e44a294806de3ed05..7258a4c24661645b48d7f59ab12f49a69db6c928 100644
--- a/src/main/java/org/olat/course/run/CourseRuntimeController.java
+++ b/src/main/java/org/olat/course/run/CourseRuntimeController.java
@@ -82,10 +82,11 @@ import org.olat.course.area.CourseAreasController;
 import org.olat.course.assessment.AssessmentChangedEvent;
 import org.olat.course.assessment.AssessmentMainController;
 import org.olat.course.assessment.CoachingGroupAccessAssessmentCallback;
-import org.olat.course.assessment.EfficiencyStatementController;
 import org.olat.course.assessment.EfficiencyStatementManager;
 import org.olat.course.assessment.FullAccessAssessmentCallback;
 import org.olat.course.assessment.UserEfficiencyStatement;
+import org.olat.course.certificate.ui.CertificatesOptionsController;
+import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementController;
 import org.olat.course.config.CourseConfig;
 import org.olat.course.config.CourseConfigEvent;
 import org.olat.course.config.ui.CourseOptionsController;
@@ -120,6 +121,7 @@ import org.olat.repository.RepositoryService;
 import org.olat.repository.controllers.EntryChangedEvent;
 import org.olat.repository.model.RepositoryEntrySecurity;
 import org.olat.repository.ui.RepositoryEntryRuntimeController;
+import org.olat.resource.OLATResource;
 import org.olat.util.logging.activity.LoggingResourceable;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -140,7 +142,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		courseStatisticLink, surveyStatisticLink, testStatisticLink,
 		areaLink, dbLink,
 		//settings
-		layoutLink, optionsLink,
+		layoutLink, optionsLink, certificatesOptionsLink,
 		//my course
 		efficiencyStatementsLink, calendarLink, noteLink, chatLink,
 		//glossary
@@ -158,6 +160,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 	private MembersManagementMainController membersCtrl;
 	private StatisticCourseNodesController statsToolCtr;
 	private CourseLayoutGeneratorController courseLayoutCtrl;
+	private CertificatesOptionsController certificatesOptionsCtrl;
 
 	private int currentUserCount;
 	private Map<String, Boolean> courseRightsCache;
@@ -448,6 +451,10 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 			optionsLink = LinkFactory.createToolLink("access.cmd", translate("command.options"), this, "o_icon_options");
 			optionsLink.setElementCssClass("o_sel_course_options");
 			settings.addComponent(optionsLink);
+			
+			certificatesOptionsLink = LinkFactory.createToolLink("certificates.cmd", translate("command.options.certificates"), this, "o_icon_certificate");
+			certificatesOptionsLink.setElementCssClass("o_sel_course_options_certificates");
+			settings.addComponent(certificatesOptionsLink);
 		}
 	}
 	
@@ -622,6 +629,8 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 			doLayout(ureq);
 		} else if(optionsLink == source) {
 			doOptions(ureq);
+		} else if(certificatesOptionsLink == source) {
+			doCertificatesOptions(ureq);
 		} else if(archiverLink == source) {
 			doArchive(ureq);
 		} else if(folderLink == source) {
@@ -882,6 +891,18 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		}
 	}
 	
+	private void doCertificatesOptions(UserRequest ureq) {
+		if (reSecurity.isEntryAdmin() || hasCourseRight(CourseRights.RIGHT_COURSEEDITOR)) {
+			removeCustomCSS(ureq);
+			ICourse course = CourseFactory.loadCourse(getOlatResourceable());
+			CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig().clone();
+			CertificatesOptionsController ctrl = new CertificatesOptionsController(ureq, getWindowControl(), getRepositoryEntry(), courseConfig, true);
+			certificatesOptionsCtrl = pushController(ureq, translate("command.options"), ctrl);
+			setActiveTool(certificatesOptionsLink);
+			currentToolCtr = certificatesOptionsCtrl;
+		}
+	}
+	
 	private void doArchive(UserRequest ureq) {
 		if (reSecurity.isEntryAdmin() || hasCourseRight(CourseRights.RIGHT_ARCHIVING)) {
 			removeCustomCSS(ureq);
@@ -1060,8 +1081,9 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		// will not be disposed on course run dispose, popus up as new browserwindow
 		ControllerCreator ctrlCreator = new ControllerCreator() {
 			public Controller createController(UserRequest lureq, WindowControl lwControl) {
-				ICourse course = CourseFactory.loadCourse(getRepositoryEntry().getOlatResource());
-				EfficiencyStatementController efficiencyStatementController = new EfficiencyStatementController(lwControl, lureq, getRepositoryEntry().getKey());
+				OLATResource resource = getRepositoryEntry().getOlatResource();
+				ICourse course = CourseFactory.loadCourse(resource);
+				CertificateAndEfficiencyStatementController efficiencyStatementController = new CertificateAndEfficiencyStatementController(lwControl, lureq, resource.getKey());
 				LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(lureq, getWindowControl(), efficiencyStatementController);
 				layoutCtr.setCustomCSS(CourseFactory.getCustomCourseCss(lureq.getUserSession(), course.getCourseEnvironment()));
 				return layoutCtr;
diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
index 2e5259c2165e76e81c6e04078c330e82e5fb3ef0..599a7edc01e394b146ca14c0755bec8dea2e1851 100644
--- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
@@ -30,6 +30,7 @@ command.openstatistic=Kurs Statistiken
 command.openteststatistic=Test Statistiken
 command.opensurveystatistic=Fragebogen Statistiken
 command.options=Optionen
+command.options.certificates=Leistungnachweis
 command.personalnote=Notizen
 command.previous=Zur\u00FCck zur letzten Seite
 command.run=Kurs Laufzeitumgebung
diff --git a/src/main/java/org/olat/home/_spring/homeContext.xml b/src/main/java/org/olat/home/_spring/homeContext.xml
index 70bf9b2ea9401ad08b716b45e0de1334aa0d99a7..8f9cc076f612a1af2cb6b5a5de8121ff965d594f 100644
--- a/src/main/java/org/olat/home/_spring/homeContext.xml
+++ b/src/main/java/org/olat/home/_spring/homeContext.xml
@@ -115,14 +115,15 @@
 	</bean>
 	
 	<!--  the users assessments ("Leistungsnachweise") -->
-	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
+	<bean class="org.olat.admin.user.tools.UserToolExtension" init-method="initExtensionPoints">
 		<property name="order" value="107" />
+		<property name="uniqueExtensionID" value="org.olat.home.HomeMainController:org.olat.course.assessment.EfficiencyStatementsListController:Evidences of achievement:107:effstatements" />
 		<property name="enabled" value="${minimalhome.ext.efficiencystatement}"></property>
 		<property name="navigationKey" value="effstatements" />
 		<property name="iconCssClass" value="o_icon o_icon-fw o_icon_certificate" />
 		<property name="actionController">	
 			<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
-				<property name="className" value="org.olat.course.assessment.EfficiencyStatementsListController"/>
+				<property name="className" value="org.olat.course.certificate.ui.CertificateAndEfficiencyStatementListController"/>
 			</bean>
 		</property>
 		<property name="securityCallbackClassName" value="org.olat.core.extensions.security.UserOnlyExtensionSecurityCallback" />
diff --git a/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementDetailsController.java b/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementDetailsController.java
index 5bdc9f550da166441fcaab31f113328e87dc9b5e..395f0ecee267144e64cc731bc27ad3fc7f0303ef 100644
--- a/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementDetailsController.java
+++ b/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementDetailsController.java
@@ -43,10 +43,10 @@ import org.olat.core.id.context.StateEntry;
 import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.EfficiencyStatement;
-import org.olat.course.assessment.EfficiencyStatementController;
 import org.olat.course.assessment.EfficiencyStatementManager;
 import org.olat.course.assessment.IdentityAssessmentEditController;
 import org.olat.course.assessment.UserEfficiencyStatement;
+import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementController;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.coach.model.EfficiencyStatementEntry;
 import org.olat.repository.RepositoryEntry;
@@ -68,7 +68,7 @@ public class EfficiencyStatementDetailsController extends BasicController implem
 	
 	private boolean hasChanged;
 	private EfficiencyStatementEntry statementEntry;
-	private EfficiencyStatementController statementCtrl;
+	private CertificateAndEfficiencyStatementController statementCtrl;
 	private IdentityAssessmentEditController assessmentCtrl;
 	
 	private final Identity assessedIdentity;
@@ -159,14 +159,14 @@ public class EfficiencyStatementDetailsController extends BasicController implem
 		//
 	}
 	
-	private EfficiencyStatementController createEfficiencyStatementController(UserRequest ureq) {
+	private CertificateAndEfficiencyStatementController createEfficiencyStatementController(UserRequest ureq) {
 		RepositoryEntry entry = statementEntry.getCourse();
 		UserEfficiencyStatement statement = statementEntry.getUserEfficencyStatement();
 		EfficiencyStatement efficiencyStatement = null;
 		if(statement != null) {
-			efficiencyStatement = EfficiencyStatementManager.getInstance().getUserEfficiencyStatement(statement.getCourseRepoKey(), assessedIdentity);
+			efficiencyStatement = EfficiencyStatementManager.getInstance().getUserEfficiencyStatementByCourseRepoKey(statement.getCourseRepoKey(), assessedIdentity);
 		}
-		return new EfficiencyStatementController(getWindowControl(), ureq, assessedIdentity, group, entry, efficiencyStatement, true);
+		return new CertificateAndEfficiencyStatementController(getWindowControl(), ureq, assessedIdentity, null, entry.getOlatResource().getKey(), entry, efficiencyStatement, true);
 	}
 	
 	private void efficiencyStatementChanged() {
diff --git a/src/main/java/org/olat/repository/RepositoryService.java b/src/main/java/org/olat/repository/RepositoryService.java
index e67861fcdbd238b47ade29044b0aec2286c38235..169cdf4c4124c6332eeb5421c8cb3ea41c48cb25 100644
--- a/src/main/java/org/olat/repository/RepositoryService.java
+++ b/src/main/java/org/olat/repository/RepositoryService.java
@@ -54,6 +54,8 @@ public interface RepositoryService {
 	
 	public RepositoryEntry loadByKey(Long key);
 	
+	public RepositoryEntry loadByResourceKey(Long key);
+	
 	public VFSLeaf getIntroductionImage(RepositoryEntry re);
 
 	public VFSLeaf getIntroductionMovie(RepositoryEntry re);
diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryDAO.java b/src/main/java/org/olat/repository/manager/RepositoryEntryDAO.java
index dddae18a37c7149331912a56f882f0c4e6af7de8..aae66366f94a1d16662dcddec42f78d6186644fe 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryEntryDAO.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryEntryDAO.java
@@ -60,6 +60,25 @@ public class RepositoryEntryDAO {
 		
 	}
 	
+	public RepositoryEntry loadByResourceKey(Long resourceKey) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select v from ").append(RepositoryEntry.class.getName()).append(" as v ")
+		  .append(" inner join fetch v.olatResource as ores")
+		  .append(" inner join fetch v.statistics as statistics")
+		  .append(" left join fetch v.lifecycle as lifecycle")
+		  .append(" where ores.key = :resourceKey");
+		
+		List<RepositoryEntry> entries = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), RepositoryEntry.class)
+				.setParameter("resourceKey", resourceKey)
+				.getResultList();
+		if(entries.isEmpty()) {
+			return null;
+		}
+		return entries.get(0);
+		
+	}
+	
 	public List<RepositoryEntry> searchByIdAndRefs(String idAndRefs) {
 		StringBuilder sb = new StringBuilder();
 		sb.append("select v from ").append(RepositoryEntry.class.getName()).append(" as v ")
diff --git a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
index 592602d8a8102e1d8a97104a7fedee09c77a1cae..c3e5a01e0bfbee050192eb45be830ef08ba9e284 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
@@ -218,7 +218,12 @@ public class RepositoryServiceImpl implements RepositoryService {
 	public RepositoryEntry loadByKey(Long key) {
 		return repositoryEntryDAO.loadByKey(key);
 	}
-	
+
+	@Override
+	public RepositoryEntry loadByResourceKey(Long resourceKey) {
+		return repositoryEntryDAO.loadByResourceKey(resourceKey);
+	}
+
 	@Override
 	public VFSLeaf getIntroductionImage(RepositoryEntry re) {
 		VFSContainer repositoryHome = new LocalFolderImpl(new File(FolderConfig.getCanonicalRepositoryHome()));
diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
index 218fc1412e51d5d50682f83e78f26e8ddc94d60e..8d780170555a4991b8d3deab1f443bfbd1fab53e 100644
--- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
+++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
@@ -68,6 +68,10 @@
 					<constructor-arg index="0" value="OLAT_10.0.0" />
 					<property name="alterDbStatements" value="alter_9_4_0_to_10_0_0.sql" />
 				</bean>
+				<bean id="database_upgrade_10_1_0" class="org.olat.upgrade.DatabaseUpgrade">
+					<constructor-arg index="0" value="OLAT_10.1.0" />
+					<property name="alterDbStatements" value="alter_10_0_0_to_10_1_0.sql" />
+				</bean>
 			</list>
 		</property>
 	</bean>
diff --git a/src/main/resources/database/mysql/alter_10_0_0_to_10_1_0.sql b/src/main/resources/database/mysql/alter_10_0_0_to_10_1_0.sql
index 86f0942f793d7cdb2f2db4828bd4dffd9a647049..637bca792d972384a509e26ef3ce47973d68e1a5 100644
--- a/src/main/resources/database/mysql/alter_10_0_0_to_10_1_0.sql
+++ b/src/main/resources/database/mysql/alter_10_0_0_to_10_1_0.sql
@@ -1,3 +1,35 @@
+create table o_cer_template (
+   id bigint not null,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_public boolean not null,
+   primary key (id)
+);
+alter table o_cer_template ENGINE = InnoDB;
+
+create table o_cer_certificate (
+   id bigint not null,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   c_uuid varchar(36) not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_last boolean not null default 1,
+   c_archived_resource_id bigint not null,
+   fk_olatresource bigint,
+   fk_identity bigint not null,
+   primary key (id)
+);
+alter table o_cer_certificate ENGINE = InnoDB;
+
+alter table o_cer_certificate add constraint cer_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+alter table o_cer_certificate add constraint cer_to_resource_idx foreign key (fk_olatresource) references o_olatresource (resource_id);
+create index cer_archived_resource_idx on o_cer_certificate (c_archived_resource_id);
+create index cer_uuid_idx on o_cer_certificate (c_uuid);
+
+
 drop view o_qp_item_shared_v;
 drop view o_qp_item_pool_v;
 drop view o_qp_item_author_v;
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 5a7f4736b5b3d8a8189b2fdc8978075039004f02..69a7702c136c19e0619d2dfd9b96388915b9312b 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1059,6 +1059,30 @@ create table o_as_user_course_infos (
    primary key (id)
 );
 
+create table o_cer_template (
+   id bigint not null,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_public boolean not null,
+   primary key (id)
+);
+
+create table o_cer_certificate (
+   id bigint not null,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   c_uuid varchar(36) not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_last boolean not null default 1,
+   c_archived_resource_id bigint not null,
+   fk_olatresource bigint,
+   fk_identity bigint not null,
+   primary key (id)
+);
+
 -- instant messaging
 create table if not exists o_im_message (
    id bigint not null,
@@ -1702,7 +1726,8 @@ alter table o_ex_task_modifier ENGINE = InnoDB;
 alter table o_checklist ENGINE = InnoDB;
 alter table o_cl_checkbox ENGINE = InnoDB;
 alter table o_cl_check ENGINE = InnoDB;
-
+alter table o_cer_template ENGINE = InnoDB;
+alter table o_cer_certificate ENGINE = InnoDB;
 
 -- rating
 alter table o_userrating add constraint FKF26C8375236F20X foreign key (creator_id) references o_bs_identity (id);
@@ -2016,6 +2041,12 @@ alter table o_qp_item_type add unique (q_type(200));
 alter table o_lti_outcome add constraint idx_lti_outcome_ident_id foreign key (fk_identity_id) references o_bs_identity(id);
 alter table o_lti_outcome add constraint idx_lti_outcome_rsrc_id foreign key (fk_resource_id) references o_olatresource(resource_id);
 
+-- certificate
+alter table o_cer_certificate add constraint cer_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+alter table o_cer_certificate add constraint cer_to_resource_idx foreign key (fk_olatresource) references o_olatresource (resource_id);
+
+create index cer_archived_resource_idx on o_cer_certificate (c_archived_resource_id);
+create index cer_uuid_idx on o_cer_certificate (c_uuid);
 
 
 insert into hibernate_unique_key values ( 0 );
diff --git a/src/main/resources/database/postgresql/alter_10_0_0_to_10_1_0.sql b/src/main/resources/database/postgresql/alter_10_0_0_to_10_1_0.sql
index 86f0942f793d7cdb2f2db4828bd4dffd9a647049..3fb3ab11fd1a2a81e4c4f93cd9b16d7fc26e69fa 100644
--- a/src/main/resources/database/postgresql/alter_10_0_0_to_10_1_0.sql
+++ b/src/main/resources/database/postgresql/alter_10_0_0_to_10_1_0.sql
@@ -1,3 +1,35 @@
+create table o_cer_template (
+   id int8 not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_public bool not null,
+   primary key (id)
+);
+
+create table o_cer_certificate (
+   id int8 not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   c_uuid varchar(36) not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_last bool not null default true,
+   c_archived_resource_id int8 not null,
+   fk_olatresource int8,
+   fk_identity int8 not null,
+   primary key (id)
+);
+
+alter table o_cer_certificate add constraint cer_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+create index cer_identity_idx on o_cer_certificate (fk_identity);
+alter table o_cer_certificate add constraint cer_to_resource_idx foreign key (fk_olatresource) references o_olatresource (resource_id);
+create index cer_resource_idx on o_cer_certificate (fk_olatresource);
+create index cer_archived_resource_idx on o_cer_certificate (c_archived_resource_id);
+create index cer_uuid_idx on o_cer_certificate (c_uuid);
+
+
 drop view o_qp_item_shared_v;
 drop view o_qp_item_pool_v;
 drop view o_qp_item_author_v;
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index bc6fc216e351cdbae12e97a2a1590d4e006ba7fd..5479d5704d144b2ffd64c20e0b11383c4acb81e0 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1060,6 +1060,30 @@ create table o_as_user_course_infos (
    primary key (id)
 );
 
+create table o_cer_template (
+   id int8 not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_public bool not null,
+   primary key (id)
+);
+
+create table o_cer_certificate (
+   id int8 not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   c_uuid varchar(36) not null,
+   c_name varchar(256) not null,
+   c_path varchar(1024) not null,
+   c_last bool not null default true,
+   c_archived_resource_id int8 not null,
+   fk_olatresource int8,
+   fk_identity int8 not null,
+   primary key (id)
+);
+
 -- instant messaging
 create table o_im_message (
    id int8 not null,
@@ -2031,6 +2055,12 @@ create index idx_lti_outcome_ident_id_idx on o_lti_outcome (fk_identity_id);
 alter table o_lti_outcome add constraint idx_lti_outcome_rsrc_id foreign key (fk_resource_id) references o_olatresource(resource_id);
 create index idx_lti_outcome_rsrc_id_idx on o_lti_outcome (fk_resource_id);
 
-
+-- certificates
+alter table o_cer_certificate add constraint cer_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+create index cer_identity_idx on o_cer_certificate (fk_identity);
+alter table o_cer_certificate add constraint cer_to_resource_idx foreign key (fk_olatresource) references o_olatresource (resource_id);
+create index cer_resource_idx on o_cer_certificate (fk_olatresource);
+create index cer_archived_resource_idx on o_cer_certificate (c_archived_resource_id);
+create index cer_uuid_idx on o_cer_certificate (c_uuid);
 
 insert into hibernate_unique_key values ( 0 );
diff --git a/src/main/resources/org/olat/course/certificate/template.pdf b/src/main/resources/org/olat/course/certificate/template.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3fa314b644641056e74f98959b499675767af645
Binary files /dev/null and b/src/main/resources/org/olat/course/certificate/template.pdf differ
diff --git a/src/main/webapp/static/themes/light/modules/_form.scss b/src/main/webapp/static/themes/light/modules/_form.scss
index 5af48d2a42cc9a39f2125c0a78a9ccf17c73ddd5..81e6ade2b8420cfd1cd7ccda06531dd46f2c2965 100644
--- a/src/main/webapp/static/themes/light/modules/_form.scss
+++ b/src/main/webapp/static/themes/light/modules/_form.scss
@@ -44,6 +44,10 @@
 		margin-left: 25px;
 	}
 	
+	.o_form_element.form-inline .o_form_element.form-group {
+		margin-left: 25px;
+	}
+	
 	.input-group.o_date_picker {
 		width: 16em;
 	}
diff --git a/src/main/webapp/static/themes/light/theme.css b/src/main/webapp/static/themes/light/theme.css
index bd425450a3ca552d88534b862c3019491af22366..6878f5ec6c804e2fff2a6b0fa15853d82dff5d9e 100644
--- a/src/main/webapp/static/themes/light/theme.css
+++ b/src/main/webapp/static/themes/light/theme.css
@@ -63,7 +63,7 @@ fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%
 #o_footer_powered{position:absolute;top:30px;right:15px;z-index:1}#o_footer_powered img{opacity:0.6;filter:alpha(opacity=60)}#o_footer_powered img:hover{opacity:1;filter:alpha(opacity=100)}#o_footer_impressum{position:absolute;top:10px;width:100%;text-align:center;z-index:-1}#o_footer_impressum i{display:none}#o_footer_textline{position:absolute;top:30px;width:100%;text-align:center;z-index:-1}#o_share{margin-top:10px}#o_share a{opacity:0.6;filter:alpha(opacity=60)}#o_share a:hover{opacity:1;filter:alpha(opacity=100)}#o_share a,#o_share_social_container a{color:#999;margin:0 0.25em 0 0}#o_share a:hover,#o_share_social_container a:hover{color:#000}@media (max-width: 767px){#o_counter,#o_footer_version,#o_share{display:none}#o_footer_impressum{top:30px;text-align:left}#o_footer_textline{top:50px;text-align:left}#o_footer_powered{top:10px}#o_footer_powered a:after{content:"\221E";font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:bold;font-size:14px}#o_footer_powered img{display:none}}#o_navbar_wrapper{z-index:4;border-top:1px solid #e7e7e7;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1)}#o_navbar_wrapper #o_navbar_container{position:relative}.o_navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid #e7e7e7;background-color:#f8f8f8}.o_navbar:before,.o_navbar:after{content:" ";display:table}.o_navbar:after{clear:both}.o_navbar .o_navbar_tabs li a{padding-right:30px}.o_navbar .o_navbar_tabs .o_navbar_tab_close{position:absolute;top:15px;right:0.5em;padding:0;width:1em;height:1em}.o_navbar .o_navbar_tabs .o_navbar_tab_close i:before{color:#d9534f}.o_navbar .o_navbar_tabs .o_navbar_tab_close:hover i:before{color:#c9302c}.o_navbar .o_custom_navbar-brand{background-position:5px 0;background-repeat:no-repeat;height:50px;width:120px}.o_navbar #o_navbar_langchooser{color:#777;padding:7px 15px}.o_navbar #o_navbar_langchooser form span+div{display:inline}.o_navbar #o_navbar_tools_permanent #o_navbar_print a,.o_navbar #o_navbar_tools_permanent #o_navbar_impress a,.o_navbar #o_navbar_tools_permanent #o_navbar_help a{color:#777;padding-right:0}.o_navbar #o_navbar_tools_permanent #o_navbar_login a{color:#f0ad4e}.o_navbar #o_navbar_tools_personal .o_navbar_tool a{padding-right:5px}.o_navbar #o_navbar_tools_personal #o_navbar_my_menu .dropdown-toggle{padding-left:45px}.o_navbar #o_navbar_tools_personal #o_navbar_my_menu .o_portrait{position:absolute;left:7px;top:10px}.o_navbar #o_navbar_tools_personal .o_logout{color:#d9534f}.o_navbar.o_navbar-offcanvas .o_navbar_tab_close{top:10px;right:10px}.o_navbar.o_navbar-offcanvas .o_navbar_tool{display:none}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a{color:#777}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a:hover,.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a:focus{color:#fff;background-color:transparent}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a.o_logout{color:#d9534f}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a.o_logout:hover,.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a.o_logout:focus{color:#c9302c}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-header{padding-left:15px}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-toggle{display:none}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-menu{box-shadow:none;position:relative;top:0;left:0;display:block;float:none;background-color:#222;color:#777;font-size:14px}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-menu .divider{background:none}.o_navbar.o_navbar-offcanvas .o_navbar-nav a{color:#777;text-shadow:none}.o_navbar.o_navbar-offcanvas .o_navbar-nav a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav a:focus{background-color:transparent;color:#fff}.o_navbar.o_navbar-offcanvas .o_navbar-nav .active a,.o_navbar.o_navbar-offcanvas .o_navbar-nav .active a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .active a:focus{background-color:#090909;color:#fff}.o_navbar.o_navbar-offcanvas .o_navbar-nav .o_navbar-link{color:#777}.o_navbar.o_navbar-offcanvas .o_navbar-nav .o_navbar-link:hover{color:#fff}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>li>a{color:#777}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>li>a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.active>a,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.active>a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.disabled>a,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.disabled>a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}#o_navbar_print a span,#o_navbar_impress a span,#o_navbar_help a span,#o_navbar_search_opener a span{display:none}body.o_dmz #o_navbar_print a span,body.o_dmz #o_navbar_impress a span,body.o_dmz #o_navbar_help a span,body.o_dmz #o_navbar_search_opener a span{display:inline}.o_navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;-webkit-overflow-scrolling:touch}.o_navbar-collapse:before,.o_navbar-collapse:after{content:" ";display:table}.o_navbar-collapse:after{clear:both}.o_navbar-collapse.o_collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.o_navbar-offcanvas .o_navbar-collapse{width:auto;box-shadow:none;margin-top:10px;margin-right:-15px;margin-left:-15px}.o_navbar-brand{float:left;font-size:18px;line-height:20px;height:50px;color:#777}.o_navbar-brand:hover,.o_navbar-brand:focus{text-decoration:none;color:#5e5e5e;background-color:transparent}.o_navbar-toggle{position:relative;margin-right:15px;margin-left:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;border:1px solid #ddd;border-radius:4px;background-color:transparent;background-image:none}.o_navbar-toggle:hover,.o_navbar-toggle:focus{outline:none;background-color:#ddd}.o_navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px;background-color:#888}.o_navbar-toggle .icon-bar+.icon-bar{margin-top:4px}#o_navbar_left-toggle{float:left}#o_navbar_right-toggle{float:right}.o_navbar-link{color:#777}.o_navbar-link:hover{color:#333}.o_navbar-nav{margin:7.5px -15px}.o_navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px;color:#777}.o_navbar-nav>li>a:hover,.o_navbar-nav>li>a:focus{color:#333;background-color:transparent}.o_navbar-nav>.active>a,.o_navbar-nav>.active>a:hover,.o_navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.o_navbar-nav>.disabled>a,.o_navbar-nav>.disabled>a:hover,.o_navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.o_navbar-nav>.open>a,.o_navbar-nav>.open>a:hover,.o_navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}.o_collapse .o_navbar-nav{float:left;margin:0}.o_collapse .o_navbar-nav>li{float:left}.o_collapse .o_navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.o_collapse .o_navbar-nav.o_navbar-right:last-child{margin-right:-15px}.o_collapse.o_navbar-collapse .o_navbar-left{float:left !important}.o_collapse.o_navbar-collapse .o_navbar-right{float:right !important}.o_navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid #e7e7e7;border-bottom:1px solid #e7e7e7;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@media (max-width: 767px){.o_navbar-form .form-group{margin-bottom:5px}}
 .o_collapse .o_navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.o_collapse .o_navbar-form.o_navbar-right:last-child{margin-right:-15px}.o_navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.o_navbar-fixed-bottom .o_navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.o_navbar-btn{margin-top:8px;margin-bottom:8px}.o_navbar-btn.btn-sm,.btn-group-sm>.o_navbar-btn.btn{margin-top:10px;margin-bottom:10px}.o_navbar-btn.btn-xs,.btn-group-xs>.o_navbar-btn.btn{margin-top:14px;margin-bottom:14px}.o_navbar-text{margin-top:15px;margin-bottom:15px;color:#777}.o_collapse .o_navbar-text{float:left;margin-left:15px;margin-right:15px}.o_collapse .o_navbar-text.o_navbar-right:last-child{margin-right:0}.o_toolbar{position:relative;margin-bottom:20px;margin-top:-10px;border:1px solid #e7e7e7}.o_toolbar:before,.o_toolbar:after{content:" ";display:table}.o_toolbar:after{clear:both}@media (min-width: 768px){.o_toolbar{border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px}}.o_toolbar .o_breadcrumb:before,.o_toolbar .o_breadcrumb:after{content:" ";display:table}.o_toolbar .o_breadcrumb:after{clear:both}.o_toolbar .o_breadcrumb .breadcrumb{margin-bottom:0;padding:5px 9px;font-size:11px;line-height:15px;border-radius:0;background:#f5f5f5;border-top-right-radius:4px;border-top-left-radius:4px}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close{float:right;position:relative;margin:0 0 0 15px;vertical-align:middle}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a{line-height:15px;color:#d9534f}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a:hover{color:#b52b27}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a i{font-size:16px}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a span{display:none}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close:before{content:none}.o_toolbar .o_tools_container{text-align:center;min-height:37px;position:realtive;background-color:#f8f8f8;border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-top:1px solid #e7e7e7}.o_toolbar .o_tools_container:before,.o_toolbar .o_tools_container:after{content:" ";display:table}.o_toolbar .o_tools_container:after{clear:both}@media (max-width: 991px){.o_toolbar .o_tools_container{min-height:35px}}@media (max-width: 767px){.o_toolbar .o_tools_container{min-height:22px;text-align:left}}.o_toolbar .o_tools_container a{color:#777;display:inline-block}.o_toolbar .o_tools_container a:hover{color:#333}.o_toolbar .o_tools_container a.o_disabled{color:#aaa !important}.o_toolbar .o_tools_container a.o_disabled:hover{color:#aaa !important}.o_toolbar .o_tools_container a.active{color:#428bca;background-color:transparent}.o_toolbar .o_tools_container .dropdown-menu a{display:block}.o_toolbar .o_tools_container .dropdown-menu a.active{color:#428bca;background-color:transparent}.o_toolbar .o_tools{margin-top:8px;margin-bottom:5px}.o_toolbar .o_tool,.o_toolbar .o_text{position:relative;margin:0 10px}.o_toolbar .o_tool:first-child,.o_toolbar .o_text:first-child{margin-left:0}.o_toolbar .o_tool:last-child,.o_toolbar .o_text:last-child{margin-right:0}.o_toolbar .o_tool a i,.o_toolbar .o_tool .o_disabled i,.o_toolbar .o_text a i,.o_toolbar .o_text .o_disabled i{font-size:18px}.o_toolbar .o_tool a span,.o_toolbar .o_tool .o_disabled span,.o_toolbar .o_text a span,.o_toolbar .o_text .o_disabled span{display:block;font-size:12px}.o_toolbar .o_tool a span.badge,.o_toolbar .o_tool .o_disabled span.badge,.o_toolbar .o_text a span.badge,.o_toolbar .o_text .o_disabled span.badge{position:absolute;right:50%;top:-18px;margin-right:-12px;font-size:13px}@media (min-width: 767px) and (max-width: 991px){.o_toolbar .o_tool a i,.o_toolbar .o_tool .o_disabled i,.o_toolbar .o_text a i,.o_toolbar .o_text .o_disabled i{font-size:16px}.o_toolbar .o_tool a span,.o_toolbar .o_tool .o_disabled span,.o_toolbar .o_text a span,.o_toolbar .o_text .o_disabled span{font-size:11px}.o_toolbar .o_tool a span.badge,.o_toolbar .o_tool .o_disabled span.badge,.o_toolbar .o_text a span.badge,.o_toolbar .o_text .o_disabled span.badge{top:-16.5px;margin-right:-11px;font-size:12px}}@media (max-width: 767px){.o_toolbar .o_tool a i,.o_toolbar .o_tool .o_disabled i,.o_toolbar .o_text a i,.o_toolbar .o_text .o_disabled i{font-size:20px}.o_toolbar .o_tool a span,.o_toolbar .o_tool .o_disabled span,.o_toolbar .o_text a span,.o_toolbar .o_text .o_disabled span{display:none}.o_toolbar .o_tool a span.badge,.o_toolbar .o_tool .o_disabled span.badge,.o_toolbar .o_text a span.badge,.o_toolbar .o_text .o_disabled span.badge{display:block;position:relative;top:0;left:0}}.o_toolbar .o_tool .o_chelp,.o_toolbar .o_text .o_chelp{position:relative;top:-1em;vertical-align:top}.o_toolbar .o_tool_next,.o_toolbar .o_tool_previous{padding:0;margin-top:5px;border:1px solid #ccc;background-color:#eee}.o_toolbar .o_tool_next a,.o_toolbar .o_tool_previous a{color:#777}.o_toolbar .o_tool_next a:hover,.o_toolbar .o_tool_previous a:hover{color:#333}.o_toolbar .o_tool_next a.o_disabled,.o_toolbar .o_tool_previous a.o_disabled{color:#aaa !important}.o_toolbar .o_tool_next a.o_disabled:hover,.o_toolbar .o_tool_previous a.o_disabled:hover{color:#aaa !important}.o_toolbar .o_tool_next i,.o_toolbar .o_tool_previous i{font-size:21px}@media (min-width: 767px) and (max-width: 991px){.o_toolbar .o_tool_next,.o_toolbar .o_tool_previous{margin-top:4px}.o_toolbar .o_tool_next i,.o_toolbar .o_tool_previous i{font-size:18px}}@media (max-width: 767px){.o_toolbar .o_tool_next,.o_toolbar .o_tool_previous{margin-top:0}.o_toolbar .o_tool_next i,.o_toolbar .o_tool_previous i{font-size:20px}}.o_toolbar .o_tool_previous{margin-left:10px;border-bottom-left-radius:4px;border-top-left-radius:4px;border-right:0}.o_toolbar .o_tool_next{border-bottom-right-radius:4px;border-top-right-radius:4px}.o_toolbar .o_tool_dropdown{position:relative;margin:0 10px}.o_toolbar .o_tool_dropdown:first-child{margin-left:0}.o_toolbar .o_tool_dropdown:last-child{margin-right:0}.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:18px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span{display:block;font-size:12px}.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{position:absolute;right:50%;top:4px;margin-right:-20px;font-size:14px}@media (min-width: 767px) and (max-width: 991px){.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:16px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span,.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{font-size:11px}.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{top:4px;margin-right:-18px;font-size:12px}}@media (max-width: 767px){.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:20px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span,.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{display:none}}.o_toolbar .o_tool_dropdown .dropdown-menu{text-align:left}.o_toolbar .o_tools_left{float:left}.o_toolbar .o_tools_right{float:right}.o_toolbar .o_tools_right_edge{float:right}@media (max-width: 991px){.o_toolbar .o_tools{margin-top:6px;margin-bottom:4px}.o_toolbar .o_tool span{max-width:10em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_toolbar .o_tool,.o_toolbar .o_text,.o_toolbar .o_tool_dropdown{margin:0 5px}}@media (max-width: 767px){.o_toolbar .o_tools{margin-top:6px;margin-bottom:4px}.o_toolbar .o_tools .o_chelp{top:0;vertical-align:top}.o_toolbar .o_tools_right,.o_toolbar .o_tools_right,.o_toolbar .o_tools_center{float:left}.o_toolbar .o_tool,.o_toolbar .o_text,.o_toolbar .o_tool_dropdown{margin:0 0}}
 body{overflow-x:hidden}.o_container_offcanvas{position:relative;max-width:1324px;-webkit-transition:all .25s ease-in-out;-moz-transition:all .25s ease-in-out;-o-transition:all .25s ease-in-out;-m-transition:all .25s ease-in-out;transition:all .25s ease-in-out}@media screen and (min-width: 1324px) and (max-width: 1574px){body.o_offcanvas_right_visible .o_container_offcanvas{left:-125px;max-width:1074px}}@media screen and (min-width: 1574px) and (max-width: 1824px){body.o_offcanvas_right_visible .o_container_offcanvas{left:-125px}}#o_offcanvas_right{position:absolute;top:0;right:-250px;width:250px;padding:15px 15px;background-color:#222;color:#777;border:1px solid #090909;-webkit-box-shadow:0px 0px 4px 3px rgba(0,0,0,0.25);box-shadow:0px 0px 4px 3px rgba(0,0,0,0.25);min-height:100%;z-index:10;display:none}#o_offcanvas_right:before,#o_offcanvas_right:after{content:" ";display:table}#o_offcanvas_right:after{clear:both}@media screen and (max-width: 767px){.row-offcanvas{position:relative;-webkit-transition:all .25s ease-out;-moz-transition:all .25s ease-out;transition:all .25s ease-out}.row-offcanvas-right{right:0}.row-offcanvas-right .sidebar-offcanvas{right:-50%}.row-offcanvas-right.active{right:50%}.row-offcanvas-left{left:0}.row-offcanvas-left .sidebar-offcanvas{left:-50%}.row-offcanvas-left.active{left:50%}.sidebar-offcanvas{position:absolute;top:0;width:50%}}.o_info,.o_form .o_info,.o_togglebox_wrapper div.o_togglebox_content,div.o_qti_item_itemfeedback{margin:20px 0;padding:20px;border-left:3px solid #777;background-color:#eee}.o_info h2,.o_info h3,.o_info h4,.o_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_info h2,.o_info h5,.o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback h2,.o_form .o_info h3,.o_togglebox_wrapper div.o_togglebox_content h3,div.o_qti_item_itemfeedback h3,.o_form .o_info h4,.o_togglebox_wrapper div.o_togglebox_content h4,div.o_qti_item_itemfeedback h4,.o_form .o_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback .o_cal .fc-header-title h2,.o_cal .fc-header-title div.o_qti_item_itemfeedback h2,.o_form .o_info h5,.o_togglebox_wrapper div.o_togglebox_content h5,div.o_qti_item_itemfeedback h5{color:#777}.o_note,.o_form .o_desc,.o_course_run .o_statusinfo,.o_course_stats .o_desc{margin:20px 0;padding:20px;border-left:3px solid #31708f;background-color:#d9edf7}.o_note h2,.o_note h3,.o_note h4,.o_note .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_note h2,.o_note h5,.o_form .o_desc h2,.o_course_run .o_statusinfo h2,.o_course_stats .o_desc h2,.o_form .o_desc h3,.o_course_run .o_statusinfo h3,.o_course_stats .o_desc h3,.o_form .o_desc h4,.o_course_run .o_statusinfo h4,.o_course_stats .o_desc h4,.o_form .o_desc .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_desc h2,.o_course_run .o_statusinfo .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_run .o_statusinfo h2,.o_course_stats .o_desc .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_stats .o_desc h2,.o_form .o_desc h5,.o_course_run .o_statusinfo h5,.o_course_stats .o_desc h5{color:#31708f}.o_important,.o_bc_empty,.o_course_run .o_no_scoreinfo{margin:20px 0;padding:20px;border-left:3px solid #f4d000;background-color:#fff1a4}.o_important h2,.o_important h3,.o_important h4,.o_important .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_important h2,.o_important h5,.o_bc_empty h2,.o_course_run .o_no_scoreinfo h2,.o_bc_empty h3,.o_course_run .o_no_scoreinfo h3,.o_bc_empty h4,.o_course_run .o_no_scoreinfo h4,.o_bc_empty .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_bc_empty h2,.o_course_run .o_no_scoreinfo .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_run .o_no_scoreinfo h2,.o_bc_empty h5,.o_course_run .o_no_scoreinfo h5{color:#f4d000}.o_success{margin:20px 0;padding:20px;border-left:3px solid #3c763d;background-color:#dff0d8}.o_success h2,.o_success h3,.o_success h4,.o_success .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_success h2,.o_success h5{color:#3c763d}.o_warning,.o_form .o_warning{margin:20px 0;padding:20px;border-left:3px solid #8a6d3b;background-color:#fcf8e3}.o_warning h2,.o_warning h3,.o_warning h4,.o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_warning h2,.o_warning h5,.o_form .o_warning h2,.o_form .o_warning h3,.o_form .o_warning h4,.o_form .o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_warning h2,.o_form .o_warning h5{color:#8a6d3b}.o_error{margin:20px 0;padding:20px;border-left:3px solid #a94442;background-color:#f2dede}.o_error h2,.o_error h3,.o_error h4,.o_error .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_error h2,.o_error h5{color:#a94442}div.o_callout_overlay{position:fixed;top:0;left:0;width:100%;height:100%;zoom:1;background:#000;opacity:0;filter:alpha(opacity=0)}.o_alert_info{position:fixed;top:-100%;left:0;display:none;z-index:2000;width:100%;text-align:center}.o_alert_info .alert{position:relative;width:auto;margin:0 auto;text-align:left;-webkit-box-shadow:0px 1px 5px -1px rgba(0,0,0,0.15);box-shadow:0px 1px 5px -1px rgba(0,0,0,0.15)}.o_alert_info .alert .o_icon_close{float:right;color:#777}.o_alert_info .alert .o_icon_close:hover{color:#555}@media (min-width: 768px){.o_alert_info .alert{width:600px}}
-#o_msg_sticky,#o_msg_sticky_preview{position:relative;color:#a94442;background-color:#f2dede;border:1px solid #ebccd1;padding:10px 16px 10px 60px;min-height:40px;margin:-20px 0 20px 0}#o_msg_sticky .o_icon_info_msg,#o_msg_sticky_preview .o_icon_info_msg{position:absolute;left:10px;top:5px;font-size:40px}@media (min-width: 768px){.modal .o_modal_fullwidth{width:90%}}@media (min-width: 992px){.modal .o_modal_fullwidth{width:80%}}.modal .modal-header h4,.modal .modal-header .o_cal .fc-header-title h2,.o_cal .fc-header-title .modal .modal-header h2{color:#428bca;font-weight:500;font-family:inherit;line-height:1.1}.o_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_tree a{color:#777;background-color:none}.o_tree a:hover,.o_tree a:focus{color:#333;background-color:#f8f8f8}.o_tree ul{margin:0;padding:0;list-style-type:none}.o_tree ul li{margin:0;padding:0;white-space:nowrap}.o_tree ul li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_tree ul li div a.o_tree_oc_l0{position:absolute;top:10px;left:-4px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l0,.o_tree ul .o_tree_level_close.b_tree_oc_l0{z-index:10}.o_tree ul li div a.o_tree_oc_l1{position:absolute;top:10px;left:11px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l1,.o_tree ul .o_tree_level_close.b_tree_oc_l1{z-index:10}.o_tree ul li div a.o_tree_oc_l2{position:absolute;top:10px;left:26px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l2,.o_tree ul .o_tree_level_close.b_tree_oc_l2{z-index:10}.o_tree ul li div a.o_tree_oc_l3{position:absolute;top:10px;left:41px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l3,.o_tree ul .o_tree_level_close.b_tree_oc_l3{z-index:10}.o_tree ul li div a.o_tree_oc_l4{position:absolute;top:10px;left:56px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l4,.o_tree ul .o_tree_level_close.b_tree_oc_l4{z-index:10}.o_tree ul li div a.o_tree_oc_l5{position:absolute;top:10px;left:71px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l5,.o_tree ul .o_tree_level_close.b_tree_oc_l5{z-index:10}.o_tree ul li div a.o_tree_oc_l6{position:absolute;top:10px;left:86px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l6,.o_tree ul .o_tree_level_close.b_tree_oc_l6{z-index:10}.o_tree ul li div a.o_tree_oc_l7{position:absolute;top:10px;left:101px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l7,.o_tree ul .o_tree_level_close.b_tree_oc_l7{z-index:10}.o_tree ul li div a.o_tree_oc_l8{position:absolute;top:10px;left:116px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l8,.o_tree ul .o_tree_level_close.b_tree_oc_l8{z-index:10}.o_tree ul li div a.o_tree_oc_l9{position:absolute;top:10px;left:131px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l9,.o_tree ul .o_tree_level_close.b_tree_oc_l9{z-index:10}.o_tree ul li div a.o_tree_oc_l10{position:absolute;top:10px;left:146px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l10,.o_tree ul .o_tree_level_close.b_tree_oc_l10{z-index:10}.o_tree ul li div a.o_tree_oc_l11{position:absolute;top:10px;left:161px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l11,.o_tree ul .o_tree_level_close.b_tree_oc_l11{z-index:10}.o_tree ul li div a.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_tree ul li div a.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_tree ul li div a.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_tree ul li div a.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_tree ul li div a.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_tree ul li div a.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_tree ul li div a.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_tree ul li div a.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_tree ul li div a.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_tree ul li div a.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_tree ul li div a.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_tree ul li div a.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_tree ul span.o_tree_leaf{display:none}.o_tree ul li .badge{position:absolute;font-size:70%}.o_tree ul li .badge:before{content:none}.o_tree ul li .badge.o_badge_1{top:3px;right:1px}.o_tree ul li .badge.o_badge_2{bottom:3px;right:1px}.o_tree ul li .badge.o_badge_3{top:3px;right:25px}.o_tree ul li .badge.o_badge_4{bottom:3px;right:25px}.o_tree ul li div.o_dnd_sibling{margin:0;padding:0;border-bottom:none}.o_tree ul li a.active{color:#428bca;background-color:none;font-weight:bold}.o_tree ul li a.active:hover,.o_tree ul li a.active:focus{color:#2a6496;background-color:#eee}.o_tree ul li a.active_parent{color:#777;font-weight:bold}.o_tree ul li a.active_parent:hover,.o_tree ul li a.active_parent:focus{color:#333}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l0{left:6px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l1{left:21px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l2{left:36px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l3{left:51px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l4{left:66px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l5{left:81px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l6{left:96px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l7{left:111px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l8{left:126px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l9{left:141px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l10{left:156px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l11{left:171px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l0{padding:10px 2px 10px 20px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l1{padding:10px 2px 10px 35px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l2{padding:10px 2px 10px 50px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l3{padding:10px 2px 10px 65px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l4{padding:10px 2px 10px 80px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l5{padding:10px 2px 10px 95px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l6{padding:10px 2px 10px 110px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l7{padding:10px 2px 10px 125px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l8{padding:10px 2px 10px 140px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l9{padding:10px 2px 10px 155px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l10{padding:10px 2px 10px 170px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l11{padding:10px 2px 10px 185px}.o_tree .o_dnd_item{cursor:move;z-index:100}.o_tree .o_dnd_proxy{opacity:0.4;filter:alpha(opacity=40);background-color:yellow}.o_tree .o_dnd_item.o_dnd_over{background-color:#ffff60}.o_tree .o_dnd_sibling{height:3px;width:100%}.o_tree .o_dnd_sibling.o_dnd_over{background:transparent url(../light/images/arrow_dd.png) top left no-repeat}.o_tree .o_dnd_l1{margin-left:0 !important}.o_tree .o_dnd_l2{margin-left:1em !important}.o_tree .o_dnd_l3{margin-left:2em !important}.o_tree .o_dnd_l4{margin-left:3em !important}.o_tree .o_dnd_l5{margin-left:4em !important}.o_tree .o_dnd_l6{margin-left:5em !important}.o_tree .o_dnd_l7{margin-left:6em !important}.o_tree .o_dnd_l8{margin-left:7em !important}.o_tree .o_dnd_l9{margin-left:8em !important}.o_tree .o_dnd_l10{margin-left:9em !important}.o_tree .o_dnd_l11{margin-left:10em !important}.o_offcanvas .o_tree{border:0}.o_selection_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_selection_tree ul{margin:0;padding:0;list-style-type:none}.o_selection_tree li{margin:0;padding:0;white-space:nowrap}.o_selection_tree li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_selection_tree li>div>span.o_tree_l0,.o_selection_tree li>div>div.checkbox.o_tree_l0,.o_selection_tree li>div>div.radio.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_selection_tree li>div>span.o_tree_l1,.o_selection_tree li>div>div.checkbox.o_tree_l1,.o_selection_tree li>div>div.radio.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_selection_tree li>div>span.o_tree_l2,.o_selection_tree li>div>div.checkbox.o_tree_l2,.o_selection_tree li>div>div.radio.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_selection_tree li>div>span.o_tree_l3,.o_selection_tree li>div>div.checkbox.o_tree_l3,.o_selection_tree li>div>div.radio.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_selection_tree li>div>span.o_tree_l4,.o_selection_tree li>div>div.checkbox.o_tree_l4,.o_selection_tree li>div>div.radio.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_selection_tree li>div>span.o_tree_l5,.o_selection_tree li>div>div.checkbox.o_tree_l5,.o_selection_tree li>div>div.radio.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_selection_tree li>div>span.o_tree_l6,.o_selection_tree li>div>div.checkbox.o_tree_l6,.o_selection_tree li>div>div.radio.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_selection_tree li>div>span.o_tree_l7,.o_selection_tree li>div>div.checkbox.o_tree_l7,.o_selection_tree li>div>div.radio.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_selection_tree li>div>span.o_tree_l8,.o_selection_tree li>div>div.checkbox.o_tree_l8,.o_selection_tree li>div>div.radio.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_selection_tree li>div>span.o_tree_l9,.o_selection_tree li>div>div.checkbox.o_tree_l9,.o_selection_tree li>div>div.radio.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_selection_tree li>div>span.o_tree_l10,.o_selection_tree li>div>div.checkbox.o_tree_l10,.o_selection_tree li>div>div.radio.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_selection_tree li>div>span.o_tree_l11,.o_selection_tree li>div>div.checkbox.o_tree_l11,.o_selection_tree li>div>div.radio.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_breadcrumb{position:relative}.o_breadcrumb .o_breadcrumb_close{float:right;position:relative;margin:0 0 0 15px;vertical-align:middle}.o_breadcrumb .o_breadcrumb_close a{line-height:15px;color:#d9534f}.o_breadcrumb .o_breadcrumb_close a:hover{color:#b52b27}.o_breadcrumb .o_breadcrumb_close a i{font-size:16px}.o_breadcrumb .o_breadcrumb_close a span{display:none}.o_breadcrumb .o_breadcrumb_close:before{content:none}.o_form .o_icon_mandatory{position:relative;right:0;line-height:inherit;margin-left:0.25em}.o_form .o_error{margin-top:1px;margin-bottom:0;padding:10px}.o_form hr.o_spacer_noline{border-top:1px solid transparent}.o_form .o_date{position:relative;padding-right:34px}.o_form .o_date.form-inline .form-group,.o_form .o_date.navbar-form .form-group,.o_form .o_date.o_navbar-form .form-group{margin-left:0}.o_form input.o_date_ms{width:3em}.o_form .o_date.form-inline .o_date_ms.form-group,.o_form .o_date.navbar-form .o_date_ms.form-group,.o_form .o_date.o_navbar-form .o_date_ms.form-group{margin-left:25px}.o_form .input-group.o_date_picker{width:16em}.o_form .o_filepreview{margin-bottom:10px}.o_form .o_fileinput{cursor:pointer;position:relative}.o_form .o_fileinput .o_fakechooser{position:relative;z-index:1}.o_form .o_fileinput .o_realchooser{position:absolute;top:0;left:0;z-index:2;opacity:0;filter:alpha(opacity=0)}.o_choice_checkrow,.o_choice_textrow{vertical-align:text-top;padding-bottom:2px}.o_choice_textrow{padding-left:1em}.o_togglecheck a{white-space:nowrap}.o_catalog .o_catalog_delete_img{position:relative;top:-0.5em}.o_button_dirty{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_button_dirty:hover,.o_button_dirty:focus,.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{background-image:none}.o_button_dirty.disabled,.o_button_dirty.disabled:hover,.o_button_dirty.disabled:focus,.o_button_dirty.disabled:active,.o_button_dirty.disabled.active,.o_button_dirty[disabled],.o_button_dirty[disabled]:hover,.o_button_dirty[disabled]:focus,.o_button_dirty[disabled]:active,.o_button_dirty[disabled].active,fieldset[disabled] .o_button_dirty,fieldset[disabled] .o_button_dirty:hover,fieldset[disabled] .o_button_dirty:focus,fieldset[disabled] .o_button_dirty:active,fieldset[disabled] .o_button_dirty.active{background-color:#f0ad4e;border-color:#eea236}.o_button_dirty .badge{color:#f0ad4e;background-color:#fff}.o_button_toggle{border:1px solid #777;border-top-right-radius:9px;border-top-left-radius:9px;border-bottom-right-radius:9px;border-bottom-left-radius:9px;background:#eee;display:inline-block;height:18px;line-height:16px;font-size:16px;text-align:left;padding:0 0.5em 0 0;margin:0}.o_button_toggle i{color:#777;text-shadow:1px 0 2px rgba(0,0,0,0.25)}.o_button_toggle span{line-height:16px;vertical-align:top;font-size:60%;color:#777;text-transform:uppercase}.o_button_toggle.o_on{text-align:right;padding:0 0 0 0.5em}.o_button_toggle.o_on i{color:#428bca;text-shadow:-1px 0 2px rgba(0,0,0,0.25)}.o_table_wrapper{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_table_wrapper.o_table_flexi .o_table_body{margin-top:20px}.o_table_wrapper.o_table_flexi .table{margin-top:20px}.o_table_wrapper.o_table_flexi .table td ul{margin:0}.o_table_wrapper.o_table_edit table tbody{border-top:solid #f90 4px;background-color:#fefbf6}.o_table_wrapper .o_table_search{max-width:50em}.o_table_wrapper .o_table_footer .o_table_pagination{text-align:center}.o_table_wrapper .o_row_selected td{background-color:#dff0d8 !important}.o_table_wrapper .o_table{margin-bottom:0}.o_table_wrapper .o_marked{font-weight:bold}.o_table_wrapper .table{margin-bottom:0}.o_table_wrapper th{color:#333}a.o_orderby,a.o_orderby:hover{color:#333;text-decoration:none}a.o_orderby.o_orderby_asc,a.o_orderby.o_orderby_desc,a.o_orderby:hover.o_orderby_asc,a.o_orderby:hover.o_orderby_desc{border-bottom:1px solid #ddd}.o_table_row_count{padding-top:6px;padding-bottom:6px;vertical-align:middle}.o_table_row_details td{background-color:white !important}.o_table_config{font-size:12px}.o_table_buttons{text-align:center}.o_table_buttons input{margin-right:1em}.o_table_buttons input:last-child{margin-right:0}.o_table_tools{margin-left:6px}.o_table_tools_indications{margin-left:10px;padding-top:3px;font-size:66%}.o_table_count{max-width:20em;float:left;padding:0 15px}.o_info .table-bordered td,o_note .table-bordered td,o_important .table-bordered td,o_warning .table-bordered td,o_error .table-bordered td{border-color:#333}.panel .o_table_layout{border-top:1px solid #ddd;padding-top:6px}.panel .o_table_count{padding:0 15px}#o_navbar_imclient #o_im_message,#o_navbar_imclient #o_im_status,#o_navbar_imclient #o_im_summary{float:left;position:relative;padding:15px 3px}#o_navbar_imclient #o_im_summary{padding-right:15px}#o_navbar_imclient #o_im_summary .badge{color:#fff;background-color:#777}#o_navbar_imclient #o_im_status div.o_chelp_wrapper{right:0.5em}.o_im_load_history{margin-bottom:6px}.o_im_load_history .o_label{font-size:12px;padding-right:0.5em;line-height:1.5em;color:#777}.o_im_chat_history{height:170px;font-size:90%;border:1px solid #eee;margin:0 0 1em 0;overflow:scroll;overflow-x:auto}.o_im_message_group{padding:3px 3px 3px 40px;min-height:40px;position:relative;border-top:1px solid #eee;background:#fff}.o_im_message_group.o_odd{background:#f4f4f4}.o_im_message_group .o_portrait{position:absolute;top:3px;left:3px}.o_im_message_group .o_im_from{color:#777;font-size:12px;font-weight:bold}.o_im_message_group .o_im_from:hover{color:#5e5e5e}.o_im_message_group div.o_im_body{padding:3px 0 3px 0;font-size:12px}.o_im_message_group div.o_im_body .o_date{float:right;color:#777;font-size:9px}.o_groupchat_roster{font-size:12px}.o_groupchat_roster li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333}.o_groupchat_roster li.o_vip{color:#3c763d}.o_groupchat_roster li.o_anonymous{color:#31708f}.o_im_buddieslist .o_im_buddieslist_toggler .btn{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_im_buddieslist ul{font-size:12px}.o_im_buddieslist ul ul{padding-left:1em}.o_im_buddieslist ul a{color:#428bca}.o_im_buddieslist ul a:hover{color:#2a6496}.o_flag{position:relative;top:1px;display:inline-block;line-height:1;width:16px;height:16px;background-repeat:no-repeat;background-position:0 100%}option.o_with_flag{padding-left:23px;min-height:16px;background-repeat:no-repeat;background-position:2px 50%}.o_flag_en{background-image:url("../light/images/flags/gb.png")}.o_flag_de{background-image:url("../light/images/flags/de.png")}.o_flag_fr{background-image:url("../light/images/flags/fr.png")}.o_flag_it{background-image:url("../light/images/flags/it.png")}.o_flag_es{background-image:url("../light/images/flags/es.png")}.o_flag_da{background-image:url("../light/images/flags/dk.png")}.o_flag_cs{background-image:url("../light/images/flags/cz.png")}.o_flag_el{background-image:url("../light/images/flags/gr.png")}.o_flag_ee{background-image:url("../light/images/flags/ee.png")}.o_flag_ru{background-image:url("../light/images/flags/ru.png")}.o_flag_pl{background-image:url("../light/images/flags/pl.png")}.o_flag_zh_CN{background-image:url("../light/images/flags/cn.png")}.o_flag_zh_TW{background-image:url("../light/images/flags/tw.png")}.o_flag_lt{background-image:url("../light/images/flags/lt.png")}.o_flag_fa{background-image:url("../light/images/flags/ir.png")}.o_flag_pt_PT{background-image:url("../light/images/flags/pt.png")}.o_flag_pt_BR{background-image:url("../light/images/flags/br.png")}.o_flag_tr{background-image:url("../light/images/flags/tr.png")}.o_flag_hu{background-image:url("../light/images/flags/hu.png")}.o_flag_sq{background-image:url("../light/images/flags/al.png")}.o_flag_in{background-image:url("../light/images/flags/id.png")}.o_flag_ar{background-image:url("../light/images/flags/eg.png")}.o_flag_rm{background-image:url("../light/images/flags/rm.png")}.o_flag_af{background-image:url("../light/images/flags/za.png")}.o_flag_vi{background-image:url("../light/images/flags/vn.png")}.o_flag_mn{background-image:url("../light/images/flags/mn.png")}.o_flag_iw{background-image:url("../light/images/flags/il.png")}.o_flag_ko{background-image:url("../light/images/flags/kr.png")}.o_flag_nl_NL{background-image:url("../light/images/flags/nl.png")}.o_flag_jp{background-image:url("../light/images/flags/jp.png")}.o_flag_nb_NO{background-image:url("../light/images/flags/no.png")}.o_flag_et_EE{background-image:url("../light/images/flags/ee.png")}.o_flag_bg{background-image:url("../light/images/flags/bg.png")}.o_flag_hi_IN_ASIA{background-image:url("../light/images/flags/in.png")}.o_flag_ar_LB{background-image:url("../light/images/flags/lb.png")}.o_flag_gl_ES{background-image:url("../light/images/flags/galicia.png")}.o_flag_sk{background-image:url("../light/images/flags/sk.png")}.o_rating .o_rating_title{font-size:12px}.o_rating .o_rating_items{white-space:nowrap}.o_rating .o_rating_items .o_icon{color:#f0ad4e}.o_rating .o_rating_items .o_icon:hover{color:#428bca}.o_rating .o_rating_items .o_legend{margin-left:1em;font-size:12px;line-height:1em}.o_rating .o_rating_explanation{font-size:12px;color:#777}@media (max-width: 991px){.o_rating .o_rating_title,.o_rating .o_rating_explanation{display:none}}
+#o_msg_sticky,#o_msg_sticky_preview{position:relative;color:#a94442;background-color:#f2dede;border:1px solid #ebccd1;padding:10px 16px 10px 60px;min-height:40px;margin:-20px 0 20px 0}#o_msg_sticky .o_icon_info_msg,#o_msg_sticky_preview .o_icon_info_msg{position:absolute;left:10px;top:5px;font-size:40px}@media (min-width: 768px){.modal .o_modal_fullwidth{width:90%}}@media (min-width: 992px){.modal .o_modal_fullwidth{width:80%}}.modal .modal-header h4,.modal .modal-header .o_cal .fc-header-title h2,.o_cal .fc-header-title .modal .modal-header h2{color:#428bca;font-weight:500;font-family:inherit;line-height:1.1}.o_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_tree a{color:#777;background-color:none}.o_tree a:hover,.o_tree a:focus{color:#333;background-color:#f8f8f8}.o_tree ul{margin:0;padding:0;list-style-type:none}.o_tree ul li{margin:0;padding:0;white-space:nowrap}.o_tree ul li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_tree ul li div a.o_tree_oc_l0{position:absolute;top:10px;left:-4px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l0,.o_tree ul .o_tree_level_close.b_tree_oc_l0{z-index:10}.o_tree ul li div a.o_tree_oc_l1{position:absolute;top:10px;left:11px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l1,.o_tree ul .o_tree_level_close.b_tree_oc_l1{z-index:10}.o_tree ul li div a.o_tree_oc_l2{position:absolute;top:10px;left:26px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l2,.o_tree ul .o_tree_level_close.b_tree_oc_l2{z-index:10}.o_tree ul li div a.o_tree_oc_l3{position:absolute;top:10px;left:41px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l3,.o_tree ul .o_tree_level_close.b_tree_oc_l3{z-index:10}.o_tree ul li div a.o_tree_oc_l4{position:absolute;top:10px;left:56px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l4,.o_tree ul .o_tree_level_close.b_tree_oc_l4{z-index:10}.o_tree ul li div a.o_tree_oc_l5{position:absolute;top:10px;left:71px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l5,.o_tree ul .o_tree_level_close.b_tree_oc_l5{z-index:10}.o_tree ul li div a.o_tree_oc_l6{position:absolute;top:10px;left:86px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l6,.o_tree ul .o_tree_level_close.b_tree_oc_l6{z-index:10}.o_tree ul li div a.o_tree_oc_l7{position:absolute;top:10px;left:101px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l7,.o_tree ul .o_tree_level_close.b_tree_oc_l7{z-index:10}.o_tree ul li div a.o_tree_oc_l8{position:absolute;top:10px;left:116px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l8,.o_tree ul .o_tree_level_close.b_tree_oc_l8{z-index:10}.o_tree ul li div a.o_tree_oc_l9{position:absolute;top:10px;left:131px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l9,.o_tree ul .o_tree_level_close.b_tree_oc_l9{z-index:10}.o_tree ul li div a.o_tree_oc_l10{position:absolute;top:10px;left:146px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l10,.o_tree ul .o_tree_level_close.b_tree_oc_l10{z-index:10}.o_tree ul li div a.o_tree_oc_l11{position:absolute;top:10px;left:161px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l11,.o_tree ul .o_tree_level_close.b_tree_oc_l11{z-index:10}.o_tree ul li div a.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_tree ul li div a.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_tree ul li div a.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_tree ul li div a.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_tree ul li div a.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_tree ul li div a.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_tree ul li div a.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_tree ul li div a.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_tree ul li div a.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_tree ul li div a.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_tree ul li div a.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_tree ul li div a.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_tree ul span.o_tree_leaf{display:none}.o_tree ul li .badge{position:absolute;font-size:70%}.o_tree ul li .badge:before{content:none}.o_tree ul li .badge.o_badge_1{top:3px;right:1px}.o_tree ul li .badge.o_badge_2{bottom:3px;right:1px}.o_tree ul li .badge.o_badge_3{top:3px;right:25px}.o_tree ul li .badge.o_badge_4{bottom:3px;right:25px}.o_tree ul li div.o_dnd_sibling{margin:0;padding:0;border-bottom:none}.o_tree ul li a.active{color:#428bca;background-color:none;font-weight:bold}.o_tree ul li a.active:hover,.o_tree ul li a.active:focus{color:#2a6496;background-color:#eee}.o_tree ul li a.active_parent{color:#777;font-weight:bold}.o_tree ul li a.active_parent:hover,.o_tree ul li a.active_parent:focus{color:#333}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l0{left:6px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l1{left:21px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l2{left:36px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l3{left:51px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l4{left:66px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l5{left:81px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l6{left:96px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l7{left:111px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l8{left:126px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l9{left:141px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l10{left:156px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l11{left:171px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l0{padding:10px 2px 10px 20px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l1{padding:10px 2px 10px 35px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l2{padding:10px 2px 10px 50px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l3{padding:10px 2px 10px 65px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l4{padding:10px 2px 10px 80px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l5{padding:10px 2px 10px 95px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l6{padding:10px 2px 10px 110px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l7{padding:10px 2px 10px 125px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l8{padding:10px 2px 10px 140px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l9{padding:10px 2px 10px 155px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l10{padding:10px 2px 10px 170px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l11{padding:10px 2px 10px 185px}.o_tree .o_dnd_item{cursor:move;z-index:100}.o_tree .o_dnd_proxy{opacity:0.4;filter:alpha(opacity=40);background-color:yellow}.o_tree .o_dnd_item.o_dnd_over{background-color:#ffff60}.o_tree .o_dnd_sibling{height:3px;width:100%}.o_tree .o_dnd_sibling.o_dnd_over{background:transparent url(../light/images/arrow_dd.png) top left no-repeat}.o_tree .o_dnd_l1{margin-left:0 !important}.o_tree .o_dnd_l2{margin-left:1em !important}.o_tree .o_dnd_l3{margin-left:2em !important}.o_tree .o_dnd_l4{margin-left:3em !important}.o_tree .o_dnd_l5{margin-left:4em !important}.o_tree .o_dnd_l6{margin-left:5em !important}.o_tree .o_dnd_l7{margin-left:6em !important}.o_tree .o_dnd_l8{margin-left:7em !important}.o_tree .o_dnd_l9{margin-left:8em !important}.o_tree .o_dnd_l10{margin-left:9em !important}.o_tree .o_dnd_l11{margin-left:10em !important}.o_offcanvas .o_tree{border:0}.o_selection_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_selection_tree ul{margin:0;padding:0;list-style-type:none}.o_selection_tree li{margin:0;padding:0;white-space:nowrap}.o_selection_tree li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_selection_tree li>div>span.o_tree_l0,.o_selection_tree li>div>div.checkbox.o_tree_l0,.o_selection_tree li>div>div.radio.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_selection_tree li>div>span.o_tree_l1,.o_selection_tree li>div>div.checkbox.o_tree_l1,.o_selection_tree li>div>div.radio.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_selection_tree li>div>span.o_tree_l2,.o_selection_tree li>div>div.checkbox.o_tree_l2,.o_selection_tree li>div>div.radio.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_selection_tree li>div>span.o_tree_l3,.o_selection_tree li>div>div.checkbox.o_tree_l3,.o_selection_tree li>div>div.radio.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_selection_tree li>div>span.o_tree_l4,.o_selection_tree li>div>div.checkbox.o_tree_l4,.o_selection_tree li>div>div.radio.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_selection_tree li>div>span.o_tree_l5,.o_selection_tree li>div>div.checkbox.o_tree_l5,.o_selection_tree li>div>div.radio.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_selection_tree li>div>span.o_tree_l6,.o_selection_tree li>div>div.checkbox.o_tree_l6,.o_selection_tree li>div>div.radio.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_selection_tree li>div>span.o_tree_l7,.o_selection_tree li>div>div.checkbox.o_tree_l7,.o_selection_tree li>div>div.radio.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_selection_tree li>div>span.o_tree_l8,.o_selection_tree li>div>div.checkbox.o_tree_l8,.o_selection_tree li>div>div.radio.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_selection_tree li>div>span.o_tree_l9,.o_selection_tree li>div>div.checkbox.o_tree_l9,.o_selection_tree li>div>div.radio.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_selection_tree li>div>span.o_tree_l10,.o_selection_tree li>div>div.checkbox.o_tree_l10,.o_selection_tree li>div>div.radio.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_selection_tree li>div>span.o_tree_l11,.o_selection_tree li>div>div.checkbox.o_tree_l11,.o_selection_tree li>div>div.radio.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_breadcrumb{position:relative}.o_breadcrumb .o_breadcrumb_close{float:right;position:relative;margin:0 0 0 15px;vertical-align:middle}.o_breadcrumb .o_breadcrumb_close a{line-height:15px;color:#d9534f}.o_breadcrumb .o_breadcrumb_close a:hover{color:#b52b27}.o_breadcrumb .o_breadcrumb_close a i{font-size:16px}.o_breadcrumb .o_breadcrumb_close a span{display:none}.o_breadcrumb .o_breadcrumb_close:before{content:none}.o_form .o_icon_mandatory{position:relative;right:0;line-height:inherit;margin-left:0.25em}.o_form .o_error{margin-top:1px;margin-bottom:0;padding:10px}.o_form hr.o_spacer_noline{border-top:1px solid transparent}.o_form .o_date{position:relative;padding-right:34px}.o_form .o_date.form-inline .form-group,.o_form .o_date.navbar-form .form-group,.o_form .o_date.o_navbar-form .form-group{margin-left:0}.o_form input.o_date_ms{width:3em}.o_form .o_date.form-inline .o_date_ms.form-group,.o_form .o_date.navbar-form .o_date_ms.form-group,.o_form .o_date.o_navbar-form .o_date_ms.form-group{margin-left:25px}.o_form .o_form_element.form-inline .o_form_element.form-group,.o_form .o_form_element.navbar-form .o_form_element.form-group,.o_form .o_form_element.o_navbar-form .o_form_element.form-group{margin-left:25px}.o_form .input-group.o_date_picker{width:16em}.o_form .o_filepreview{margin-bottom:10px}.o_form .o_fileinput{cursor:pointer;position:relative}.o_form .o_fileinput .o_fakechooser{position:relative;z-index:1}.o_form .o_fileinput .o_realchooser{position:absolute;top:0;left:0;z-index:2;opacity:0;filter:alpha(opacity=0)}.o_choice_checkrow,.o_choice_textrow{vertical-align:text-top;padding-bottom:2px}.o_choice_textrow{padding-left:1em}.o_togglecheck a{white-space:nowrap}.o_catalog .o_catalog_delete_img{position:relative;top:-0.5em}.o_button_dirty{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_button_dirty:hover,.o_button_dirty:focus,.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{background-image:none}.o_button_dirty.disabled,.o_button_dirty.disabled:hover,.o_button_dirty.disabled:focus,.o_button_dirty.disabled:active,.o_button_dirty.disabled.active,.o_button_dirty[disabled],.o_button_dirty[disabled]:hover,.o_button_dirty[disabled]:focus,.o_button_dirty[disabled]:active,.o_button_dirty[disabled].active,fieldset[disabled] .o_button_dirty,fieldset[disabled] .o_button_dirty:hover,fieldset[disabled] .o_button_dirty:focus,fieldset[disabled] .o_button_dirty:active,fieldset[disabled] .o_button_dirty.active{background-color:#f0ad4e;border-color:#eea236}.o_button_dirty .badge{color:#f0ad4e;background-color:#fff}.o_button_toggle{border:1px solid #777;border-top-right-radius:9px;border-top-left-radius:9px;border-bottom-right-radius:9px;border-bottom-left-radius:9px;background:#eee;display:inline-block;height:18px;line-height:16px;font-size:16px;text-align:left;padding:0 0.5em 0 0;margin:0}.o_button_toggle i{color:#777;text-shadow:1px 0 2px rgba(0,0,0,0.25)}.o_button_toggle span{line-height:16px;vertical-align:top;font-size:60%;color:#777;text-transform:uppercase}.o_button_toggle.o_on{text-align:right;padding:0 0 0 0.5em}.o_button_toggle.o_on i{color:#428bca;text-shadow:-1px 0 2px rgba(0,0,0,0.25)}.o_table_wrapper{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_table_wrapper.o_table_flexi .o_table_body{margin-top:20px}.o_table_wrapper.o_table_flexi .table{margin-top:20px}.o_table_wrapper.o_table_flexi .table td ul{margin:0}.o_table_wrapper.o_table_edit table tbody{border-top:solid #f90 4px;background-color:#fefbf6}.o_table_wrapper .o_table_search{max-width:50em}.o_table_wrapper .o_table_footer .o_table_pagination{text-align:center}.o_table_wrapper .o_row_selected td{background-color:#dff0d8 !important}.o_table_wrapper .o_table{margin-bottom:0}.o_table_wrapper .o_marked{font-weight:bold}.o_table_wrapper .table{margin-bottom:0}.o_table_wrapper th{color:#333}a.o_orderby,a.o_orderby:hover{color:#333;text-decoration:none}a.o_orderby.o_orderby_asc,a.o_orderby.o_orderby_desc,a.o_orderby:hover.o_orderby_asc,a.o_orderby:hover.o_orderby_desc{border-bottom:1px solid #ddd}.o_table_row_count{padding-top:6px;padding-bottom:6px;vertical-align:middle}.o_table_row_details td{background-color:white !important}.o_table_config{font-size:12px}.o_table_buttons{text-align:center}.o_table_buttons input{margin-right:1em}.o_table_buttons input:last-child{margin-right:0}.o_table_tools{margin-left:6px}.o_table_tools_indications{margin-left:10px;padding-top:3px;font-size:66%}.o_table_count{max-width:20em;float:left;padding:0 15px}.o_info .table-bordered td,o_note .table-bordered td,o_important .table-bordered td,o_warning .table-bordered td,o_error .table-bordered td{border-color:#333}.panel .o_table_layout{border-top:1px solid #ddd;padding-top:6px}.panel .o_table_count{padding:0 15px}#o_navbar_imclient #o_im_message,#o_navbar_imclient #o_im_status,#o_navbar_imclient #o_im_summary{float:left;position:relative;padding:15px 3px}#o_navbar_imclient #o_im_summary{padding-right:15px}#o_navbar_imclient #o_im_summary .badge{color:#fff;background-color:#777}#o_navbar_imclient #o_im_status div.o_chelp_wrapper{right:0.5em}.o_im_load_history{margin-bottom:6px}.o_im_load_history .o_label{font-size:12px;padding-right:0.5em;line-height:1.5em;color:#777}.o_im_chat_history{height:170px;font-size:90%;border:1px solid #eee;margin:0 0 1em 0;overflow:scroll;overflow-x:auto}.o_im_message_group{padding:3px 3px 3px 40px;min-height:40px;position:relative;border-top:1px solid #eee;background:#fff}.o_im_message_group.o_odd{background:#f4f4f4}.o_im_message_group .o_portrait{position:absolute;top:3px;left:3px}.o_im_message_group .o_im_from{color:#777;font-size:12px;font-weight:bold}.o_im_message_group .o_im_from:hover{color:#5e5e5e}.o_im_message_group div.o_im_body{padding:3px 0 3px 0;font-size:12px}.o_im_message_group div.o_im_body .o_date{float:right;color:#777;font-size:9px}.o_groupchat_roster{font-size:12px}.o_groupchat_roster li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333}.o_groupchat_roster li.o_vip{color:#3c763d}.o_groupchat_roster li.o_anonymous{color:#31708f}.o_im_buddieslist .o_im_buddieslist_toggler .btn{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_im_buddieslist ul{font-size:12px}.o_im_buddieslist ul ul{padding-left:1em}.o_im_buddieslist ul a{color:#428bca}.o_im_buddieslist ul a:hover{color:#2a6496}.o_flag{position:relative;top:1px;display:inline-block;line-height:1;width:16px;height:16px;background-repeat:no-repeat;background-position:0 100%}option.o_with_flag{padding-left:23px;min-height:16px;background-repeat:no-repeat;background-position:2px 50%}.o_flag_en{background-image:url("../light/images/flags/gb.png")}.o_flag_de{background-image:url("../light/images/flags/de.png")}.o_flag_fr{background-image:url("../light/images/flags/fr.png")}.o_flag_it{background-image:url("../light/images/flags/it.png")}.o_flag_es{background-image:url("../light/images/flags/es.png")}.o_flag_da{background-image:url("../light/images/flags/dk.png")}.o_flag_cs{background-image:url("../light/images/flags/cz.png")}.o_flag_el{background-image:url("../light/images/flags/gr.png")}.o_flag_ee{background-image:url("../light/images/flags/ee.png")}.o_flag_ru{background-image:url("../light/images/flags/ru.png")}.o_flag_pl{background-image:url("../light/images/flags/pl.png")}.o_flag_zh_CN{background-image:url("../light/images/flags/cn.png")}.o_flag_zh_TW{background-image:url("../light/images/flags/tw.png")}.o_flag_lt{background-image:url("../light/images/flags/lt.png")}.o_flag_fa{background-image:url("../light/images/flags/ir.png")}.o_flag_pt_PT{background-image:url("../light/images/flags/pt.png")}.o_flag_pt_BR{background-image:url("../light/images/flags/br.png")}.o_flag_tr{background-image:url("../light/images/flags/tr.png")}.o_flag_hu{background-image:url("../light/images/flags/hu.png")}.o_flag_sq{background-image:url("../light/images/flags/al.png")}.o_flag_in{background-image:url("../light/images/flags/id.png")}.o_flag_ar{background-image:url("../light/images/flags/eg.png")}.o_flag_rm{background-image:url("../light/images/flags/rm.png")}.o_flag_af{background-image:url("../light/images/flags/za.png")}.o_flag_vi{background-image:url("../light/images/flags/vn.png")}.o_flag_mn{background-image:url("../light/images/flags/mn.png")}.o_flag_iw{background-image:url("../light/images/flags/il.png")}.o_flag_ko{background-image:url("../light/images/flags/kr.png")}.o_flag_nl_NL{background-image:url("../light/images/flags/nl.png")}.o_flag_jp{background-image:url("../light/images/flags/jp.png")}.o_flag_nb_NO{background-image:url("../light/images/flags/no.png")}.o_flag_et_EE{background-image:url("../light/images/flags/ee.png")}.o_flag_bg{background-image:url("../light/images/flags/bg.png")}.o_flag_hi_IN_ASIA{background-image:url("../light/images/flags/in.png")}.o_flag_ar_LB{background-image:url("../light/images/flags/lb.png")}.o_flag_gl_ES{background-image:url("../light/images/flags/galicia.png")}.o_flag_sk{background-image:url("../light/images/flags/sk.png")}.o_rating .o_rating_title{font-size:12px}.o_rating .o_rating_items{white-space:nowrap}.o_rating .o_rating_items .o_icon{color:#f0ad4e}.o_rating .o_rating_items .o_icon:hover{color:#428bca}.o_rating .o_rating_items .o_legend{margin-left:1em;font-size:12px;line-height:1em}.o_rating .o_rating_explanation{font-size:12px;color:#777}@media (max-width: 991px){.o_rating .o_rating_title,.o_rating .o_rating_explanation{display:none}}
 .o_comments .o_comment_wrapper .o_avatar{float:left;margin:0 1em 0 0}.o_comments .o_comment_wrapper .o_reply,.o_comments .o_comment_wrapper .o_delete{float:right}.o_ratings_and_comments .o_rating_wrapper{vertical-align:middle;display:inline-block}.o_ratings_and_comments a.o_comments{margin-left:10px;position:relative;top:0.1em}.d3chart .bar{shape-rendering:crispEdges}.d3chart .bar_default_light{fill:#7eb0db}.d3chart .bar_default{fill:#428bca}.d3chart .bar_default_dark{fill:#2a6496}.d3chart .axis{font:12px sans-serif}.d3chart .axis path,.d3chart .axis line{fill:none;stroke:#000;shape-rendering:crispEdges}.o_forum_peekview .o_quote_wrapper,.o_forum_peekview .b_quote_wrapper{display:none}.o_forum_thread_sticky{font-weight:bold}.o_forum_switch{font-size:12px}.o_forum_toolbar{margin-bottom:6px;float:left}.o_forum_fulltextsearch{float:right}@media (max-width: 767px){.o_forum_fulltextsearch{float:left}}.o_forum .o_mark,.o_forum .o_ep_collect{float:right;position:relative;width:2em;margin-left:12px}.o_forum .o_portrait{float:left;margin-right:16px}.o_forum .o_portrait_avatar{width:70px;height:70px}.o_forum .o_newindicator{font-size:10px;color:#5cb85c;text-transform:uppercase;padding-left:1em;vertical-align:text-top;white-space:nowrap}.o_forum .o_author,.o_forum .o_date{display:inline-block;color:#777}.o_forum .o_date{font-size:12px}.o_forum .o_modified{color:#8a6d3b;font-size:12px;font-style:italic}.o_forum .o_forum_message{margin-bottom:20px;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1)}.o_forum .o_forum_message_new{-webkit-box-shadow:0 1px 10px rgba(92,184,92,0.3);box-shadow:0 1px 10px rgba(92,184,92,0.3)}.o_forum .o_forum_message_highlight{-webkit-box-shadow:0 1px 10px rgba(240,173,78,0.5);box-shadow:0 1px 10px rgba(240,173,78,0.5)}.o_forum .o_forum_message_header{padding:10px 15px;border-bottom:1px solid #ddd;background-color:#f5f5f5;border-top-right-radius:3px;border-top-left-radius:3px}.o_forum .o_forum_message_title{margin-top:0}.o_forum .o_forum_message_body{padding:10px 15px}.o_forum .o_forum_message_attachments{border-top:1px solid #ddd;padding:10px 15px;font-size:12px;background-color:#f7f7f9}.o_forum .o_attachment{position:relative;max-width:250px;vertical-align:top;margin:6px 12px 10px 0}.o_forum .o_attachment img{margin-top:6px}.o_forum .o_filename{max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_forum .o_icon_enlarge{position:absolute;left:1em;bottom:1em;text-shadow:1px 1px 2px #fff, -1px 1px 2px #fff, 1px -1px 2px #fff, -1px -1px 2px #fff}@media (min-width: 768px) and (max-width: 991px){.o_forum .o_attachments{font-size:10px}.o_forum .o_attachment{max-width:200px}.o_forum .o_attachment img{max-width:150px}.o_forum .o_filename{max-width:200px}}@media (max-width: 767px){.o_forum .o_attachments{font-size:9px}.o_forum .o_attachment{max-width:150px}.o_forum .o_attachment img{max-width:100px}.o_forum .o_filename{max-width:150px}}
 .o_quote_wrapper,.b_quote_wrapper{position:relative;margin:10px 0}.o_quote_author,.b_quote_author{color:#777;font-size:12px}.o_quote_author:before,.b_quote_author:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f10e";font-size:21px;padding-right:0.5em}blockquote.o_quote,blockquote.b_quote{color:#555;font-size:12px;margin-top:6px;padding:0 12px}a.o_chelp{display:inline-block;padding:1px 3px;text-align:center;vertical-align:middle;white-space:nowrap;font-size:10px;font-weight:normal;line-height:15px;color:#fff;background-color:#428bca;border:1px solid #357ebd;border-radius:2px;cursor:help;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}a.o_chelp:active,a.o_chelp:hover,a.o_chelp:focus{text-decoration:none;color:#fff;background-color:#3276b1;border-color:#285e8e}a.o_chelp i{font-size:10px !important}.o_chelp_wrapper{position:relative;float:right;display:inline-block;line-height:1em}.o_iframedisplay iframe{width:100%}.o_singlepage .o_edit{position:absolute;top:10px;right:37px}.o_content_popup{position:absolute;top:10px;right:12px}.o_module_cp_wrapper .o_tools{position:absolute;top:10px;right:12px;text-align:right;vertical-align:middle}.o_module_cp_wrapper .o_tools .o_search_wrapper{display:inline-block;position:relative;top:-2px}.o_dropdown .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.o_dropdown>li>a{display:block;padding:3px 0;clear:both;font-weight:normal;line-height:1.42857;color:#333;white-space:nowrap}.badge.o_scorm_completed{background-color:#3c763d}.badge.o_scorm_failed{background-color:#a94442}.badge.o_scorm_incomplete{background-color:#8a6d3b}.badge.o_scorm_not_attempted{background:none}.o_bc_meta h5,.o_bc_meta .o_author,.o_bc_meta .o_comment,.tooltip h5,.tooltip .o_author,.tooltip .o_comment{color:#fff;margin:5px 0}.o_bc_meta .o_thumbnail,.tooltip .o_thumbnail{width:200px;height:200px;background-color:#fff;margin:0 -5px}.o_htmleditor .o_lastmodified{color:#777}.o_htmleditor #o_save{margin-top:10px;text-align:center}.o_htmleditor #o_save input{margin-right:1em}.o_htmleditor #o_save input:last-child{margin-right:0}.o_notifications_news_wrapper .o_notifications_news_subscription{margin:10px 0}.o_notifications_news_wrapper .o_notifications_news_subscription h4 i,.o_notifications_news_wrapper .o_notifications_news_subscription .o_cal .fc-header-title h2 i,.o_cal .fc-header-title .o_notifications_news_wrapper .o_notifications_news_subscription h2 i{display:none}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_context{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content{margin-left:1.5em;position:relative}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_icon{position:absolute;left:-1.5em;line-height:1.5em;top:0}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_date{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_url{margin-left:1.5em}.o_noti{margin:6px 0 6px 12px;float:right;color:#777}.o_noti .o_label{color:#777;cursor:help}@media (max-width: 767px){.o_noti .o_label span{display:none}}
 .panel-body .o_noti{margin:0}.o_portrait{display:inline-block}.o_portrait img{border-radius:50%;border:none;background-color:#eee;background-position:50% 50%;background-repeat:no-repeat;background-size:cover}.o_portrait_name{margin-top:6px}.o_portrait_avatar,.o_portrait_dummy,.o_portrait_dummy_female_big,.o_portrait_dummy_male_big,.o_portrait_anonymous{width:100px;height:100px}.o_portrait_dummy{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_dummy_female_big{background-image:url("../light/images/portrait/dummy_female_big.png")}.o_portrait_dummy_male_big{background-image:url("../light/images/portrait/dummy_male_big.png")}.o_portrait_anonymous{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_avatar_small,.o_portrait_dummy_small,.o_portrait_dummy_female_small,.o_portrait_dummy_male_small,.o_portrait_anonymous_small{width:30px;height:30px}.o_portrait_dummy_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_portrait_dummy_female_small{background-image:url("../light/images/portrait/dummy_female_small.png")}.o_portrait_dummy_male_small{background-image:url("../light/images/portrait/dummy_male_small.png")}.o_portrait_anonymous_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_datecomp{position:relative;width:40px;height:52px;border:1px solid #555;margin-right:12px;text-align:center;vertical-align:middle}.o_datecomp div.o_year{position:absolute;left:0;width:100%;top:-20px;height:20px;line-height:20px;font-size:10px}.o_datecomp div.o_month{height:20px;line-height:20px;font-size:12px;background-color:#428bca;color:#fff}.o_datecomp div.o_day{height:30px;line-height:30px;font-size:18px;border-top:1px solid #555;background-color:#fff;color:#333}.o_block_with_datecomp .o_head{position:relative;padding-left:52px}.o_block_with_datecomp .o_datecomp{position:absolute;top:0.2em;left:0}.o_block_with_datecomp .o_title{margin-top:0}.o_block_with_datecomp .o_meta{color:#777}.o_block_with_datecomp .o_content{border-left:5px solid #eee;padding:0 20px}.o_block_with_datecomp .o_block_footer{padding-left:25px}.o_cal_toptoolbar{margin-bottom:6px}.o_cal_toptoolbar .o_cal_toptoolbar_sub,.o_cal_toptoolbar .o_cal_toptoolbar_help{float:left;margin-right:12px}.o_feed .o_date,.o_feed .o_author{color:#777}.o_feed .o_subscription a{margin-right:1.5em}.o_feed .o_subscription form{margin-top:6px}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper{float:left}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_title,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_explanation,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_legend{display:none}.o_feed .o_blog_posts .o_ratings_and_comments a.o_comments span{display:none}.o_glossary .o_register{text-align:center}.o_glossary .o_meta{font-size:90%;color:#777;font-style:italic}.o_glossary dl dt:first-letter{font-size:21px}.o_glossary dl dt small{color:#777}.o_tm_glossary{border-bottom:1px dotted #666699 !important}.o_tm_yellow{background-color:#FFFF66}.o_tm_blue{background-color:#33FFFF}.o_tm_red{background-color:#FF3333}.o_tm_green{background-color:#99FF00}.o_segments_content{margin-top:20px}.o_tabbed_pane .o_tabbed_pane_content{padding:20px 0 6px 0}.o_togglebox_wrapper .o_opener{position:relative;left:-0.5em}.o_togglebox_wrapper div.o_togglebox_content{position:relative;margin:0}.o_togglebox_wrapper div.o_togglebox_content .o_hide{position:absolute;bottom:0.5em;right:1em}.o_toolboxes ul{margin:0 0 1.5em 0;padding:0 0 0 1.5em}.o_qrcode{width:256px;height:256px}#o_ajax_busy{position:absolute;left:50%;top:20em;margin-left:-2.5em;height:5em;width:5em;color:#fff;z-index:1201;display:none}#o_body.o_ajax_busy{cursor:busy}.o_exception .o_visual{position:relative;background-image:url("../light/images/lion-500x333.jpg");filter:grayscale(50%);-webkit-filter:grayscale(50%);-moz-filter:grayscale(50%);-ms-filter:grayscale(50%);-o-filter:grayscale(50%);width:500px;height:333px;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;margin:0 0 10px 16px}@media (min-width: 768px) and (max-width: 991px){.o_exception .o_visual{width:375px;height:249px}}@media (min-width: 500px) and (max-width: 767px){.o_exception .o_visual{width:250px;height:166px}}@media (max-width: 500px){.o_exception .o_visual{background-size:cover}}.o_exception .jumbotron h1,.o_exception .o_repo_details .o_lead h1,.o_repo_details .o_exception .o_lead h1{color:#d9534f}.tt-input{width:400px}.tt-dropdown-menu{width:400px;margin-top:6px;padding:0 0 0;color:#555;background-color:#fff;border:1px solid #66afe9;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;-webkit-box-shadow:0 0 8px rgba(102,175,233,0.6);box-shadow:0 0 8px rgba(102,175,233,0.6)}.tt-suggestion{padding:6px 12px;font-size:14px;line-height:1.42857}.tt-suggestion.tt-cursor{color:#fff;background-color:#428bca}.tt-suggestion p{margin:0}.o_search_link_extended,.o_search_link_simple{margin-top:12px;display:inline-block}.o_search_results_stats{color:#777;padding-left:1.5em}.o_search_highlight{margin-left:12px;font-size:12px}.o_search_result_title h4,.o_search_result_title .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_search_result_title h2{display:inline-block;margin-right:12px;margin-bottom:6px}.o_search_result_highlight{font-weight:bold}.o_search_result_context{color:#3c763d}.o_search_result_excerpt{color:#555}.o_search_result_details .o_togglebox_wrapper.o_block{margin-top:0;margin-bottom:0}.o_search_result_details .o_togglebox_wrapper .o_togglebox_content{color:#777;font-size:12px;background:#fff;padding:6px 12px}@media (max-width: 767px){.o_search_result_details{display:none}}
diff --git a/src/test/java/org/olat/course/assessment/AssessmentManagerTest.java b/src/test/java/org/olat/course/assessment/AssessmentManagerTest.java
index 5c147b5cb123d3b729692854e3bb8d1f1d1716c3..200b734c98a13465d86885e5910e84f0cc5667a5 100644
--- a/src/test/java/org/olat/course/assessment/AssessmentManagerTest.java
+++ b/src/test/java/org/olat/course/assessment/AssessmentManagerTest.java
@@ -236,7 +236,7 @@ public class AssessmentManagerTest extends OlatTestCase  {
 		//delete the efficiencyStatements for the current course
 		efficiencyStatementManager.deleteEfficiencyStatementsFromCourse(courseRepositoryEntry.getKey());
 		DBFactory.getInstance().closeSession();
-		efficiencyStatement = efficiencyStatementManager.getUserEfficiencyStatement(courseRepositoryEntry.getKey(), student);
+		efficiencyStatement = efficiencyStatementManager.getUserEfficiencyStatementByCourseRepoKey(courseRepositoryEntry.getKey(), student);
 		DBFactory.getInstance().closeSession();
 		assertNull(efficiencyStatement);
 		
@@ -252,7 +252,7 @@ public class AssessmentManagerTest extends OlatTestCase  {
 		//delete the efficiencyStatement of the student
 		efficiencyStatementManager.deleteUserData(student, "deleted_" + student.getName());
 		DBFactory.getInstance().closeSession();
-		efficiencyStatement = efficiencyStatementManager.getUserEfficiencyStatement(courseRepositoryEntry.getKey(), student);
+		efficiencyStatement = efficiencyStatementManager.getUserEfficiencyStatementByCourseRepoKey(courseRepositoryEntry.getKey(), student);
 		DBFactory.getInstance().closeSession();
 		assertNull(efficiencyStatement);
 	}
@@ -266,7 +266,7 @@ public class AssessmentManagerTest extends OlatTestCase  {
 	private EfficiencyStatement checkEfficiencyStatement(RepositoryEntry courseRepositoryEntry) {
 		//check the stored EfficiencyStatement		
 		EfficiencyStatement efficiencyStatement = efficiencyStatementManager
-				.getUserEfficiencyStatement(courseRepositoryEntry.getKey(), student);
+				.getUserEfficiencyStatementByCourseRepoKey(courseRepositoryEntry.getKey(), student);
 		assertNotNull(efficiencyStatement);
 		List<Map<String,Object>> assessmentNodes = efficiencyStatement.getAssessmentNodes();
 		Iterator<Map<String,Object>> listIterator = assessmentNodes.iterator();
diff --git a/src/test/java/org/olat/course/certificate/manager/CertificatesManagerTest.java b/src/test/java/org/olat/course/certificate/manager/CertificatesManagerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e8fc7f90565ac6314bafdc3ce91356bac59ffd4
--- /dev/null
+++ b/src/test/java/org/olat/course/certificate/manager/CertificatesManagerTest.java
@@ -0,0 +1,182 @@
+/**
+ * <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.certificate.manager;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Calendar;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.GroupRoles;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.course.certificate.Certificate;
+import org.olat.course.certificate.CertificateTemplate;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.model.CertificateInfos;
+import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupService;
+import org.olat.group.manager.BusinessGroupRelationDAO;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.manager.RepositoryEntryRelationDAO;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 24.10.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CertificatesManagerTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private CertificatesManager certificatesManager;
+	@Autowired
+	private BusinessGroupService businessGroupService;
+	@Autowired
+	private BusinessGroupRelationDAO businessGroupRelationDao;
+	@Autowired
+	private RepositoryEntryRelationDAO repositoryEntryRelationDao;
+	
+	@Test
+	public void createTemplate() throws URISyntaxException {
+		URL templateUrl = CertificatesManagerTest.class.getResource("template.pdf");
+		Assert.assertNotNull(templateUrl);
+		File templateFile = new File(templateUrl.toURI());
+		
+		String name = UUID.randomUUID() + ".pdf";
+		CertificateTemplate template = certificatesManager.addTemplate(name, templateFile, true);
+		dbInstance.commitAndCloseSession();
+		
+		Assert.assertNotNull(template);
+		Assert.assertNotNull(template.getKey());
+		Assert.assertNotNull(template.getCreationDate());
+		Assert.assertNotNull(template.getLastModified());
+		Assert.assertEquals(name, template.getName());
+		Assert.assertTrue(template.isPublicTemplate());
+	}
+	
+	@Test
+	public void createCertificate() throws URISyntaxException {
+		Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-1");
+		RepositoryEntry entry = JunitTestHelper.deployDemoCourse(identity);
+		dbInstance.commitAndCloseSession();
+		
+		CertificateInfos certificateInfos = new CertificateInfos(identity, null, null);
+		Certificate certificate = certificatesManager.generateCertificate(certificateInfos, entry, null);
+		Assert.assertNotNull(certificate);
+		Assert.assertNotNull(certificate.getKey());
+		Assert.assertNotNull(certificate.getUuid());
+		Assert.assertNotNull(certificate.getName());
+		Assert.assertEquals(entry.getOlatResource().getKey(), certificate.getArchivedResourceKey());
+		
+		//check if the pdf exists
+		VFSLeaf certificateFile = certificatesManager.getCertificateLeaf(certificate);
+		Assert.assertNotNull(certificateFile);
+		Assert.assertTrue(certificateFile.exists());
+	}
+	
+	@Test
+	public void certificateNotifications_courseCoach() throws URISyntaxException {
+		Identity owner = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-2");
+		Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-3");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-4");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-4");
+		RepositoryEntry entry = JunitTestHelper.deployDemoCourse(owner);
+		repositoryEntryRelationDao.addRole(coach, entry, GroupRoles.coach.name());
+		repositoryEntryRelationDao.addRole(participant1, entry, GroupRoles.participant.name());
+		repositoryEntryRelationDao.addRole(participant2, entry, GroupRoles.participant.name());
+		dbInstance.commitAndCloseSession();
+		
+		CertificateInfos certificateInfos1 = new CertificateInfos(participant1, null, null);
+		Certificate certificate1 = certificatesManager.generateCertificate(certificateInfos1, entry, null);
+		Assert.assertNotNull(certificate1);
+		CertificateInfos certificateInfos2 = new CertificateInfos(participant2, null, null);
+		Certificate certificate2 = certificatesManager.generateCertificate(certificateInfos2, entry, null);
+		Assert.assertNotNull(certificate2);
+		dbInstance.commitAndCloseSession();
+		
+		Calendar lastestNews = Calendar.getInstance();
+		lastestNews.add(Calendar.HOUR_OF_DAY, -1);
+	
+		//check the notifications of the author ( entry admin )
+		List<Certificate> authorNotifications = certificatesManager.getCertificatesForNotifications(owner, entry, lastestNews.getTime());
+		Assert.assertNotNull(authorNotifications);
+		Assert.assertEquals(2, authorNotifications.size());
+		
+		//check the notifications of the coach
+		List<Certificate> coachNotifications = certificatesManager.getCertificatesForNotifications(coach, entry, lastestNews.getTime());
+		Assert.assertNotNull(coachNotifications);
+		Assert.assertEquals(2, coachNotifications.size());
+		
+		//check the notifications of the participant
+		List<Certificate> participantNotifications = certificatesManager.getCertificatesForNotifications(participant1, entry, lastestNews.getTime());
+		Assert.assertNotNull(participantNotifications);
+		Assert.assertEquals(1, participantNotifications.size());
+		Assert.assertTrue(participantNotifications.contains(certificate1));
+	}
+	
+	@Test
+	public void certificateNotifications_courseCoachByGroups() throws URISyntaxException {
+		Identity owner = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-5");
+		Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-6");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-7");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-8");
+		RepositoryEntry entry = JunitTestHelper.deployDemoCourse(owner);
+		BusinessGroup group = businessGroupService.createBusinessGroup(null, "certified-group", "Group with certification", null, null, false, false, entry);
+	    businessGroupRelationDao.addRole(coach, group, GroupRoles.coach.name());
+	    businessGroupRelationDao.addRole(participant1, group, GroupRoles.participant.name());
+	    businessGroupRelationDao.addRole(participant2, group, GroupRoles.participant.name());
+		dbInstance.commitAndCloseSession();
+		
+		//make a certificate
+		CertificateInfos certificateInfos1 = new CertificateInfos(participant1, null, null);
+		Certificate certificate1 = certificatesManager.generateCertificate(certificateInfos1, entry, null);
+		Assert.assertNotNull(certificate1);
+		CertificateInfos certificateInfos2 = new CertificateInfos(participant2, null, null);
+		Certificate certificate2 = certificatesManager.generateCertificate(certificateInfos2, entry, null);
+		Assert.assertNotNull(certificate2);
+		dbInstance.commitAndCloseSession();
+		
+		
+		Calendar lastestNews = Calendar.getInstance();
+		lastestNews.add(Calendar.HOUR_OF_DAY, -1);
+		
+		//check the notifications of the coach
+		List<Certificate> coachNotifications = certificatesManager.getCertificatesForNotifications(coach, entry, lastestNews.getTime());
+		Assert.assertNotNull(coachNotifications);
+		Assert.assertEquals(2, coachNotifications.size());
+		
+		//check the notifications of the participant
+		List<Certificate> participantNotifications = certificatesManager.getCertificatesForNotifications(participant1, entry, lastestNews.getTime());
+		Assert.assertNotNull(participantNotifications);
+		Assert.assertEquals(1, participantNotifications.size());
+		Assert.assertTrue(participantNotifications.contains(certificate1));
+	}
+}
diff --git a/src/test/java/org/olat/course/certificate/manager/template.pdf b/src/test/java/org/olat/course/certificate/manager/template.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3fa314b644641056e74f98959b499675767af645
Binary files /dev/null and b/src/test/java/org/olat/course/certificate/manager/template.pdf differ
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 87ef6fe24a545551815271e2f19aac4b7eb91200..8766314c19e3fb3177488db9ca0f650aea63cdb3 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -120,6 +120,7 @@ import org.junit.runners.Suite;
 	org.olat.course.nodes.en.EnrollmentManagerTest.class,//ok
 	org.olat.course.assessment.AssessmentManagerTest.class,//ok
 	org.olat.course.assessment.manager.UserCourseInformationsManagerTest.class,//ok
+	org.olat.course.certificate.manager.CertificatesManagerTest.class,
 	org.olat.course.config.CourseConfigManagerImplTest.class,//ok
 	org.olat.course.groupsandrights.CourseGroupManagementTest.class,//ok
 	org.olat.course.editor.PublishProcessTest.class,//ok