From 6cb3a1538f304c3c1005216c45aafe67296756b9 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Wed, 19 Nov 2014 20:07:00 +0100 Subject: [PATCH] OO-1254: generate of the certificate is asynchronous --- .../org/olat/course/_spring/courseContext.xml | 2 + .../AssessedIdentitiesTableDataModel.java | 6 + .../assessment/AssessmentMainController.java | 39 ++- .../olat/course/certificate/Certificate.java | 4 +- .../course/certificate/CertificateEvent.java | 48 +++ .../course/certificate/CertificateLight.java | 4 +- .../course/certificate/CertificateStatus.java | 32 ++ .../certificate/CertificatesManager.java | 4 + .../olat/course/certificate/EmailStatus.java | 33 ++ .../_spring/certificateContext.xml | 9 + .../_spring/certificateJms_activemq.xml | 17 ++ .../_spring/certificateJms_jndi.xml | 15 + ...ker.java => CertificatePDFFormWorker.java} | 6 +- .../manager/CertificatePhantomWorker.java | 29 +- .../manager/CertificatesManagerImpl.java | 287 ++++++++++++++---- .../model/AbstractCertificate.java | 201 ++++++++++++ .../certificate/model/CertificateImpl.java | 133 +------- .../model/CertificateLightImpl.java | 30 +- .../model/CertificateStandalone.java | 135 +------- .../certificate/model/JmsCertificateWork.java | 98 ++++++ ...ssessedIdentityCertificatesController.java | 43 ++- ...eAndEfficiencyStatementListController.java | 51 +++- .../ui/DownloadCertificateCellRenderer.java | 24 +- .../ui/_content/certificate_overview.html | 10 +- .../ui/_i18n/LocalStrings_de.properties | 3 +- .../database/mysql/alter_10_0_0_to_10_1_0.sql | 5 +- .../database/mysql/setupDatabase.sql | 5 +- .../oracle/alter_10_0_0_to_10_1_0.sql | 5 +- .../database/oracle/setupDatabase.sql | 7 +- .../postgresql/alter_10_0_0_to_10_1_0.sql | 5 +- .../database/postgresql/setupDatabase.sql | 5 +- .../resources/serviceconfig/olat.properties | 2 + .../static/themes/light/modules/_icons.scss | 1 + src/main/webapp/static/themes/light/theme.css | 2 +- .../themes/light/theme_ie_completions.css | 2 +- .../manager/CertificatesManagerTest.java | 167 +++++++++- .../org/olat/restapi/CertificationTest.java | 3 + 37 files changed, 1071 insertions(+), 401 deletions(-) create mode 100644 src/main/java/org/olat/course/certificate/CertificateEvent.java create mode 100644 src/main/java/org/olat/course/certificate/CertificateStatus.java create mode 100644 src/main/java/org/olat/course/certificate/EmailStatus.java create mode 100644 src/main/java/org/olat/course/certificate/_spring/certificateContext.xml create mode 100644 src/main/java/org/olat/course/certificate/_spring/certificateJms_activemq.xml create mode 100644 src/main/java/org/olat/course/certificate/_spring/certificateJms_jndi.xml rename src/main/java/org/olat/course/certificate/manager/{CertificateTemplateWorker.java => CertificatePDFFormWorker.java} (97%) create mode 100644 src/main/java/org/olat/course/certificate/model/AbstractCertificate.java create mode 100644 src/main/java/org/olat/course/certificate/model/JmsCertificateWork.java diff --git a/src/main/java/org/olat/course/_spring/courseContext.xml b/src/main/java/org/olat/course/_spring/courseContext.xml index 5f1c0a997bc..55642808310 100644 --- a/src/main/java/org/olat/course/_spring/courseContext.xml +++ b/src/main/java/org/olat/course/_spring/courseContext.xml @@ -11,6 +11,7 @@ <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/certificate/_spring/certificateContext.xml"/> <import resource="classpath:/org/olat/course/config/ui/courselayout/_spring/courseLayoutContext.xml"/> <import resource="classpath:/org/olat/course/db/_spring/coursedbContext.xml"/> <import resource="classpath:/org/olat/course/nodes/_spring/buildingblockContext.xml"/> @@ -22,6 +23,7 @@ <import resource="classpath:/org/olat/course/nodes/vitero/_spring/buildingblockContext.xml"/> <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"> diff --git a/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java b/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java index 81483e072e4..0e480ffc480 100644 --- a/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java +++ b/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java @@ -146,6 +146,12 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel<Asse public void setCertificates(Map<Long, CertificateLight> certificates) { this.certificates = certificates; } + + public void putCertificate(CertificateLight certificate) { + if(certificates != null) { + certificates.put(certificate.getIdentityKey(), certificate); + } + } /** * @see org.olat.core.gui.components.table.TableDataModel#getColumnCount() diff --git a/src/main/java/org/olat/course/assessment/AssessmentMainController.java b/src/main/java/org/olat/course/assessment/AssessmentMainController.java index 447930a9282..952a0eef86d 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentMainController.java +++ b/src/main/java/org/olat/course/assessment/AssessmentMainController.java @@ -28,7 +28,6 @@ package org.olat.course.assessment; import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -89,6 +88,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.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.tree.TreeHelper; @@ -97,6 +97,7 @@ 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.CertificateEvent; import org.olat.course.certificate.CertificateLight; import org.olat.course.certificate.CertificatesManager; import org.olat.course.certificate.ui.CertificatesWizardController; @@ -374,6 +375,8 @@ public class AssessmentMainController extends MainLayoutBasicController implemen // Register for assessment changed events course.getCourseEnvironment().getAssessmentManager().registerForAssessmentChangeEvents(this, ureq.getIdentity()); + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .registerFor(this, getIdentity(), CertificatesManager.ORES_CERTIFICATE_EVENT); } /** @@ -634,6 +637,21 @@ public class AssessmentMainController extends MainLayoutBasicController implemen if ((event instanceof AssessmentChangedEvent) && event.getCommand().equals(AssessmentChangedEvent.TYPE_SCORE_EVAL_CHANGED)) { AssessmentChangedEvent ace = (AssessmentChangedEvent) event; doUpdateLocalCacheAndUserModelFromAssessmentEvent(ace); + } else if(event instanceof CertificateEvent) { + CertificateEvent ce = (CertificateEvent)event; + if(re.getOlatResource().getKey().equals(ce.getResourceKey()) + && localUserCourseEnvironmentCache.containsKey(ce.getOwnerKey())) { + updateCertificate(ce.getCertificateKey()); + } + } + } + + private void updateCertificate(Long certificateKey) { + if (userListCtr != null + && userListCtr.getTableDataModel() instanceof AssessedIdentitiesTableDataModel) { + CertificateLight certificate = certificatesManager.getCertificateLightById(certificateKey); + ((AssessedIdentitiesTableDataModel)userListCtr + .getTableDataModel()).putCertificate(certificate); } } @@ -962,7 +980,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen } private Map<Long, CertificateLight> getCertificates(ICourse course) { - Map<Long, CertificateLight> certificates = new HashMap<>(); + Map<Long, CertificateLight> certificates = new ConcurrentHashMap<>(); OLATResource resource = course.getCourseEnvironment().getCourseGroupManager().getCourseResource(); List<CertificateLight> certificateList = certificatesManager.getCertificates(resource); for(CertificateLight certificate:certificateList) { @@ -981,18 +999,15 @@ public class AssessmentMainController extends MainLayoutBasicController implemen TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setTableEmptyMessage(translate("nodesoverview.nonodes")); tableConfig.setDownloadOffered(false); - //fxdiff VCRP-4: assessment overview with max score tableConfig.setSortingEnabled(true); tableConfig.setDisplayTableHeader(true); tableConfig.setDisplayRowCount(false); tableConfig.setPageingEnabled(false); - //fxdiff VCRP-4: assessment overview with max score tableConfig.setPreferencesOffered(true, "assessmentNodeList"); nodeListCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator()); listenTo(nodeListCtr); - //fxdiff VCRP-4: assessment overview with max score final IndentedNodeRenderer nodeRenderer = new IndentedNodeRenderer() { @Override public boolean isIndentationEnabled() { @@ -1235,10 +1250,10 @@ public class AssessmentMainController extends MainLayoutBasicController implemen } /** - * @param hasAssessableNodes true: show menu, false: hide menu + * @param assessableNodes true: show menu, false: hide menu * @return The tree model */ - private TreeModel buildTreeModel(boolean hasAssessableNodes, boolean certificate) { + private TreeModel buildTreeModel(boolean assessableNodes, boolean certificate) { GenericTreeNode root, gtn; GenericTreeModel gtm = new GenericTreeModel(); @@ -1249,7 +1264,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen gtm.setRootNode(root); // show real menu only when there are some assessable nodes - if (hasAssessableNodes) { + if (assessableNodes) { gtn = new GenericTreeNode(); gtn.setTitle(translate("menu.groupfocus")); gtn.setUserObject(CMD_GROUPFOCUS); @@ -1263,7 +1278,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen root.addChild(gtn); } - if (hasAssessableNodes || certificate) { + if (assessableNodes || certificate) { gtn = new GenericTreeNode(); gtn.setTitle(translate("menu.userfocus")); gtn.setUserObject(CMD_USERFOCUS); @@ -1271,7 +1286,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen root.addChild(gtn); } - if (hasAssessableNodes) { + if (assessableNodes) { gtn = new GenericTreeNode(); gtn.setTitle(translate("menu.bulkfocus")); gtn.setUserObject(CMD_BULKFOCUS); @@ -1293,6 +1308,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen /** * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) */ + @Override protected void doDispose() { // controllers disposed by BasicController userListCtr = null; @@ -1313,6 +1329,9 @@ public class AssessmentMainController extends MainLayoutBasicController implemen log.debug("Interrupting assessment cache preload in course::" + course.getResourceableId() + " while in doDispose()"); } } + + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .deregisterFor(this, CertificatesManager.ORES_CERTIFICATE_EVENT); } /** diff --git a/src/main/java/org/olat/course/certificate/Certificate.java b/src/main/java/org/olat/course/certificate/Certificate.java index df71bd342bb..1c8ccdd8b88 100644 --- a/src/main/java/org/olat/course/certificate/Certificate.java +++ b/src/main/java/org/olat/course/certificate/Certificate.java @@ -32,9 +32,11 @@ public interface Certificate extends CreateInfo { public Long getKey(); + public CertificateStatus getStatus(); + public String getUuid(); - public String getName(); + //public String getName(); public String getPath(); diff --git a/src/main/java/org/olat/course/certificate/CertificateEvent.java b/src/main/java/org/olat/course/certificate/CertificateEvent.java new file mode 100644 index 00000000000..bb12662d0de --- /dev/null +++ b/src/main/java/org/olat/course/certificate/CertificateEvent.java @@ -0,0 +1,48 @@ +package org.olat.course.certificate; + +import org.olat.core.util.event.MultiUserEvent; + +/** + * + * Initial date: 19.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CertificateEvent extends MultiUserEvent { + + private static final long serialVersionUID = -1854321062161208157L; + private Long ownerKey; + private Long resourceKey; + private Long certificateKey; + + public CertificateEvent(Long ownerKey, Long certificateKey, Long resourceKey) { + super("certificate-update"); + this.ownerKey = ownerKey; + this.resourceKey = resourceKey; + this.certificateKey = certificateKey; + } + + public Long getOwnerKey() { + return ownerKey; + } + + public void setOwnerKey(Long ownerKey) { + this.ownerKey = ownerKey; + } + + public Long getResourceKey() { + return resourceKey; + } + + public void setResourceKey(Long resourceKey) { + this.resourceKey = resourceKey; + } + + public Long getCertificateKey() { + return certificateKey; + } + + public void setCertificateKey(Long certificateKey) { + this.certificateKey = certificateKey; + } +} diff --git a/src/main/java/org/olat/course/certificate/CertificateLight.java b/src/main/java/org/olat/course/certificate/CertificateLight.java index 4cbe0602a80..6f29478365a 100644 --- a/src/main/java/org/olat/course/certificate/CertificateLight.java +++ b/src/main/java/org/olat/course/certificate/CertificateLight.java @@ -29,7 +29,9 @@ import org.olat.core.id.CreateInfo; */ public interface CertificateLight extends CreateInfo { - public String getName(); + public Long getKey(); + + public CertificateStatus getStatus(); public String getUuid(); diff --git a/src/main/java/org/olat/course/certificate/CertificateStatus.java b/src/main/java/org/olat/course/certificate/CertificateStatus.java new file mode 100644 index 00000000000..057441e925d --- /dev/null +++ b/src/main/java/org/olat/course/certificate/CertificateStatus.java @@ -0,0 +1,32 @@ +/** + * <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: 19.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum CertificateStatus { + pending, + ok, + error +} diff --git a/src/main/java/org/olat/course/certificate/CertificatesManager.java b/src/main/java/org/olat/course/certificate/CertificatesManager.java index 9eb6221a7b3..6517e51d060 100644 --- a/src/main/java/org/olat/course/certificate/CertificatesManager.java +++ b/src/main/java/org/olat/course/certificate/CertificatesManager.java @@ -28,6 +28,7 @@ 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.id.OLATResourceable; import org.olat.core.util.mail.MailerResult; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.VFSLeaf; @@ -45,6 +46,7 @@ import org.olat.resource.OLATResource; public interface CertificatesManager { public static final String ORES_CERTIFICATE = OresHelper.calculateTypeName(CertificatesManager.class); + public static final OLATResourceable ORES_CERTIFICATE_EVENT = OresHelper.createOLATResourceableInstance("Certificate", 0l); public boolean isHTMLTemplateAllowed(); @@ -93,6 +95,8 @@ public interface CertificatesManager { public Certificate getCertificateByUuid(String uuid); + public CertificateLight getCertificateLightById(Long key); + public VFSLeaf getCertificateLeaf(Certificate certificate); diff --git a/src/main/java/org/olat/course/certificate/EmailStatus.java b/src/main/java/org/olat/course/certificate/EmailStatus.java new file mode 100644 index 00000000000..62d5bba6d87 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/EmailStatus.java @@ -0,0 +1,33 @@ +/** + * <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: 19.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum EmailStatus { + + ok, + error, + +} diff --git a/src/main/java/org/olat/course/certificate/_spring/certificateContext.xml b/src/main/java/org/olat/course/certificate/_spring/certificateContext.xml new file mode 100644 index 00000000000..23558cb5126 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/_spring/certificateContext.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> + + <import resource="classpath:org/olat/course/certificate/_spring/certificateJms_${jms.provider}.xml" /> + +</beans> \ No newline at end of file diff --git a/src/main/java/org/olat/course/certificate/_spring/certificateJms_activemq.xml b/src/main/java/org/olat/course/certificate/_spring/certificateJms_activemq.xml new file mode 100644 index 00000000000..0df5e613c76 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/_spring/certificateJms_activemq.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> + + <!-- JMS Stuff for ActiveMQ --> + <bean id="certificateConnectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory" lazy-init="true"> + <property name="brokerURL" value="${certificate.broker.url}" /> + </bean> + + <bean id="certificateQueue" class="org.apache.activemq.command.ActiveMQQueue" lazy-init="true"> + <constructor-arg value="olat/certificateQueue" /> + </bean> + + +</beans> diff --git a/src/main/java/org/olat/course/certificate/_spring/certificateJms_jndi.xml b/src/main/java/org/olat/course/certificate/_spring/certificateJms_jndi.xml new file mode 100644 index 00000000000..a0e3b73e13b --- /dev/null +++ b/src/main/java/org/olat/course/certificate/_spring/certificateJms_jndi.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> + + <!-- JMS Stuff --> + <bean id="certificateConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true"> + <property name="jndiName"><value>${jms.broker.jndi}</value></property> + </bean> + <bean id="certificateQueue" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true"> + <property name="jndiName"><value>${certificate.broker.jndi}</value></property> + </bean> + +</beans> diff --git a/src/main/java/org/olat/course/certificate/manager/CertificateTemplateWorker.java b/src/main/java/org/olat/course/certificate/manager/CertificatePDFFormWorker.java similarity index 97% rename from src/main/java/org/olat/course/certificate/manager/CertificateTemplateWorker.java rename to src/main/java/org/olat/course/certificate/manager/CertificatePDFFormWorker.java index 7dae2a57b3d..667f93f54ff 100644 --- a/src/main/java/org/olat/course/certificate/manager/CertificateTemplateWorker.java +++ b/src/main/java/org/olat/course/certificate/manager/CertificatePDFFormWorker.java @@ -54,10 +54,10 @@ import org.olat.user.propertyhandlers.UserPropertyHandler; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class CertificateTemplateWorker { +public class CertificatePDFFormWorker { private static final OLog log = Tracing - .createLoggerFor(CertificateTemplateWorker.class); + .createLoggerFor(CertificatePDFFormWorker.class); private final Float score; private final Boolean passed; @@ -71,7 +71,7 @@ public class CertificateTemplateWorker { private final UserManager userManager; private final CertificatesManagerImpl certificatesManager; - public CertificateTemplateWorker(Identity identity, RepositoryEntry entry, + public CertificatePDFFormWorker(Identity identity, RepositoryEntry entry, Float score, Boolean passed, Date dateCertification, Date dateFirstCertification, Locale locale, UserManager userManager, CertificatesManagerImpl certificatesManager) { diff --git a/src/main/java/org/olat/course/certificate/manager/CertificatePhantomWorker.java b/src/main/java/org/olat/course/certificate/manager/CertificatePhantomWorker.java index 42e93909450..38ab7e69b0b 100644 --- a/src/main/java/org/olat/course/certificate/manager/CertificatePhantomWorker.java +++ b/src/main/java/org/olat/course/certificate/manager/CertificatePhantomWorker.java @@ -58,7 +58,7 @@ import org.olat.user.propertyhandlers.UserPropertyHandler; */ public class CertificatePhantomWorker { private static final OLog log = Tracing - .createLoggerFor(CertificateTemplateWorker.class); + .createLoggerFor(CertificatePDFFormWorker.class); private final Float score; private final Boolean passed; @@ -109,17 +109,15 @@ public class CertificatePhantomWorker { } CountDownLatch doneSignal = new CountDownLatch(1); - ProcessWorker worker = new ProcessWorker(cmds, doneSignal); + ProcessWorker worker = new ProcessWorker(cmds, htmlCertificateFile, doneSignal); worker.start(); try { - doneSignal.await(3000, TimeUnit.MILLISECONDS); + doneSignal.await(30000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("", e); } - htmlCertificateFile.delete(); - worker.destroyProcess(); return certificateFile; } @@ -131,6 +129,7 @@ public class CertificatePhantomWorker { try(Reader in = Files.newBufferedReader(templateFile.toPath(), Charset.forName("UTF-8")); Writer output = new FileWriter(htmlCertificate)) { result = certificatesManager.getVelocityEngine().evaluate(context, output, "mailTemplate", in); + output.flush(); } catch(Exception e) { log.error("", e); } @@ -216,11 +215,11 @@ public class CertificatePhantomWorker { cmds.add("--help"); CountDownLatch doneSignal = new CountDownLatch(1); - ProcessWorker worker = new ProcessWorker(cmds, doneSignal); + ProcessWorker worker = new ProcessWorker(cmds, null, doneSignal); worker.start(); try { - doneSignal.await(3000, TimeUnit.MILLISECONDS); + doneSignal.await(10000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("", e); } @@ -236,10 +235,12 @@ public class CertificatePhantomWorker { private int exitValue = -1; private final List<String> cmd; private final CountDownLatch doneSignal; + private final File htmlCertificateFile; - public ProcessWorker(List<String> cmd, CountDownLatch doneSignal) { + public ProcessWorker(List<String> cmd, File htmlCertificateFile, CountDownLatch doneSignal) { this.cmd = cmd; this.doneSignal = doneSignal; + this.htmlCertificateFile = htmlCertificateFile; } public void destroyProcess() { @@ -267,6 +268,10 @@ public class CertificatePhantomWorker { } catch (IOException e) { log.error ("Could not spawn convert sub process", e); destroyProcess(); + } finally { + if(htmlCertificateFile != null) { + htmlCertificateFile.delete(); + } } } @@ -298,8 +303,8 @@ public class CertificatePhantomWorker { } catch (IOException e) { // } - - if (log.isDebug()) { + + if(log.isDebug()) { log.debug("Error: " + errors.toString()); log.debug("Output: " + output.toString()); } @@ -307,10 +312,10 @@ public class CertificatePhantomWorker { try { exitValue = proc.waitFor(); if (exitValue != 0) { - log.warn("Problem with PhantomJS?"); + log.warn("Problem with PhantomJS? " + exitValue); } } catch (InterruptedException e) { - // + log.warn("Takes too long"); } } } diff --git a/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java b/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java index 345e5fe324f..28ed611d3bd 100644 --- a/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java +++ b/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java @@ -35,6 +35,18 @@ import java.util.Locale; import java.util.Properties; import java.util.UUID; +import javax.annotation.Resource; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.ObjectMessage; +import javax.jms.Queue; +import javax.jms.QueueConnection; +import javax.jms.QueueSender; +import javax.jms.QueueSession; +import javax.jms.Session; import javax.persistence.TypedQuery; import org.apache.velocity.app.VelocityEngine; @@ -54,8 +66,10 @@ import org.olat.core.id.Roles; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; +import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.mail.MailBundle; import org.olat.core.util.mail.MailManager; @@ -70,14 +84,18 @@ import org.olat.course.CorruptedCourseException; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.certificate.Certificate; +import org.olat.course.certificate.CertificateEvent; import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificateStatus; import org.olat.course.certificate.CertificateTemplate; import org.olat.course.certificate.CertificatesManager; +import org.olat.course.certificate.EmailStatus; import org.olat.course.certificate.RecertificationTimeUnit; import org.olat.course.certificate.model.CertificateImpl; import org.olat.course.certificate.model.CertificateInfos; import org.olat.course.certificate.model.CertificateStandalone; import org.olat.course.certificate.model.CertificateTemplateImpl; +import org.olat.course.certificate.model.JmsCertificateWork; import org.olat.course.certificate.ui.CertificateController; import org.olat.course.config.CourseConfig; import org.olat.course.nodes.CourseNode; @@ -88,10 +106,12 @@ 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.RepositoryService; import org.olat.repository.model.RepositoryEntrySecurity; import org.olat.resource.OLATResource; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -105,7 +125,7 @@ import uk.ac.reload.diva.util.ZipUtils; * */ @Service("certificatesManager") -public class CertificatesManagerImpl implements CertificatesManager, InitializingBean { +public class CertificatesManagerImpl implements CertificatesManager, MessageListener, InitializingBean, DisposableBean { private static final OLog log = Tracing.createLoggerFor(CertificatesManagerImpl.class); @@ -122,12 +142,23 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin @Autowired private BaseSecurity securityManager; @Autowired + private RepositoryService repositoryService; + @Autowired private RepositoryManager repositoryManager; @Autowired private NotificationsManager notificationsManager; @Autowired private BusinessGroupService businessGroupService; - + @Autowired + private CoordinatorManager coordinatorManager; + + @Resource(name="certificateQueue") + private Queue jmsQueue; + private Session certificateSession; + private MessageConsumer consumer; + @Resource(name="certificateConnectionFactory") + private ConnectionFactory connectionFactory; + private QueueConnection connection; private Boolean phantomAvailable; private FileStorage usersStorage; @@ -159,6 +190,51 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin } catch(Exception e) { log.error("", e); } + + //start the queues + try { + startQueue(); + } catch (JMSException e) { + log.error("", e); + } + } + + private void startQueue() throws JMSException { + connection = (QueueConnection)connectionFactory.createConnection(); + connection.start(); + log.info("springInit: JMS connection started with connectionFactory=" + connectionFactory); + + //listen to the queue only if indexing node + certificateSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + consumer = certificateSession.createConsumer(jmsQueue); + consumer.setMessageListener(this); + } + + @Override + public void destroy() throws Exception { + closeJms(); + } + + private void closeJms() { + if(consumer != null) { + try { + consumer.close(); + } catch (JMSException e) { + log.error("", e); + } + } + if(connection != null) { + try { + certificateSession.close(); + connection.close(); + } catch (JMSException e) { + log.error("", e); + } + } + } + + private Queue getJmsQueue() { + return jmsQueue; } @Override @@ -203,22 +279,40 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin @Override public VFSLeaf getCertificateLeaf(Certificate certificate) { VFSContainer cerContainer = getCertificateRootContainer(); - VFSItem cerItem = cerContainer.resolve(certificate.getPath()); + VFSItem cerItem = null; + if(StringHelper.containsNonWhitespace(certificate.getPath())) { + cerItem = cerContainer.resolve(certificate.getPath()); + } return cerItem instanceof VFSLeaf ? (VFSLeaf)cerItem : null; } private File getCertificateFile(Certificate certificate) { File file = getCertificateRoot(); - return new File(file, certificate.getPath()); + if(StringHelper.containsNonWhitespace(certificate.getPath())) { + return new File(file, certificate.getPath()); + } + return null; } @Override - public Certificate getCertificateById(Long key) { + public CertificateImpl 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) + List<CertificateImpl> certificates = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), CertificateImpl.class) + .setParameter("certificateKey", key) + .getResultList(); + return certificates.isEmpty() ? null : certificates.get(0); + } + + @Override + public CertificateLight getCertificateLightById(Long key) { + StringBuilder sb = new StringBuilder(); + sb.append("select cer from certificatelight cer") + .append(" where cer.key=:certificateKey"); + List<CertificateLight> certificates = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), CertificateLight.class) .setParameter("certificateKey", key) .getResultList(); return certificates.isEmpty() ? null : certificates.get(0); @@ -459,10 +553,15 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin if(creationDate != null) { certificate.setCreationDate(creationDate); } + RepositoryEntry entry = repositoryService.loadByResourceKey(resource.getKey()); + if(entry != null) { + certificate.setCourseTitle(entry.getDisplayname()); + } certificate.setLastModified(certificate.getCreationDate()); certificate.setIdentity(identity); certificate.setUuid(UUID.randomUUID().toString()); certificate.setLast(true); + certificate.setStatus(CertificateStatus.ok); String dir = usersStorage.generateDir(); try (InputStream in = Files.newInputStream(certificateFile.toPath())) { @@ -472,12 +571,11 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin File storedCertificateFile = new File(dirFile, "Certificate.pdf"); Files.copy(in, storedCertificateFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - certificate.setName(storedCertificateFile.getName()); certificate.setPath(dir + storedCertificateFile.getName()); - Date dateFirstCertification = getDateFirstCertification(identity, resource); + Date dateFirstCertification = getDateFirstCertification(identity, resource.getKey()); if (dateFirstCertification != null) { - removeLastFlag(identity, resource); + removeLastFlag(identity, resource.getKey()); } dbInstance.getCurrentEntityManager().persist(certificate); @@ -500,6 +598,7 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin certificate.setUuid(UUID.randomUUID().toString()); certificate.setLast(true); certificate.setCourseTitle(courseTitle); + certificate.setStatus(CertificateStatus.ok); String dir = usersStorage.generateDir(); try (InputStream in = Files.newInputStream(certificateFile.toPath())) { @@ -509,8 +608,12 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin File storedCertificateFile = new File(dirFile, "Certificate.pdf"); Files.copy(in, storedCertificateFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - certificate.setName(storedCertificateFile.getName()); certificate.setPath(dir + storedCertificateFile.getName()); + + Date dateFirstCertification = getDateFirstCertification(identity, resourceKey); + if (dateFirstCertification != null) { + removeLastFlag(identity, resourceKey); + } dbInstance.getCurrentEntityManager().persist(certificate); } catch (Exception e) { @@ -539,7 +642,7 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin File certificateFile; File dirFile = new File(WebappHelper.getTmpDir(), UUID.randomUUID().toString()); if(template.getPath().toLowerCase().endsWith("pdf")) { - CertificateTemplateWorker worker = new CertificateTemplateWorker(identity, entry, 2.0f, true, + CertificatePDFFormWorker worker = new CertificatePDFFormWorker(identity, entry, 2.0f, true, new Date(), new Date(), locale, userManager, this); certificateFile = worker.fill(template, dirFile); } else { @@ -563,13 +666,13 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin @Override public Certificate generateCertificate(CertificateInfos certificateInfos, RepositoryEntry entry, CertificateTemplate template, MailerResult result) { - Certificate certificate = persistCertificate(certificateInfos, entry, template, result); + Certificate certificate = persistCertificate(certificateInfos, entry, template); markPublisherNews(null, entry.getOlatResource()); return certificate; } private Certificate persistCertificate(CertificateInfos certificateInfos, RepositoryEntry entry, - CertificateTemplate template, MailerResult result) { + CertificateTemplate template) { OLATResource resource = entry.getOlatResource(); Identity identity = certificateInfos.getAssessedIdentity(); @@ -586,52 +689,125 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin certificate.setUuid(UUID.randomUUID().toString()); certificate.setLast(true); certificate.setCourseTitle(entry.getDisplayname()); + certificate.setStatus(CertificateStatus.pending); - String dir = usersStorage.generateDir(); - try { - File dirFile = new File(getCertificateRoot(), dir); - dirFile.mkdirs(); - - Float score = certificateInfos.getScore(); - Locale locale = I18nManager.getInstance().getLocaleOrDefault(null); - Boolean passed = certificateInfos.getPassed(); - Date dateCertification = certificate.getCreationDate(); - Date dateFirstCertification = getDateFirstCertification(identity, resource); - - File certificateFile; - if(template == null || template.getPath().toLowerCase().endsWith("pdf")) { - CertificateTemplateWorker worker = new CertificateTemplateWorker(identity, entry, score, passed, - dateCertification, dateFirstCertification, locale, userManager, this); - certificateFile = worker.fill(template, dirFile); - } else { - CertificatePhantomWorker worker = new CertificatePhantomWorker(identity, entry, score, passed, - dateCertification, dateFirstCertification, locale, userManager, this); - certificateFile = worker.fill(template, dirFile); - } + dbInstance.getCurrentEntityManager().persist(certificate); + dbInstance.commit(); + + //send message + sendJmsCertificateFile(certificate, template, certificateInfos.getScore(), certificateInfos.getPassed()); - certificate.setName(certificateFile.getName()); - certificate.setPath(dir + certificateFile.getName()); - - if(dateFirstCertification != null) { - //not the first certification, reset the last of the others certificates - removeLastFlag(identity, resource); + return certificate; + } + + protected VelocityEngine getVelocityEngine() { + return velocityEngine; + } + + private void sendJmsCertificateFile(Certificate certificate, CertificateTemplate template, Float score, Boolean passed) { + QueueSender sender; + QueueSession session = null; + try { + JmsCertificateWork workUnit = new JmsCertificateWork(); + workUnit.setCertificateKey(certificate.getKey()); + if(template != null) { + workUnit.setTemplateKey(template.getKey()); } + workUnit.setPassed(passed); + workUnit.setScore(score); - dbInstance.getCurrentEntityManager().persist(certificate); + session = connection.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE ); + ObjectMessage message = session.createObjectMessage(); + message.setObject(workUnit); - MailerResult newResult = sendCertificate(identity, entry, certificateFile); - if(result != null) { - result.append(newResult); + sender = session.createSender(getJmsQueue()); + sender.send( message ); + } catch (JMSException e) { + log.error("", e ); + } finally { + if(session != null) { + try { + session.close(); + } catch (JMSException e) { + //last hope + } } - - } catch (Exception e) { - log.error("", e); } - return certificate; } - protected VelocityEngine getVelocityEngine() { - return velocityEngine; + @Override + public void onMessage(Message message) { + if(message instanceof ObjectMessage) { + try { + ObjectMessage objMsg = (ObjectMessage)message; + JmsCertificateWork workUnit = (JmsCertificateWork)objMsg.getObject(); + doCertificate(workUnit); + message.acknowledge(); + } catch (JMSException e) { + log.error("", e); + } finally { + dbInstance.commitAndCloseSession(); + } + } + } + + private void doCertificate(JmsCertificateWork workUnit) { + CertificateImpl certificate = getCertificateById(workUnit.getCertificateKey()); + CertificateTemplate template = null; + if(workUnit.getTemplateKey() != null) { + template = getTemplateById(workUnit.getTemplateKey()); + } + OLATResource resource = certificate.getOlatResource(); + Identity identity = certificate.getIdentity(); + RepositoryEntry entry = repositoryService.loadByResourceKey(resource.getKey()); + + String dir = usersStorage.generateDir(); + File dirFile = new File(getCertificateRoot(), dir); + dirFile.mkdirs(); + + Float score = workUnit.getScore(); + Locale locale = I18nManager.getInstance().getLocaleOrDefault(null); + Boolean passed = workUnit.getPassed(); + Date dateCertification = certificate.getCreationDate(); + Date dateFirstCertification = getDateFirstCertification(identity, resource.getKey()); + + File certificateFile; + if(template == null || template.getPath().toLowerCase().endsWith("pdf")) { + CertificatePDFFormWorker worker = new CertificatePDFFormWorker(identity, entry, score, passed, + dateCertification, dateFirstCertification, locale, userManager, this); + certificateFile = worker.fill(template, dirFile); + if(certificateFile == null) { + certificate.setStatus(CertificateStatus.error); + } else { + certificate.setStatus(CertificateStatus.ok); + } + } else { + CertificatePhantomWorker worker = new CertificatePhantomWorker(identity, entry, score, passed, + dateCertification, dateFirstCertification, locale, userManager, this); + certificateFile = worker.fill(template, dirFile); + if(certificateFile == null) { + certificate.setStatus(CertificateStatus.error); + } else { + certificate.setStatus(CertificateStatus.ok); + } + } + + certificate.setPath(dir + certificateFile.getName()); + if(dateFirstCertification != null) { + //not the first certification, reset the last of the others certificates + removeLastFlag(identity, resource.getKey()); + } + MailerResult result = sendCertificate(identity, entry, certificateFile); + if(result.isSuccessful()) { + certificate.setEmailStatus(EmailStatus.ok); + } else { + certificate.setEmailStatus(EmailStatus.error); + } + dbInstance.getCurrentEntityManager().merge(certificate); + dbInstance.commit(); + + CertificateEvent event = new CertificateEvent(identity.getKey(), certificate.getKey(), resource.getKey()); + coordinatorManager.getCoordinator().getEventBus().fireEventToListenersOf(event, ORES_CERTIFICATE_EVENT); } private MailerResult sendCertificate(Identity to, RepositoryEntry entry, File certificateFile) { @@ -653,30 +829,31 @@ public class CertificatesManagerImpl implements CertificatesManager, Initializin return mailManager.sendMessage(bundle); } - private Date getDateFirstCertification(Identity identity, OLATResource resource) { + private Date getDateFirstCertification(Identity identity, Long resourceKey) { 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("resourceKey", resourceKey) .setParameter("identityKey", identity.getKey()) .setMaxResults(1) .getResultList(); return dates.isEmpty() ? null : dates.get(0); } - private void removeLastFlag(Identity identity, OLATResource resource) { + private void removeLastFlag(Identity identity, Long resourceKey) { 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("resourceKey", resourceKey) .setParameter("identityKey", identity.getKey()) .executeUpdate(); } + @Override public List<CertificateTemplate> getTemplates() { diff --git a/src/main/java/org/olat/course/certificate/model/AbstractCertificate.java b/src/main/java/org/olat/course/certificate/model/AbstractCertificate.java new file mode 100644 index 00000000000..40de69289b6 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/model/AbstractCertificate.java @@ -0,0 +1,201 @@ +/** + * <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.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +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.course.certificate.CertificateStatus; +import org.olat.course.certificate.EmailStatus; + +/** + * + * Initial date: 19.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@MappedSuperclass +public abstract class AbstractCertificate implements Certificate, Persistable { + + private static final long serialVersionUID = 2614314930775241116L; + + @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_status", nullable=false, insertable=true, updatable=true) + private String statusString; + @Column(name="c_email_status", nullable=true, insertable=true, updatable=true) + private String emailStatusString; + + @Column(name="c_uuid", nullable=false, insertable=true, updatable=false) + private String uuid; + + @Column(name="c_path", nullable=true, insertable=true, updatable=true) + private String path; + @Column(name="c_last", nullable=false, insertable=true, updatable=true) + private boolean last; + @Column(name="c_course_title", nullable=true, insertable=true, updatable=false) + private String courseTitle; + + @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; + } + + @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; + } + + public String getEmailStatusString() { + return emailStatusString; + } + + public void setEmailStatusString(String emailStatusString) { + this.emailStatusString = emailStatusString; + } + + public EmailStatus getEmailStatus() { + return emailStatusString == null ? null : EmailStatus.valueOf(emailStatusString); + } + + public void setEmailStatus(EmailStatus emailStatus) { + if(emailStatus == null) { + emailStatusString = null; + } else { + emailStatusString = emailStatus.name(); + } + } + + public String getStatusString() { + return statusString; + } + + public void setStatusString(String statusString) { + this.statusString = statusString; + } + + @Override + public CertificateStatus getStatus() { + return CertificateStatus.valueOf(statusString); + } + + public void setStatus(CertificateStatus status) { + this.statusString = status.name(); + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getCourseTitle() { + return courseTitle; + } + + public void setCourseTitle(String courseTitle) { + this.courseTitle = courseTitle; + } + + public boolean isLast() { + return last; + } + + public void setLast(boolean last) { + this.last = last; + } + + @Override + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + 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 equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} diff --git a/src/main/java/org/olat/course/certificate/model/CertificateImpl.java b/src/main/java/org/olat/course/certificate/model/CertificateImpl.java index b6fa18668a8..312aef7dbcf 100644 --- a/src/main/java/org/olat/course/certificate/model/CertificateImpl.java +++ b/src/main/java/org/olat/course/certificate/model/CertificateImpl.java @@ -19,24 +19,13 @@ */ 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; @@ -48,112 +37,18 @@ import org.olat.resource.OLATResourceImpl; */ @Entity(name="certificate") @Table(name="o_cer_certificate") -public class CertificateImpl implements Certificate, Persistable { +public class CertificateImpl extends AbstractCertificate { 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_course_title", nullable=true, insertable=true, updatable=false) - private String courseTitle; @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; - } - - @Override - public String getCourseTitle() { - return courseTitle; - } - - public void setCourseTitle(String courseTitle) { - this.courseTitle = courseTitle; - } - - 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; } @@ -170,35 +65,15 @@ public class CertificateImpl implements Certificate, Persistable { 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); + CertificateImpl cert = (CertificateImpl)obj; + return getKey() != null && getKey().equals(cert.getKey()); } 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/model/CertificateLightImpl.java b/src/main/java/org/olat/course/certificate/model/CertificateLightImpl.java index 4b0038d2c0b..62bd7d85573 100644 --- a/src/main/java/org/olat/course/certificate/model/CertificateLightImpl.java +++ b/src/main/java/org/olat/course/certificate/model/CertificateLightImpl.java @@ -32,6 +32,7 @@ import javax.persistence.TemporalType; import org.hibernate.annotations.GenericGenerator; import org.olat.core.id.Persistable; import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificateStatus; /** * @@ -54,11 +55,12 @@ public class CertificateLightImpl implements CertificateLight, Persistable { @Temporal(TemporalType.TIMESTAMP) @Column(name="creationdate", nullable=false, insertable=true, updatable=false) private Date creationDate; - + + @Column(name="c_status", nullable=false, insertable=true, updatable=true) + private String statusString; + @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_course_title", nullable=true, insertable=true, updatable=false) @@ -86,23 +88,27 @@ public class CertificateLightImpl implements CertificateLight, Persistable { public void setCreationDate(Date creationDate) { this.creationDate = creationDate; } + + public String getStatusString() { + return statusString; + } - @Override - public String getUuid() { - return uuid; + public void setStatusString(String statusString) { + this.statusString = statusString; } - public void setUuid(String uuid) { - this.uuid = uuid; + @Override + public CertificateStatus getStatus() { + return CertificateStatus.valueOf(statusString); } @Override - public String getName() { - return name; + public String getUuid() { + return uuid; } - public void setName(String name) { - this.name = name; + public void setUuid(String uuid) { + this.uuid = uuid; } @Override diff --git a/src/main/java/org/olat/course/certificate/model/CertificateStandalone.java b/src/main/java/org/olat/course/certificate/model/CertificateStandalone.java index 23c3b8d212b..2267fdd8c2d 100644 --- a/src/main/java/org/olat/course/certificate/model/CertificateStandalone.java +++ b/src/main/java/org/olat/course/certificate/model/CertificateStandalone.java @@ -19,24 +19,9 @@ */ 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; /** * @@ -46,108 +31,14 @@ import org.olat.course.certificate.Certificate; */ @Entity(name="certificatestandalone") @Table(name="o_cer_certificate") -public class CertificateStandalone implements Certificate, Persistable { +public class CertificateStandalone extends AbstractCertificate { 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_course_title", nullable=true, insertable=true, updatable=false) - private String courseTitle; @Column(name="c_archived_resource_id", nullable=false, insertable=true, updatable=false) private Long archivedResourceKey; - - @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; - } - - @Override - public String getCourseTitle() { - return courseTitle; - } - - public void setCourseTitle(String courseTitle) { - this.courseTitle = courseTitle; - } - - 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; } @@ -156,35 +47,15 @@ public class CertificateStandalone implements Certificate, Persistable { this.archivedResourceKey = archivedResourceKey; } - 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 CertificateStandalone) { - CertificateStandalone prefs = (CertificateStandalone)obj; - return key != null && key.equals(prefs.key); + CertificateStandalone cert = (CertificateStandalone)obj; + return getKey() != null && getKey().equals(cert.getKey()); } return false; } - - @Override - public boolean equalsByPersistableKey(Persistable persistable) { - return equals(persistable); - } - - } diff --git a/src/main/java/org/olat/course/certificate/model/JmsCertificateWork.java b/src/main/java/org/olat/course/certificate/model/JmsCertificateWork.java new file mode 100644 index 00000000000..ab143554034 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/model/JmsCertificateWork.java @@ -0,0 +1,98 @@ +/** + * <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.io.Serializable; + +/** + * + * Initial date: 19.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class JmsCertificateWork implements Serializable { + + private static final long serialVersionUID = 4462884019283948487L; + + private Float score; + private Boolean passed; + private Long templateKey; + private Long certificateKey; + + public JmsCertificateWork() { + // + } + + public JmsCertificateWork(Long certificateKey, Long templateKey, Float score, Boolean passed) { + this.score = score; + this.passed = passed; + this.templateKey = templateKey; + this.certificateKey = certificateKey; + } + + 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 Long getTemplateKey() { + return templateKey; + } + + public void setTemplateKey(Long templateKey) { + this.templateKey = templateKey; + } + + public Long getCertificateKey() { + return certificateKey; + } + + public void setCertificateKey(Long certificateKey) { + this.certificateKey = certificateKey; + } + + @Override + public int hashCode() { + return certificateKey == null ? 87580 : certificateKey.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof JmsCertificateWork) { + JmsCertificateWork work = (JmsCertificateWork)obj; + return certificateKey != null && certificateKey.equals(work.certificateKey); + } + return false; + } +} \ 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 index 3dcb9c612f6..15fc037d94a 100644 --- a/src/main/java/org/olat/course/certificate/ui/AssessedIdentityCertificatesController.java +++ b/src/main/java/org/olat/course/certificate/ui/AssessedIdentityCertificatesController.java @@ -35,10 +35,13 @@ import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.core.util.Formatter; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.mail.MailerResult; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.certificate.Certificate; +import org.olat.course.certificate.CertificateEvent; import org.olat.course.certificate.CertificateTemplate; import org.olat.course.certificate.CertificatesManager; import org.olat.course.certificate.model.CertificateInfos; @@ -56,7 +59,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class AssessedIdentityCertificatesController extends BasicController { +public class AssessedIdentityCertificatesController extends BasicController implements GenericEventListener { private Link generateLink; private final VelocityContainer mainVC; @@ -88,8 +91,28 @@ public class AssessedIdentityCertificatesController extends BasicController { } loadList(); putInitialPanel(mainVC); + + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .registerFor(this, getIdentity(), CertificatesManager.ORES_CERTIFICATE_EVENT); } + @Override + protected void doDispose() { + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .deregisterFor(this, CertificatesManager.ORES_CERTIFICATE_EVENT); + } + + @Override + public void event(Event event) { + if(event instanceof CertificateEvent) { + CertificateEvent ce = (CertificateEvent)event; + if(ce.getOwnerKey().equals(assessedUserCourseEnv.getIdentityEnvironment().getIdentity().getKey()) + && resource.getKey().equals(ce.getResourceKey())) { + loadList(); + } + } + } + private void loadList() { Identity assessedIdentity = assessedUserCourseEnv.getIdentityEnvironment().getIdentity(); List<Certificate> certificates = certificatesManager.getCertificates(assessedIdentity, resource); @@ -98,7 +121,7 @@ public class AssessedIdentityCertificatesController extends BasicController { for(Certificate certificate:certificates) { String displayName = formatter.formatDateAndTime(certificate.getCreationDate()); String url = DownloadCertificateCellRenderer.getUrl(certificate); - Links links = new Links(url, displayName); + Links links = new Links(url, displayName, certificate.getStatus().name()); certificatesLink.add(links); if(canDelete) { @@ -113,11 +136,6 @@ public class AssessedIdentityCertificatesController extends BasicController { } mainVC.contextPut("certificates", certificatesLink); } - - @Override - protected void doDispose() { - // - } @Override protected void event(UserRequest ureq, Component source, Event event) { @@ -195,23 +213,32 @@ public class AssessedIdentityCertificatesController extends BasicController { MailerResult result = new MailerResult(); certificatesManager.generateCertificate(certificateInfos, courseEntry, template, result); loadList(); + showInfo("msg.certificate.pending"); } public static class Links { private String url; private String name; + private String status; private Link delete; - public Links(String url, String name) { + public Links(String url, String name, String status) { this.url = url; this.name = name; + this.status = status; } + public String getUrl() { return url; } + public String getName() { return name; } + + public String getStatus() { + return status; + } public String getDeleteName() { return delete == null ? null : delete.getComponentName(); diff --git a/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListController.java b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListController.java index 322aeee5ba1..29bfee14aa7 100644 --- a/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListController.java +++ b/src/main/java/org/olat/course/certificate/ui/CertificateAndEfficiencyStatementListController.java @@ -53,6 +53,8 @@ import org.olat.core.gui.render.URLBuilder; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.util.Util; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.vfs.VFSContainer; import org.olat.course.CorruptedCourseException; import org.olat.course.assessment.EfficiencyStatement; @@ -61,6 +63,7 @@ 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.CertificateEvent; import org.olat.course.certificate.CertificateLight; import org.olat.course.certificate.CertificatesManager; import org.olat.course.certificate.ui.CertificateAndEfficiencyStatementListModel.CertificateAndEfficiencyStatement; @@ -80,13 +83,13 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class CertificateAndEfficiencyStatementListController extends FormBasicController implements BreadcrumbPanelAware { +public class CertificateAndEfficiencyStatementListController extends FormBasicController + implements BreadcrumbPanelAware, GenericEventListener { 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; @@ -118,6 +121,37 @@ public class CertificateAndEfficiencyStatementListController extends FormBasicCo this.assessedIdentity = assessedIdentity; initForm(ureq); + + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .registerFor(this, getIdentity(), CertificatesManager.ORES_CERTIFICATE_EVENT); + } + + @Override + protected void doDispose() { + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .deregisterFor(this, CertificatesManager.ORES_CERTIFICATE_EVENT); + } + + @Override + public void event(Event event) { + if(event instanceof CertificateEvent) { + CertificateEvent ce = (CertificateEvent)event; + if(getIdentity().getKey().equals(ce.getOwnerKey())) { + updateStatement(ce.getResourceKey(), ce.getCertificateKey()); + } + } + } + + private void updateStatement(Long resourceKey, Long certificateKey) { + List<CertificateAndEfficiencyStatement> statements = tableModel.getObjects(); + for(int i=statements.size(); i-->0; ) { + CertificateAndEfficiencyStatement statement = statements.get(i); + if(resourceKey.equals(statement.getResourceKey())) { + CertificateLight certificate = certificatesManager.getCertificateLightById(certificateKey); + statement.setCertificate(certificate); + break; + } + } } @Override @@ -130,11 +164,12 @@ public class CertificateAndEfficiencyStatementListController extends FormBasicCo 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 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(assessedIdentity)))); + tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.certificate.i18n(), Cols.certificate.ordinal(), + new DownloadCertificateCellRenderer(assessedIdentity))); tableColumnModel.addFlexiColumnModel(new StaticFlexiColumnModel("table.header.launchcourse", translate("table.header.launchcourse"), CMD_LAUNCH_COURSE)); tableColumnModel.addFlexiColumnModel(new StaticFlexiColumnModel("table.header.delete", @@ -174,6 +209,7 @@ public class CertificateAndEfficiencyStatementListController extends FormBasicCo CertificateAndEfficiencyStatement wrapper = resourceKeyToStatments.get(resourceKey); if(wrapper == null) { wrapper = new CertificateAndEfficiencyStatement(); + wrapper.setDisplayName(certificate.getCourseTitle()); resourceKeyToStatments.put(resourceKey, wrapper); statments.add(wrapper); } else { @@ -184,11 +220,6 @@ public class CertificateAndEfficiencyStatementListController extends FormBasicCo tableModel.setObjects(statments); } - - @Override - protected void doDispose() { - // - } @Override protected void formOK(UserRequest ureq) { diff --git a/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java b/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java index af909497b51..0fe9f0beb59 100644 --- a/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java +++ b/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java @@ -35,6 +35,7 @@ import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.course.certificate.Certificate; import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificateStatus; import org.olat.course.certificate.model.CertificateLightPack; import org.olat.user.UserManager; @@ -59,10 +60,10 @@ public class DownloadCertificateCellRenderer implements CustomCellRenderer, Flex @Override public void render(StringOutput sb, Renderer renderer, Object val, Locale locale, int alignment, String action) { if(val instanceof CertificateLight) { - generateUrl(sb, (CertificateLight)val, assessedIdentity, locale); + render(sb, (CertificateLight)val, assessedIdentity, locale); } else if(val instanceof CertificateLightPack) { CertificateLightPack pack = (CertificateLightPack)val; - generateUrl(sb, pack.getCertificate(), pack.getIdentity(), locale); + render(sb, pack.getCertificate(), pack.getIdentity(), locale); } } @@ -70,18 +71,25 @@ public class DownloadCertificateCellRenderer implements CustomCellRenderer, Flex 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, assessedIdentity, translator.getLocale()); + render(target, (CertificateLight)cellValue, assessedIdentity, translator.getLocale()); } else if(cellValue instanceof CertificateLightPack) { CertificateLightPack pack = (CertificateLightPack)cellValue; - generateUrl(target, pack.getCertificate(), pack.getIdentity(), translator.getLocale()); + render(target, pack.getCertificate(), pack.getIdentity(), translator.getLocale()); } } - private void generateUrl(StringOutput sb, CertificateLight certificate, Identity identity, Locale locale) { + + private void render(StringOutput sb, CertificateLight certificate, Identity identity, Locale locale) { String name = Formatter.getInstance(locale).formatDate(certificate.getCreationDate()); - sb.append("<a href='").append(getUrl(certificate, identity)) - .append("' target='_blank'><i class='o_icon o_filetype_pdf'> </i> ") - .append(name).append(".pdf").append("</a>"); + if(CertificateStatus.pending.equals(certificate.getStatus())) { + sb.append("<span><i class='o_icon o_icon_pending o_icon-spin'> </i>").append(name).append(".pdf").append("</span>"); + } else if(CertificateStatus.error.equals(certificate.getStatus())) { + sb.append("<span><i class='o_icon o_icon_error'> </i>").append(name).append(".pdf").append("</span>"); + } else { + sb.append("<a href='").append(getUrl(certificate, identity)) + .append("' target='_blank'><i class='o_icon o_filetype_pdf'> </i> ") + .append(name).append(".pdf").append("</a>"); + } } public static String getName(Certificate certificate) { 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 index 3f5f16fe139..28a85c6a2e0 100644 --- 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 @@ -7,7 +7,15 @@ #else <ul class="list-unstyled o_certificates"> #foreach($certificate in $certificates) - <li><a href="$certificate.url" target="_blank"><i class="o_icon o_filetype_pdf"> </i> $certificate.name</a> + <li> + #if($certificate.status == "pending") + <span><i class="o_icon o_icon_pending o_icon-spin"> </i> $certificate.name</span> + #elseif($certificate.status == "ok") + <a href="$certificate.url" target="_blank"><i class="o_icon o_filetype_pdf"> </i> $certificate.name</a> + #elseif($certificate.status == "error") + <span><i class="o_icon o_icon_error"> </i> $certificate.name</span> + #end + #if($r.available($certificate.getDeleteName())) $r.render($certificate.getDeleteName()) #end 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 index 079f71a16f0..999f3ef428a 100644 --- 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 @@ -56,4 +56,5 @@ confirm.delete.certificate.text=Wollen Sie wirklich den Zertifikat l confirm.certificate.deleted=Der Zertifikate von {0} wurde gelöscht. certificates.wizard.title=Zertifikaten erstellen certificates.wizard.select=Benutzer auswählen -certificates.wizard.overview=Ubersicht \ No newline at end of file +certificates.wizard.overview=Ubersicht +msg.certificate.pending=Der Zertifikat wird in ein paar Sekunden erstellt. \ No newline at end of file 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 d2fa87af94a..8c71525607c 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 @@ -15,9 +15,10 @@ create table o_cer_certificate ( id bigint not null, creationdate datetime not null, lastmodified datetime not null, + c_status varchar(16) not null default 'pending', + c_email_status varchar(16), c_uuid varchar(36) not null, - c_name varchar(256) not null, - c_path varchar(1024) not null, + c_path varchar(1024), c_last boolean not null default 1, c_course_title varchar(255), c_archived_resource_id bigint not null, diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 0708fb192a6..22d7a92a2b9 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1076,9 +1076,10 @@ create table o_cer_certificate ( id bigint not null, creationdate datetime not null, lastmodified datetime not null, + c_status varchar(16) not null default 'pending', + c_email_status varchar(16), c_uuid varchar(36) not null, - c_name varchar(256) not null, - c_path varchar(1024) not null, + c_path varchar(1024), c_last boolean not null default 1, c_course_title varchar(255), c_archived_resource_id bigint not null, diff --git a/src/main/resources/database/oracle/alter_10_0_0_to_10_1_0.sql b/src/main/resources/database/oracle/alter_10_0_0_to_10_1_0.sql index f5fb9ded1f5..26b280af8c6 100644 --- a/src/main/resources/database/oracle/alter_10_0_0_to_10_1_0.sql +++ b/src/main/resources/database/oracle/alter_10_0_0_to_10_1_0.sql @@ -14,9 +14,10 @@ create table o_cer_certificate ( id number(20) not null, creationdate date not null, lastmodified date not null, + c_status varchar2(16 char) default 'pending' not null, + c_email_status varchar2(16 char), c_uuid varchar2(36 char) not null, - c_name varchar2(256 char) not null, - c_path varchar2(1024 char) not null, + c_path varchar2(1024 char), c_last number default 1 not null, c_course_title varchar2(255 char), c_archived_resource_id number(20) not null, diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index ed3cbe29bb5..3996f9b2e23 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1110,11 +1110,12 @@ create table o_cer_certificate ( id number(20) not null, creationdate date not null, lastmodified date not null, + c_status varchar2(16 char) default 'pending' not null, + c_email_status varchar2(16 char), c_uuid varchar2(36 char) not null, - c_name varchar2(256 char) not null, - c_path varchar2(1024 char) not null, + c_path varchar2(1024 char), c_last number default 1 not null, - c_course_title varchar(255), + c_course_title varchar2(255 char), c_archived_resource_id number(20) not null, fk_olatresource number(20), fk_identity number(20) not null, 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 907efe8bf3b..e19b149045a 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 @@ -14,9 +14,10 @@ create table o_cer_certificate ( id int8 not null, creationdate timestamp not null, lastmodified timestamp not null, + c_status varchar(16) not null default 'pending', + c_email_status varchar(16), c_uuid varchar(36) not null, - c_name varchar(256) not null, - c_path varchar(1024) not null, + c_path varchar(1024), c_last bool not null default true, c_course_title varchar(255), c_archived_resource_id int8 not null, diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index e9dfccbc13e..e678c844926 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1077,9 +1077,10 @@ create table o_cer_certificate ( id int8 not null, creationdate timestamp not null, lastmodified timestamp not null, + c_status varchar(16) not null default 'pending', + c_email_status varchar(16), c_uuid varchar(36) not null, - c_name varchar(256) not null, - c_path varchar(1024) not null, + c_path varchar(1024), c_last bool not null default true, c_course_title varchar(255), c_archived_resource_id int8 not null, diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index e78a0e233c5..bf830d37744 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -745,6 +745,7 @@ cluster.singleton.services = enabled jms.broker.url=vm://embedded?broker.persistent=false search.broker.url=vm://embedded?broker.persistent=false index.broker.url=vm://embedded?broker.persistent=false +certificate.broker.url=vm://embedded?broker.persistent=false # Cluster (remote) jms.broker.url #jms.broker.url=failover:(tcp://localhost:61616?wireFormat.maxInactivityDuration=0) #search.broker.url=failover:(tcp://localhost:61616?wireFormat.maxInactivityDuration=0) @@ -755,6 +756,7 @@ sysbus.broker.jndi=topic/sysbus search.broker.jndi=queue/searchQueue index.broker.jndi=queue/indexQueue exam.broker.jndi=queue/examQueue +certificate.broker.jndi=queue/certificateQueue ##### #query cache config for singlevm/cluster diff --git a/src/main/webapp/static/themes/light/modules/_icons.scss b/src/main/webapp/static/themes/light/modules/_icons.scss index 45503931a8f..9258c7237c2 100644 --- a/src/main/webapp/static/themes/light/modules/_icons.scss +++ b/src/main/webapp/static/themes/light/modules/_icons.scss @@ -148,6 +148,7 @@ $fa-css-prefix: "o_icon" !default; .o_icon_pageing:before { content: $fa-var-ellipsis-h;} .o_icon_passed:before { content: $fa-var-check-circle;} .o_icon_password:before { content: $fa-var-lock;} +.o_icon_pending:before { content: $fa-var-spinner; } .o_icon_phone:before { content: $fa-var-phone;} .o_icon_post:before { content: $fa-var-comment-o; } .o_icon_preview:before { content: $fa-var-eye;} diff --git a/src/main/webapp/static/themes/light/theme.css b/src/main/webapp/static/themes/light/theme.css index 335a97624e6..cd5c98c7309 100644 --- a/src/main/webapp/static/themes/light/theme.css +++ b/src/main/webapp/static/themes/light/theme.css @@ -58,7 +58,7 @@ fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100% @media (max-width: 767px){.hidden-xs{display:none !important}}@media (min-width: 768px) and (max-width: 991px){.hidden-sm{display:none !important}}@media (min-width: 992px) and (max-width: 1199px){.hidden-md{display:none !important}}@media (min-width: 1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}} .visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}} .visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}} -@media print{.hidden-print{display:none !important}}body .modal{position:absolute;overflow:visible}body div.tooltip-inner{max-width:400px}body div.popover{max-width:450px}body .modal-body.alert{border-radius:0}body .progress{margin-bottom:0}.panel-body:nth-child(n+2){border-top:1px solid #ddd}.form-control-feedback{top:10px}.form-horizontal .has-feedback .form-control-feedback{top:10px}.btn.btn-primary.o_disabled{color:#fff !important}body .progress-bar[aria-valuenow="1"],body .progress-bar[aria-valuenow="2"]{min-width:1px}@font-face{font-family:'FontAwesome';src:url("../../font-awesome/fonts/fontawesome-webfont.eot?v=4.2.0");src:url("../../font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0") format("embedded-opentype"),url("../../font-awesome/fonts/fontawesome-webfont.woff?v=4.2.0") format("woff"),url("../../font-awesome/fonts/fontawesome-webfont.ttf?v=4.2.0") format("truetype"),url("../../font-awesome/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.o_icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.o_icon-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.o_icon-2x{font-size:2em}.o_icon-3x{font-size:3em}.o_icon-4x{font-size:4em}.o_icon-5x{font-size:5em}.o_icon-fw{width:1.28571em;text-align:center}.o_icon-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.o_icon-ul>li{position:relative}.o_icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.o_icon-li.o_icon-lg{left:-1.85714em}.o_icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.o_icon.pull-left{margin-right:.3em}.o_icon.pull-right{margin-left:.3em}.o_icon-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.o_icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.o_icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.o_icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.o_icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.o_icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .o_icon-rotate-90,:root .o_icon-rotate-180,:root .o_icon-rotate-270,:root .o_icon-flip-horizontal,:root .o_icon-flip-vertical{filter:none}.o_icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.o_icon-stack-1x,.o_icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.o_icon-stack-1x{line-height:inherit}.o_icon-stack-2x{font-size:2em}.o_icon-inverse{color:#fff}.o_icon_accessibility:before{content:"\f193"}.o_icon_actions:before{content:"\f085"}.o_icon_archive_tool:before{content:"\f019"}.o_icon_assessment_tool:before{content:"\f091"}.o_icon_attempt_limit:before{content:"\f021"}.o_icon_accept:before{content:"\f00c";color:#5cb85c}.o_icon_add:before{content:"\f055"}.o_icon_add_search:before{content:"\f00e"}.o_icon_audio:before{content:"\f028"}.o_icon_back:before{content:"\f053"}.o_icon_back_history:before{content:"\f1da"}.o_icon_bold:before{content:"\f032"}.o_icon_booking:before{content:"\f07a"}.o_icon_bookmark:before{content:"\f02e";color:#bc2d0c}.o_icon_bookmark_add:before{content:"\f097"}.o_icon_bookmark_header:before{content:"\f02e"}.o_icon_browse:before{content:"\f00e"}.o_icon_browsercheck:before{content:"\f164"}.o_icon_busy:before{content:"\f110"}.o_icon_calendar:before{content:"\f073"}.o_icon_calendar_enabled:before{content:"\f05d"}.o_icon_calendar_disabled:before{content:"\f10c"}.o_icon_calendar:before{content:"\f073"}.o_icon_caret:before{content:"\f0d7"}.o_icon_caret_right:before{content:"\f0da"}.o_icon_catalog:before{content:"\f0e8"}.o_icon_catalog_sub:before{content:"\f07b"}.o_icon_certificate:before{content:"\f0a3"}.o_icon_chat:before{content:"\f0e5"}.o_icon_check:before{content:"\f00c"}.o_icon_check_off:before{content:"\f096"}.o_icon_check_on:before{content:"\f046"}.o_icon_checkbox:before{content:"\f096"}.o_icon_checkbox_checked:before{content:"\f14a"}.o_icon_cleanup:before{content:"\f0f9"}.o_icon_close:before{content:"\f00d"}.o_icon_close_resource:before{content:"\f011"}.o_icon_close_tab:before{content:"\f00d"}.o_icon_close_tool:before{content:"\f00d"}.o_icon_close_tree:before{content:"\f0d7"}.o_icon_close_togglebox:before,.o_togglebox_wrapper .o_opener.o_in i:before{content:"\f0d7"}.o_icon_code:before{content:"\f121"}.o_icon_color_picker:before{content:"\f043"}.o_icon_copy:before{content:"\f0c5"}.o_icon_courseareas:before{content:"\f1db"}.o_icon_coursedb:before{content:"\f1c0"}.o_icon_courseeditor:before{content:"\f044"}.o_icon_coursefolder:before{content:"\f114"}.o_icon_courserun:before{content:"\f1b2"}.o_icon_comments:before{content:"\f086"}.o_icon_comments_none:before{content:"\f0e5"}.o_icon_content_popup:before{content:"\f08e"}.o_icon_correct_answer:before{content:"\f00c";color:#5cb85c}.o_icon_customize:before{content:"\f013"}.o_icon_delete_item:before{content:"\f1f8"}.o_icon_delete:before{content:"\f056";color:#A87E7E}.o_icon_details:before{content:"\f0eb"}.o_icon_dev:before{content:"\f188"}.o_icon_disabled:before{content:"\f10c"}.o_icon_download:before{content:"\f019"}.o_icon_edit:before{content:"\f044"}.o_icon_edit_file:before{content:"\f044"}.o_icon_edit_metadata:before{content:"\f013"}.o_icon_enabled:before{content:"\f111"}.o_icon_enlarge:before{content:"\f00e"}.o_icon_eportfolio_add:before{content:"\f12e"}.o_icon_eportfolio_link:before{content:"\f12e"}.o_icon_error:before{content:"\f06a";color:#d9534f}.o_icon_expenditure:before{content:"\f017"}.o_icon_export:before{content:"\f045"}.o_icon_external_link:before{content:"\f08e"}.o_icon_failed:before{content:"\f057"}.o_icon_filter:before{content:"\f0b0"}.o_icon_group:before,.o_BusinessGroup_icon:before{content:"\f0c0"}.o_icon_header:before{content:"\f1dc"}.o_icon_help:before{content:"\f059";cursor:help}.o_icon_home:before{content:"\f015"}.o_icon_impress:before{content:"\f05a"}.o_icon_important:before{content:"\f071";color:#f0ad4e}.o_icon_import:before{content:"\f093"}.o_icon_info:before{content:"\f05a";color:#5bc0de}.o_icon_info_msg:before{content:"\f06a";color:#d9534f}.o_icon_inline_editable:before{content:"\f044"}.o_icon_institution:before{content:"\f19c"}.o_icon_italic:before{content:"\f033"}.o_icon_landingpage:before{content:"\f140"}.o_icon_language:before{content:"\f0ac"}.o_icon_layout:before{content:"\f1c5"}.o_icon_link:before{content:"\f0c1"}.o_icon_link_extern:before{content:"\f08e"}.o_icon_list:before{content:"\f03a"}.o_icon_list_num :before{content:"\f0cb"}.o_icon_lifecycle:before{content:"\f073"}.o_icon_locked:before{content:"\f023"}.o_icon_login:before{content:"\f090"}.o_icon_logout:before{content:"\f08b"}.o_icon_mandatory:before{content:"\f069";color:#f0ad4e}.o_icon_managed:before{content:"\f079";color:#777}.o_icon_mail:before{content:"\f003"}.o_icon_math:before{content:"\f198"}.o_icon_membersmanagement:before{content:"\f0c0"}.o_icon_menuhandel:before{content:"\f0c9"}.o_icon_message:before{content:"\f0e0"}.o_icon_move:before{content:"\f047"}.o_icon_move_down:before{content:"\f103"}.o_icon_move_left:before{content:"\f100"}.o_icon_move_right:before{content:"\f101"}.o_icon_move_up:before{content:"\f102"}.o_icon_new:before{content:"\f069";color:#5cb85c}.o_icon_new_document:before{content:"\f15c"}.o_icon_new_folder:before{content:"\f07b"}.o_icon_news:before{content:"\f05a"}.o_icon_next:before{content:"\f138"}.o_icon_next_page:before{content:"\f101"}.o_icon_next_toolbar:before{content:"\f0da"}.o_icon_node_after:before{content:"\f175"}.o_icon_node_before:before{content:"\f176"}.o_icon_node_under:before{content:"\f112"}.o_icon_notes:before{content:"\f1e8"}.o_icon_notification:before{content:"\f09e"}.o_icon_open_tree:before{content:"\f0da"}.o_icon_open_togglebox:before,.o_togglebox_wrapper .o_opener i:before{content:"\f0da"}.o_icon_openolat:before,.o_icon_provider_olat:before{content:"\221E";font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:bold}.o_icon_options:before{content:"\f205"}.o_icon_pageing:before{content:"\f141"}.o_icon_passed:before{content:"\f058"}.o_icon_password:before{content:"\f023"}.o_icon_phone:before{content:"\f095"}.o_icon_post:before{content:"\f0e5"}.o_icon_preview:before{content:"\f06e"}.o_icon_previous:before{content:"\f137"}.o_icon_previous_page:before{content:"\f100"}.o_icon_previous_toolbar:before{content:"\f0d9"}.o_icon_print:before{content:"\f02f"}.o_icon_private:before{content:"\f02f"}.o_icon_provider_adfs:before{content:"\f17a"}.o_icon_provider_facebook:before{content:"\f09a"}.o_icon_provider_google:before{content:"\f0d5"}.o_icon_provider_guest:before{content:"\f1ae"}.o_icon_provider_ldap:before{content:"\f19c"}.o_icon_provider_linkedin:before{content:"\f0e1"}.o_icon_provider_oauth:before{content:"\f0c2"}.o_icon_provider_shibboleth:before{content:"\f19c"}.o_icon_provider_twitter:before{content:"\f099"}.o_icon_publish:before{content:"\f064"}.o_icon_qrcode:before{content:"\f029"}.o_icon_quickview:before{content:"\f06e"}.o_icon_radio_off:before{content:"\f10c"}.o_icon_radio_on:before{content:"\f05d"}.o_icon_rating_on:before,.o_rating .o_rating_items.o_enabled .o_icon:hover:before{content:"\f005"}.o_icon_rating_off:before{content:"\f006"}.o_icon_read:before{content:"\f10c"}.o_icon_readonly:before{content:"\f044";color:red}.o_icon_readwrite:before{content:"\f044"}.o_icon_recycle:before{content:"\f1b8"}.o_icon_refresh:before{content:"\f021"}.o_icon_reject:before{content:"\f00d";color:#d9534f}.o_icon_remove:before{content:"\f00d"}.o_icon_reply:before{content:"\f112"}.o_icon_reply_with_quote:before{content:"\f122"}.o_icon_response_feedback:before{content:"\f021"}.o_icon_review:before{content:"\f06e"}.o_icon_rss:before{content:"\f09e"}.o_icon_rss_unsubscribe:before{content:"\f09e";color:#996633}.o_icon_search:before{content:"\f002"}.o_icon_settings:before{content:"\f085"}.o_icon_share:before{content:"\f064"}.o_icon_show_more:before{content:"\f150"}.o_icon_show_less:before{content:"\f151"}.o_icon_spacer:before{content:"\f07e"}.o_icon_split:before{content:"\f127"}.o_icon_sort:before{content:"\f0dc"}.o_icon_sort_asc:before{content:"\f0de"}.o_icon_sort_desc:before{content:"\f0dd"}.o_icon_sort_menu:before{content:"\f160"}.o_icon_start:before{content:"\f054"}.o_icon_status_available:before{content:"\f111";color:#063}.o_icon_status_chat:before{content:"\f075"}.o_icon_status_dnd:before{content:"\f192";color:#cc3}.o_icon_status_unavailable:before{content:"\f05c";color:#963}.o_icon_statistics_tool:before{content:"\f080"}.o_icon_table:before{content:"\f0ce"}.o_icon_table_large:before{content:"\f009"}.o_icon_tags:before{content:"\f02c"}.o_icon_timelimit:before{content:"\f1e2"}.o_icon_toggle:before{content:"\f111"}.o_icon_to_read:before{content:"\f111"}.o_icon_tool:before{content:"\f013"}.o_icon_tools:before{content:"\f0ad"}.o_icon_top:before{content:"\f077"}.o_icon_translation_item:before{content:"\f1c9"}.o_icon_translation_package:before{content:"\f115"}.o_icon_user:before{content:"\f007"}.o_icon_user_vip:before{content:"\f19d"}.o_icon_user_anonymous:before{content:"\f128"}.o_icon_upload:before{content:"\f093"}.o_icon_version:before{content:"\f1da"}.o_icon_video:before{content:"\f008"}.o_icon_warn:before{content:"\f071";color:#f0ad4e}.o_icon_wizard:before{content:"\f0d0"}.o_CourseModule_icon:before,.o_course_icon:before{content:"\f1b2"}.o_EPStructuredMapTemplate_icon:before{content:"\f12e"}.o_FileResource-BLOG_icon:before{content:"\f0a1"}.o_FileResource-IMSCP_icon:before{content:"\f187"}.o_FileResource-PODCAST_icon:before{content:"\f03d"}.o_FileResource-SHAREDFOLDER:before{content:"\f08e"}.o_FileResource-SCORMCP_icon:before{content:"\f187"}.o_FileResource-SURVEY_icon:before{content:"\f11a"}.o_FileResource-TEST_icon:before{content:"\f044"}.o_FileResource-WIKI_icon:before{content:"\f0ac"}.o_FileResource-SHAREDFOLDER_icon:before{content:"\f115"}.o_FileResource-GLOSSARY_icon:before{content:"\f19d"}.o_FileResource-PDF_icon:before{content:"\f1c1"}.o_FileResource-XLS_icon:before{content:"\f1c3"}.o_FileResource-PPT_icon:before{content:"\f1c4"}.o_FileResource-DOC_icon:before{content:"\f1c2"}.o_FileResource-ANIM_icon:before{content:"\f1c8"}.o_FileResource-IMAGE_icon:before{content:"\f1c5"}.o_FileResource-SOUND_icon:before{content:"\f1c7"}.o_FileResource-MOVIE_icon:before{content:"\f1c8"}.o_FileResource-FILE_icon:before{content:"\f016"}.o_CourseModule_icon_closed:before{content:"\f05e"}.o_sp_icon:before{content:"\f0f6"}.o_st_icon:before{content:"\f1b3"}.o_tu_icon:before{content:"\f08e"}.o_bc_icon:before{content:"\f115"}.o_lti_icon:before{content:"\f08e"}.o_cp_icon:before{content:"\f187"}.o_cp_item:before{content:"\f0f6"}.o_scorm_icon:before{content:"\f187"}.o_en_icon:before{content:"\f090"}.o_fo_icon:before{content:"\f0e6"}.o_co_icon:before{content:"\f003"}.o_infomsg_icon:before{content:"\f05a"}.o_cal_icon:before{content:"\f073"}.o_wiki_icon:before{content:"\f0ac"}.o_podcast_icon:before{content:"\f03d"}.o_blog_icon:before{content:"\f0a1"}.o_ep_icon:before{content:"\f12e"}.o_iqtest_icon:before{content:"\f044"}.o_iqself_icon:before{content:"\f044"}.o_iqsurv_icon:before{content:"\f11a"}.o_ta_icon:before{content:"\f0ae"}.o_ms_icon:before{content:"\f087"}.o_dialog_icon:before{content:"\f0c5"}.o_projectbroker_icon:before{content:"\f10c"}.o_ll_icon:before{content:"\f0c1"}.o_den_icon:before{content:"\f133"}.o_cmembers_icon:before{content:"\f0c0"}.o_cl_icon:before{content:"\f046"}.o_vc_icon:before{content:"\f108"}.o_vitero_icon:before{content:"\f108"}.o_openmeetings_icon:before{content:"\f108"}.o_portlet_infomsg_icon:before{content:"\f05a"}.o_portlet_quickstart_icon:before{content:"\f1d9"}.o_portlet_bookmark_icon:before{content:"\f02e"}.o_portlet_groups_icon:before{content:"\f0c0"}.o_portlet_notes_icon:before{content:"\f1e8"}.o_portlet_noti_icon:before{content:"\f09e"}.o_portlet_eff_icon:before{content:"\f0a3"}.o_portlet_repository_student_icon:before{content:"\f1b3"}.o_portlet_repository_teacher_icon:before{content:"\f19d"}.o_portlet_iframe_icon:before{content:"\f005"}.o_portlet_sysinfo_icon:before{content:"\f0e4"}.o_portlet_dyk_icon:before{content:"\f0eb"}.o_portlet_infomessages_icon:before{content:"\f0e5"}.o_portlet_cal_icon:before{content:"\f073"}.o_portlet_institutions_icon:before{content:"\f19c"}.o_portlet_links_icon:before{content:"\f0c1"}.o_portlet_shibboleth_icon:before{content:"\f090"}.o_icon_qpool:before{content:"\f19c"}.o_icon_pool_private:before{content:"\f096"}.o_icon_pool_public:before{content:"\f046"}.o_icon_pool_my_items:before{content:"\f007"}.o_icon_pool_favorits:before{content:"\f02e"}.o_icon_pool_collection:before{content:"\f03a"}.o_icon_pool_pool:before{content:"\f1e1"}.o_icon_pool_share:before{content:"\f0c0"}.o_forum_message_icon:before{content:"\f0e5"}.o_calendar_icon:before{content:"\f073"}.o_forum_status_thread_icon:before{content:"\f0e6"}.o_forum_status_sticky_closed_icon:before{content:"\f05e"}.o_forum_status_sticky_icon:before{content:"\f086"}.o_forum_status_closed_icon:before{content:"\f05e";color:#a94442}.o_forum_status_opened_icon:before{content:"\f05e";color:#3c763d}.o_forum_status_hidden_icon:before{content:"\f070";color:#a94442}.o_forum_status_visible_icon:before{content:"\f06e";color:#3c763d}.o_mi_qpool_import:before{content:"\f1c0"}.o_mi_qtisection:before{content:"\f1b3"}.o_mi_qtisc:before{content:"\f192"}.o_mi_qtimc:before{content:"\f046"}.o_mi_qtikprim:before{content:"\f14a"}.o_mi_qtifib:before{content:"\f141"}.o_mi_qtiessay:before{content:"\f036"}.o_black_led:before{content:"\f111";color:#428bca}.o_green_led:before{content:"\f111";color:#5cb85c}.o_yellow_led:before{content:"\f111";color:#f0ad4e}.o_red_led:before{content:"\f111";color:#d9534f}.o_ac_token_icon:before{content:"\f084"}.o_ac_free_icon:before{content:"\f06b"}.o_ac_group_icon:before{content:"\f0c0"}.o_ac_membersonly_icon:before{content:"\f023"}.o_ac_paypal_icon:before{content:"\f1f4"}.o_ac_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_status_succes_icon:before{content:"\f00c";color:#5cb85c}.o_ac_status_waiting_icon:before{content:"\f017";color:#428bca}.o_ac_order_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_order_status_prepayment_icon:before{content:"\f0d6";color:#5bc0de}.o_ac_order_status_payed_icon:before{content:"\f00c";color:#5cb85c}.o_ac_order_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_order_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_order_status_warning_icon:before{content:"\f12a";color:#f0ad4e}.o_scorm_org:before{content:"\f187"}.o_scorm_item:before{content:"\f016"}.o_scorm_completed:before,.o_scorm_passed:before{content:"\f058"}.o_scorm_failed:before{content:"\f071"}.o_scorm_incomplete:before{content:"\f071"}.o_scorm_not_attempted:before{background:none}.o_midpub:before{content:"\f058"}.o_midwarn:before{content:"\f071"}.o_midlock:before{content:"\f023"}.o_miderr:before{content:"\f071"}.o_middel:before{content:"\f12d"}.o_filetype_file:before,.o_filetype_ico:before{content:"\f016"}.o_filetype_folder:before{content:"\f114"}.o_filetype_folder_open:before{content:"\f115"}.o_filetype_zip:before,.o_filetype_gz:before,.o_filetype_tar:before,.o_filetype_tgz:before{content:"\f1c6"}.o_filetype_css:before,.o_filetype_js:before,.o_filetype_java:before,.o_filetype_numbers:before,.o_filetype_ods:before,.o_filetype_xml:before,.o_filetype_xsl:before{content:"\f1c9"}.o_filetype_bat_icon:before,.o_filetype_bat:before,.o_filetype_exe:before,.o_filetype_app:before,.o_filetype_sh:before{content:"\f1c9"}.o_filetype_xls:before,.o_filetype_xlsx:before{content:"\f1c3"}.o_filetype_png:before,.o_filetype_tiff:before,.o_filetype_webp:before,.o_filetype_gif:before,.o_filetype_ico:before,.o_filetype_jpeg:before,.o_filetype_bmp:before,.o_filetype_odg:before,.o_filetype_eps:before,.o_filetype_jpg:before{content:"\f1c5"}.o_filetype_psd:before,.o_filetype_avi:before,.o_filetype_dvi:before,.o_filetype_mp4:before,.o_filetype_m4v:before,.o_filetype_webm:before,.o_filetype_ogg:before,.o_filetype_video:before,.o_filetype_mov:before,.o_filetype_mpeg:before,.o_filetype_mpg:before,.o_filetype_qt:before,.o_filetype_ra:before,.o_filetype_ram:before,.o_filetype_swf:before,.o_filetype_flv:before{content:"\f1c8"}.o_filetype_midi:before,.o_filetype_audio:before,.o_filetype_mp3:before,.o_filetype_m3u:before,.o_filetype_wav:before{content:"\f1c7"}.o_filetype_ps:before,.o_filetype_pdf:before{content:"\f1c1"}.o_filetype_key:before,.o_filetype_odp:before,.o_filetype_ppt:before,.o_filetype_pptx:before{content:"\f1c4"}.o_filetype_odf:before,.o_filetype_rtf:before,.o_filetype_readme:before,.o_filetype_README:before,.o_filetype_log:before,.o_filetype_txt:before,.o_filetype_htm:before,.o_filetype_html:before{content:"\f0f6"}.o_filetype_odt:before,.o_filetype_pages:before,.o_filetype_doc:before,.o_filetype_docx:before{content:"\f1c2"}.o_icon_share_social:before{content:"\f14d"}.o_icon_apple:before{content:"\f179"}.o_icon_facebook:before{content:"\f082"}.o_icon_twitter:before{content:"\f081"}.o_icon_google:before{content:"\f0d4"}.o_icon_delicious:before{content:"\f1a5"}.o_icon_digg:before{content:"\f1a6"}.o_icon_mailto:before{content:"\f199"}.o_icon_link:before{content:"\f0c1"}.o_icon_yahoo:before{content:"\f19e"}a.o_icon:hover,a.o_icon:focus{text-decoration:none}img.o_emoticons_angel{background:url(../light/images/emoticons/smiley-angel.png);width:16px;height:16px}img.o_emoticons_angry{background:url(../light/images/emoticons/smiley-mad.png);width:16px;height:16px}img.o_emoticons_blushing{background:url(../light/images/emoticons/smiley-red.png);width:16px;height:16px}img.o_emoticons_confused{background:url(../light/images/emoticons/smiley-confuse.png);width:16px;height:16px}img.o_emoticons_cool{background:url(../light/images/emoticons/smiley-cool.png);width:16px;height:16px}img.o_emoticons_cry{background:url(../light/images/emoticons/smiley-cry.png);width:16px;height:16px}img.o_emoticons_devil{background:url(../light/images/emoticons/smiley-evil.png);width:16px;height:16px}img.o_emoticons_grin{background:url(../light/images/emoticons/smiley-grin.png);width:16px;height:16px}img.o_emoticons_kiss{background:url(../light/images/emoticons/smiley-kiss.png);width:16px;height:16px}img.o_emoticons_ohoh{background:url(../light/images/emoticons/smiley-eek.png);width:16px;height:16px}img.o_emoticons_sad{background:url(../light/images/emoticons/smiley-sad.png);width:16px;height:16px}img.o_emoticons_sick{background:url(../light/images/emoticons/smiley-sad-blue.png);width:16px;height:16px}img.o_emoticons_smile{background:url(../light/images/emoticons/smiley.png);width:16px;height:16px}img.o_emoticons_tongue{background:url(../light/images/emoticons/smiley-razz.png);width:16px;height:16px}img.o_emoticons_ugly{background:url(../light/images/emoticons/smiley-money.png);width:16px;height:16px}img.o_emoticons_weird{background:url(../light/images/emoticons/smiley-nerd.png);width:16px;height:16px}img.o_emoticons_wink{background:url(../light/images/emoticons/smiley-wink.png);width:16px;height:16px}img.o_emoticons_worried{background:url(../light/images/emoticons/smiley-roll-blue.png);width:16px;height:16px}img.o_emoticons_up{background:url(../light/images/emoticons/thumb-up.png);width:16px;height:16px}img.o_emoticons_down{background:url(../light/images/emoticons/thumb.png);width:16px;height:16px}.o_block_bottom,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry,.o_header_with_buttons,.o_search_result{margin-bottom:1em}.o_block_top,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry{margin-top:1em}.o_block_large_bottom,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-bottom:2em}.o_block_large_top,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-top:2em}.o_scrollblock{overflow-x:auto;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_button_group{text-align:center}.o_button_group a,.o_button_group input,.o_button_group button,.o_button_group .btn-group{margin-right:5px;margin-bottom:0.5em}.o_button_group a:last-child,.o_button_group input:last-child,.o_button_group button:last-child,.o_button_group .btn-group:last-child{margin-right:0}.o_button_group .btn-group a,.o_button_group .btn-group input,.o_button_group .btn-group button{margin-right:0;margin-bottom:0}.o_button_group .dropdown-menu{text-align:left}.o_button_group_left{text-align:left}.o_button_group_right{text-align:right}.o_button_group_top{margin-top:0}.o_header_with_buttons:before,.o_header_with_buttons:after{content:" ";display:table}.o_header_with_buttons:after{clear:both}.o_header_with_buttons h1,.o_header_with_buttons h3,.o_header_with_buttons h4,.o_header_with_buttons .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_header_with_buttons h2{display:inline-block}.o_header_with_buttons .o_button_group{margin-top:10px;margin-bottom:0;float:right}.panel-heading.o_header_with_buttons{margin-bottom:0}.o_xsmall{font-size:12px}.o_small,.o_comments .o_comment_wrapper h5,.o_comments .o_comment_wrapper .o_comment,.o_bc_meta,.tooltip,.o_noti,.o_block_with_datecomp .o_meta,.o_togglebox_wrapper div.o_togglebox_content .o_hide,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label,.o_course_run .o_toc .o_entry{font-size:12px}.o_large{font-size:18px}.o_xlarge{font-size:18px}.o_disabled{color:#777 !important;cursor:default}.o_disabled:hover{color:#777 !important}.o_dimmed{opacity:0.4;filter:alpha(opacity=40)}.o_selected{font-weight:bold}.o_deleted{text-decoration:line-through}.o_clickable{cursor:pointer}.o_ochre{color:#c8a959}.o_blue{color:#12223F}.o_undecorated:hover,.o_undecorated:focus,.o_disabled:hover,#o_main_wrapper #o_toplink:hover,#o_footer_powered a:hover,#o_share a:hover,#o_share_social_container a:hover,.o_toolbar .o_tools_container a:hover,.o_button_toggle:hover,.o_im_message_group .o_im_from:hover,.o_noti .o_label:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover,.o_catalog .o_level .o_meta .o_title a:hover,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover,.o_repo_details .o_social .o_comments:hover,.o_login .o_login_register:hover,.o_disabled:focus,#o_main_wrapper #o_toplink:focus,#o_footer_powered a:focus,#o_share a:focus,#o_share_social_container a:focus,.o_toolbar .o_tools_container a:focus,.o_button_toggle:focus,.o_im_message_group .o_im_from:focus,.o_noti .o_label:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:focus,.o_catalog .o_level .o_meta .o_title a:focus,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:focus,.o_repo_details .o_social .o_comments:focus,.o_login .o_login_register:focus{text-decoration:none}.o_copy_code{overflow-x:auto;overflow-y:auto;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}.o_nowrap{white-space:nowrap}.o_titled_wrapper .o_content{margin-top:20px}.o_video{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_image{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_with_hyphens{-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}h1{color:#428bca}h2{color:#428bca}h3{color:#428bca}h4,.o_cal .fc-header-title h2{color:#428bca}h5{color:#428bca}h5{color:#428bca}fieldset legend{color:#333}html{position:relative;min-height:100%}body{min-height:100%;margin-bottom:80px}#o_main_wrapper{background:#fff;z-index:3}#o_main_wrapper #o_main_container{background:#fff}#o_main_wrapper #o_main_container #o_main_left{float:left;z-index:2;position:relative;background:#fff}#o_main_wrapper #o_main_container #o_main_left #o_main_left_content{padding:0 0 0 15px}#o_main_wrapper #o_main_container #o_main_left #o_main_left_toggle{position:absolute;display:none;right:0;top:70px;margin-right:-30px;font-size:25px;line-height:35px;text-align:center;width:30px;height:35px;z-index:3;border:1px solid #ddd;border-left:none;border-bottom-right-radius:4px;border-top-right-radius:4px;background-color:#fbfbfb;-webkit-box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);color:#428bca}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas{background:#fbfbfb;-webkit-box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);min-width:250px}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas #o_main_left_content{padding:0 0 0 0}#o_main_wrapper #o_main_container #o_main_right{float:right;z-index:2;position:relative;background:inherit}#o_main_wrapper #o_main_container #o_main_right #o_main_right_content{padding:0 15px 0 0}#o_main_wrapper #o_main_container #o_main_center{position:relative;z-index:1;background:inherit}#o_main_wrapper #o_main_container #o_main_center h2:first-child{margin-top:0}@media screen and (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center{margin-left:0 !important}}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:0 15px}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content #o_main_center_content_inner{padding-bottom:15px}#o_main_wrapper #o_toplink{position:absolute;bottom:0;right:15px;text-align:center;z-index:3}@media (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:15px}} +@media print{.hidden-print{display:none !important}}body .modal{position:absolute;overflow:visible}body div.tooltip-inner{max-width:400px}body div.popover{max-width:450px}body .modal-body.alert{border-radius:0}body .progress{margin-bottom:0}.panel-body:nth-child(n+2){border-top:1px solid #ddd}.form-control-feedback{top:10px}.form-horizontal .has-feedback .form-control-feedback{top:10px}.btn.btn-primary.o_disabled{color:#fff !important}body .progress-bar[aria-valuenow="1"],body .progress-bar[aria-valuenow="2"]{min-width:1px}@font-face{font-family:'FontAwesome';src:url("../../font-awesome/fonts/fontawesome-webfont.eot?v=4.2.0");src:url("../../font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0") format("embedded-opentype"),url("../../font-awesome/fonts/fontawesome-webfont.woff?v=4.2.0") format("woff"),url("../../font-awesome/fonts/fontawesome-webfont.ttf?v=4.2.0") format("truetype"),url("../../font-awesome/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.o_icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.o_icon-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.o_icon-2x{font-size:2em}.o_icon-3x{font-size:3em}.o_icon-4x{font-size:4em}.o_icon-5x{font-size:5em}.o_icon-fw{width:1.28571em;text-align:center}.o_icon-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.o_icon-ul>li{position:relative}.o_icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.o_icon-li.o_icon-lg{left:-1.85714em}.o_icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.o_icon.pull-left{margin-right:.3em}.o_icon.pull-right{margin-left:.3em}.o_icon-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.o_icon-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.o_icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.o_icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.o_icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.o_icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .o_icon-rotate-90,:root .o_icon-rotate-180,:root .o_icon-rotate-270,:root .o_icon-flip-horizontal,:root .o_icon-flip-vertical{filter:none}.o_icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.o_icon-stack-1x,.o_icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.o_icon-stack-1x{line-height:inherit}.o_icon-stack-2x{font-size:2em}.o_icon-inverse{color:#fff}.o_icon_accessibility:before{content:"\f193"}.o_icon_actions:before{content:"\f085"}.o_icon_archive_tool:before{content:"\f019"}.o_icon_assessment_tool:before{content:"\f091"}.o_icon_attempt_limit:before{content:"\f021"}.o_icon_accept:before{content:"\f00c";color:#5cb85c}.o_icon_add:before{content:"\f055"}.o_icon_add_search:before{content:"\f00e"}.o_icon_audio:before{content:"\f028"}.o_icon_back:before{content:"\f053"}.o_icon_back_history:before{content:"\f1da"}.o_icon_bold:before{content:"\f032"}.o_icon_booking:before{content:"\f07a"}.o_icon_bookmark:before{content:"\f02e";color:#bc2d0c}.o_icon_bookmark_add:before{content:"\f097"}.o_icon_bookmark_header:before{content:"\f02e"}.o_icon_browse:before{content:"\f00e"}.o_icon_browsercheck:before{content:"\f164"}.o_icon_busy:before{content:"\f110"}.o_icon_calendar:before{content:"\f073"}.o_icon_calendar_enabled:before{content:"\f05d"}.o_icon_calendar_disabled:before{content:"\f10c"}.o_icon_calendar:before{content:"\f073"}.o_icon_caret:before{content:"\f0d7"}.o_icon_caret_right:before{content:"\f0da"}.o_icon_catalog:before{content:"\f0e8"}.o_icon_catalog_sub:before{content:"\f07b"}.o_icon_certificate:before{content:"\f0a3"}.o_icon_chat:before{content:"\f0e5"}.o_icon_check:before{content:"\f00c"}.o_icon_check_off:before{content:"\f096"}.o_icon_check_on:before{content:"\f046"}.o_icon_checkbox:before{content:"\f096"}.o_icon_checkbox_checked:before{content:"\f14a"}.o_icon_cleanup:before{content:"\f0f9"}.o_icon_close:before{content:"\f00d"}.o_icon_close_resource:before{content:"\f011"}.o_icon_close_tab:before{content:"\f00d"}.o_icon_close_tool:before{content:"\f00d"}.o_icon_close_tree:before{content:"\f0d7"}.o_icon_close_togglebox:before,.o_togglebox_wrapper .o_opener.o_in i:before{content:"\f0d7"}.o_icon_code:before{content:"\f121"}.o_icon_color_picker:before{content:"\f043"}.o_icon_copy:before{content:"\f0c5"}.o_icon_courseareas:before{content:"\f1db"}.o_icon_coursedb:before{content:"\f1c0"}.o_icon_courseeditor:before{content:"\f044"}.o_icon_coursefolder:before{content:"\f114"}.o_icon_courserun:before{content:"\f1b2"}.o_icon_comments:before{content:"\f086"}.o_icon_comments_none:before{content:"\f0e5"}.o_icon_content_popup:before{content:"\f08e"}.o_icon_correct_answer:before{content:"\f00c";color:#5cb85c}.o_icon_customize:before{content:"\f013"}.o_icon_delete_item:before{content:"\f1f8"}.o_icon_delete:before{content:"\f056";color:#A87E7E}.o_icon_details:before{content:"\f0eb"}.o_icon_dev:before{content:"\f188"}.o_icon_disabled:before{content:"\f10c"}.o_icon_download:before{content:"\f019"}.o_icon_edit:before{content:"\f044"}.o_icon_edit_file:before{content:"\f044"}.o_icon_edit_metadata:before{content:"\f013"}.o_icon_enabled:before{content:"\f111"}.o_icon_enlarge:before{content:"\f00e"}.o_icon_eportfolio_add:before{content:"\f12e"}.o_icon_eportfolio_link:before{content:"\f12e"}.o_icon_error:before{content:"\f06a";color:#d9534f}.o_icon_expenditure:before{content:"\f017"}.o_icon_export:before{content:"\f045"}.o_icon_external_link:before{content:"\f08e"}.o_icon_failed:before{content:"\f057"}.o_icon_filter:before{content:"\f0b0"}.o_icon_group:before,.o_BusinessGroup_icon:before{content:"\f0c0"}.o_icon_header:before{content:"\f1dc"}.o_icon_help:before{content:"\f059";cursor:help}.o_icon_home:before{content:"\f015"}.o_icon_impress:before{content:"\f05a"}.o_icon_important:before{content:"\f071";color:#f0ad4e}.o_icon_import:before{content:"\f093"}.o_icon_info:before{content:"\f05a";color:#5bc0de}.o_icon_info_msg:before{content:"\f06a";color:#d9534f}.o_icon_inline_editable:before{content:"\f044"}.o_icon_institution:before{content:"\f19c"}.o_icon_italic:before{content:"\f033"}.o_icon_landingpage:before{content:"\f140"}.o_icon_language:before{content:"\f0ac"}.o_icon_layout:before{content:"\f1c5"}.o_icon_link:before{content:"\f0c1"}.o_icon_link_extern:before{content:"\f08e"}.o_icon_list:before{content:"\f03a"}.o_icon_list_num :before{content:"\f0cb"}.o_icon_lifecycle:before{content:"\f073"}.o_icon_locked:before{content:"\f023"}.o_icon_login:before{content:"\f090"}.o_icon_logout:before{content:"\f08b"}.o_icon_mandatory:before{content:"\f069";color:#f0ad4e}.o_icon_managed:before{content:"\f079";color:#777}.o_icon_mail:before{content:"\f003"}.o_icon_math:before{content:"\f198"}.o_icon_membersmanagement:before{content:"\f0c0"}.o_icon_menuhandel:before{content:"\f0c9"}.o_icon_message:before{content:"\f0e0"}.o_icon_move:before{content:"\f047"}.o_icon_move_down:before{content:"\f103"}.o_icon_move_left:before{content:"\f100"}.o_icon_move_right:before{content:"\f101"}.o_icon_move_up:before{content:"\f102"}.o_icon_new:before{content:"\f069";color:#5cb85c}.o_icon_new_document:before{content:"\f15c"}.o_icon_new_folder:before{content:"\f07b"}.o_icon_news:before{content:"\f05a"}.o_icon_next:before{content:"\f138"}.o_icon_next_page:before{content:"\f101"}.o_icon_next_toolbar:before{content:"\f0da"}.o_icon_node_after:before{content:"\f175"}.o_icon_node_before:before{content:"\f176"}.o_icon_node_under:before{content:"\f112"}.o_icon_notes:before{content:"\f1e8"}.o_icon_notification:before{content:"\f09e"}.o_icon_open_tree:before{content:"\f0da"}.o_icon_open_togglebox:before,.o_togglebox_wrapper .o_opener i:before{content:"\f0da"}.o_icon_openolat:before,.o_icon_provider_olat:before{content:"\221E";font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:bold}.o_icon_options:before{content:"\f205"}.o_icon_pageing:before{content:"\f141"}.o_icon_passed:before{content:"\f058"}.o_icon_password:before{content:"\f023"}.o_icon_pending:before{content:"\f110"}.o_icon_phone:before{content:"\f095"}.o_icon_post:before{content:"\f0e5"}.o_icon_preview:before{content:"\f06e"}.o_icon_previous:before{content:"\f137"}.o_icon_previous_page:before{content:"\f100"}.o_icon_previous_toolbar:before{content:"\f0d9"}.o_icon_print:before{content:"\f02f"}.o_icon_private:before{content:"\f02f"}.o_icon_provider_adfs:before{content:"\f17a"}.o_icon_provider_facebook:before{content:"\f09a"}.o_icon_provider_google:before{content:"\f0d5"}.o_icon_provider_guest:before{content:"\f1ae"}.o_icon_provider_ldap:before{content:"\f19c"}.o_icon_provider_linkedin:before{content:"\f0e1"}.o_icon_provider_oauth:before{content:"\f0c2"}.o_icon_provider_shibboleth:before{content:"\f19c"}.o_icon_provider_twitter:before{content:"\f099"}.o_icon_publish:before{content:"\f064"}.o_icon_qrcode:before{content:"\f029"}.o_icon_quickview:before{content:"\f06e"}.o_icon_radio_off:before{content:"\f10c"}.o_icon_radio_on:before{content:"\f05d"}.o_icon_rating_on:before,.o_rating .o_rating_items.o_enabled .o_icon:hover:before{content:"\f005"}.o_icon_rating_off:before{content:"\f006"}.o_icon_read:before{content:"\f10c"}.o_icon_readonly:before{content:"\f044";color:red}.o_icon_readwrite:before{content:"\f044"}.o_icon_recycle:before{content:"\f1b8"}.o_icon_refresh:before{content:"\f021"}.o_icon_reject:before{content:"\f00d";color:#d9534f}.o_icon_remove:before{content:"\f00d"}.o_icon_reply:before{content:"\f112"}.o_icon_reply_with_quote:before{content:"\f122"}.o_icon_response_feedback:before{content:"\f021"}.o_icon_review:before{content:"\f06e"}.o_icon_rss:before{content:"\f09e"}.o_icon_rss_unsubscribe:before{content:"\f09e";color:#996633}.o_icon_search:before{content:"\f002"}.o_icon_settings:before{content:"\f085"}.o_icon_share:before{content:"\f064"}.o_icon_show_more:before{content:"\f150"}.o_icon_show_less:before{content:"\f151"}.o_icon_spacer:before{content:"\f07e"}.o_icon_split:before{content:"\f127"}.o_icon_sort:before{content:"\f0dc"}.o_icon_sort_asc:before{content:"\f0de"}.o_icon_sort_desc:before{content:"\f0dd"}.o_icon_sort_menu:before{content:"\f160"}.o_icon_start:before{content:"\f054"}.o_icon_status_available:before{content:"\f111";color:#063}.o_icon_status_chat:before{content:"\f075"}.o_icon_status_dnd:before{content:"\f192";color:#cc3}.o_icon_status_unavailable:before{content:"\f05c";color:#963}.o_icon_statistics_tool:before{content:"\f080"}.o_icon_table:before{content:"\f0ce"}.o_icon_table_large:before{content:"\f009"}.o_icon_tags:before{content:"\f02c"}.o_icon_timelimit:before{content:"\f1e2"}.o_icon_toggle:before{content:"\f111"}.o_icon_to_read:before{content:"\f111"}.o_icon_tool:before{content:"\f013"}.o_icon_tools:before{content:"\f0ad"}.o_icon_top:before{content:"\f077"}.o_icon_translation_item:before{content:"\f1c9"}.o_icon_translation_package:before{content:"\f115"}.o_icon_user:before{content:"\f007"}.o_icon_user_vip:before{content:"\f19d"}.o_icon_user_anonymous:before{content:"\f128"}.o_icon_upload:before{content:"\f093"}.o_icon_version:before{content:"\f1da"}.o_icon_video:before{content:"\f008"}.o_icon_warn:before{content:"\f071";color:#f0ad4e}.o_icon_wizard:before{content:"\f0d0"}.o_CourseModule_icon:before,.o_course_icon:before{content:"\f1b2"}.o_EPStructuredMapTemplate_icon:before{content:"\f12e"}.o_FileResource-BLOG_icon:before{content:"\f0a1"}.o_FileResource-IMSCP_icon:before{content:"\f187"}.o_FileResource-PODCAST_icon:before{content:"\f03d"}.o_FileResource-SHAREDFOLDER:before{content:"\f08e"}.o_FileResource-SCORMCP_icon:before{content:"\f187"}.o_FileResource-SURVEY_icon:before{content:"\f11a"}.o_FileResource-TEST_icon:before{content:"\f044"}.o_FileResource-WIKI_icon:before{content:"\f0ac"}.o_FileResource-SHAREDFOLDER_icon:before{content:"\f115"}.o_FileResource-GLOSSARY_icon:before{content:"\f19d"}.o_FileResource-PDF_icon:before{content:"\f1c1"}.o_FileResource-XLS_icon:before{content:"\f1c3"}.o_FileResource-PPT_icon:before{content:"\f1c4"}.o_FileResource-DOC_icon:before{content:"\f1c2"}.o_FileResource-ANIM_icon:before{content:"\f1c8"}.o_FileResource-IMAGE_icon:before{content:"\f1c5"}.o_FileResource-SOUND_icon:before{content:"\f1c7"}.o_FileResource-MOVIE_icon:before{content:"\f1c8"}.o_FileResource-FILE_icon:before{content:"\f016"}.o_CourseModule_icon_closed:before{content:"\f05e"}.o_sp_icon:before{content:"\f0f6"}.o_st_icon:before{content:"\f1b3"}.o_tu_icon:before{content:"\f08e"}.o_bc_icon:before{content:"\f115"}.o_lti_icon:before{content:"\f08e"}.o_cp_icon:before{content:"\f187"}.o_cp_item:before{content:"\f0f6"}.o_scorm_icon:before{content:"\f187"}.o_en_icon:before{content:"\f090"}.o_fo_icon:before{content:"\f0e6"}.o_co_icon:before{content:"\f003"}.o_infomsg_icon:before{content:"\f05a"}.o_cal_icon:before{content:"\f073"}.o_wiki_icon:before{content:"\f0ac"}.o_podcast_icon:before{content:"\f03d"}.o_blog_icon:before{content:"\f0a1"}.o_ep_icon:before{content:"\f12e"}.o_iqtest_icon:before{content:"\f044"}.o_iqself_icon:before{content:"\f044"}.o_iqsurv_icon:before{content:"\f11a"}.o_ta_icon:before{content:"\f0ae"}.o_ms_icon:before{content:"\f087"}.o_dialog_icon:before{content:"\f0c5"}.o_projectbroker_icon:before{content:"\f10c"}.o_ll_icon:before{content:"\f0c1"}.o_den_icon:before{content:"\f133"}.o_cmembers_icon:before{content:"\f0c0"}.o_cl_icon:before{content:"\f046"}.o_vc_icon:before{content:"\f108"}.o_vitero_icon:before{content:"\f108"}.o_openmeetings_icon:before{content:"\f108"}.o_portlet_infomsg_icon:before{content:"\f05a"}.o_portlet_quickstart_icon:before{content:"\f1d9"}.o_portlet_bookmark_icon:before{content:"\f02e"}.o_portlet_groups_icon:before{content:"\f0c0"}.o_portlet_notes_icon:before{content:"\f1e8"}.o_portlet_noti_icon:before{content:"\f09e"}.o_portlet_eff_icon:before{content:"\f0a3"}.o_portlet_repository_student_icon:before{content:"\f1b3"}.o_portlet_repository_teacher_icon:before{content:"\f19d"}.o_portlet_iframe_icon:before{content:"\f005"}.o_portlet_sysinfo_icon:before{content:"\f0e4"}.o_portlet_dyk_icon:before{content:"\f0eb"}.o_portlet_infomessages_icon:before{content:"\f0e5"}.o_portlet_cal_icon:before{content:"\f073"}.o_portlet_institutions_icon:before{content:"\f19c"}.o_portlet_links_icon:before{content:"\f0c1"}.o_portlet_shibboleth_icon:before{content:"\f090"}.o_icon_qpool:before{content:"\f19c"}.o_icon_pool_private:before{content:"\f096"}.o_icon_pool_public:before{content:"\f046"}.o_icon_pool_my_items:before{content:"\f007"}.o_icon_pool_favorits:before{content:"\f02e"}.o_icon_pool_collection:before{content:"\f03a"}.o_icon_pool_pool:before{content:"\f1e1"}.o_icon_pool_share:before{content:"\f0c0"}.o_forum_message_icon:before{content:"\f0e5"}.o_calendar_icon:before{content:"\f073"}.o_forum_status_thread_icon:before{content:"\f0e6"}.o_forum_status_sticky_closed_icon:before{content:"\f05e"}.o_forum_status_sticky_icon:before{content:"\f086"}.o_forum_status_closed_icon:before{content:"\f05e";color:#a94442}.o_forum_status_opened_icon:before{content:"\f05e";color:#3c763d}.o_forum_status_hidden_icon:before{content:"\f070";color:#a94442}.o_forum_status_visible_icon:before{content:"\f06e";color:#3c763d}.o_mi_qpool_import:before{content:"\f1c0"}.o_mi_qtisection:before{content:"\f1b3"}.o_mi_qtisc:before{content:"\f192"}.o_mi_qtimc:before{content:"\f046"}.o_mi_qtikprim:before{content:"\f14a"}.o_mi_qtifib:before{content:"\f141"}.o_mi_qtiessay:before{content:"\f036"}.o_black_led:before{content:"\f111";color:#428bca}.o_green_led:before{content:"\f111";color:#5cb85c}.o_yellow_led:before{content:"\f111";color:#f0ad4e}.o_red_led:before{content:"\f111";color:#d9534f}.o_ac_token_icon:before{content:"\f084"}.o_ac_free_icon:before{content:"\f06b"}.o_ac_group_icon:before{content:"\f0c0"}.o_ac_membersonly_icon:before{content:"\f023"}.o_ac_paypal_icon:before{content:"\f1f4"}.o_ac_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_status_succes_icon:before{content:"\f00c";color:#5cb85c}.o_ac_status_waiting_icon:before{content:"\f017";color:#428bca}.o_ac_order_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_order_status_prepayment_icon:before{content:"\f0d6";color:#5bc0de}.o_ac_order_status_payed_icon:before{content:"\f00c";color:#5cb85c}.o_ac_order_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_order_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_order_status_warning_icon:before{content:"\f12a";color:#f0ad4e}.o_scorm_org:before{content:"\f187"}.o_scorm_item:before{content:"\f016"}.o_scorm_completed:before,.o_scorm_passed:before{content:"\f058"}.o_scorm_failed:before{content:"\f071"}.o_scorm_incomplete:before{content:"\f071"}.o_scorm_not_attempted:before{background:none}.o_midpub:before{content:"\f058"}.o_midwarn:before{content:"\f071"}.o_midlock:before{content:"\f023"}.o_miderr:before{content:"\f071"}.o_middel:before{content:"\f12d"}.o_filetype_file:before,.o_filetype_ico:before{content:"\f016"}.o_filetype_folder:before{content:"\f114"}.o_filetype_folder_open:before{content:"\f115"}.o_filetype_zip:before,.o_filetype_gz:before,.o_filetype_tar:before,.o_filetype_tgz:before{content:"\f1c6"}.o_filetype_css:before,.o_filetype_js:before,.o_filetype_java:before,.o_filetype_numbers:before,.o_filetype_ods:before,.o_filetype_xml:before,.o_filetype_xsl:before{content:"\f1c9"}.o_filetype_bat_icon:before,.o_filetype_bat:before,.o_filetype_exe:before,.o_filetype_app:before,.o_filetype_sh:before{content:"\f1c9"}.o_filetype_xls:before,.o_filetype_xlsx:before{content:"\f1c3"}.o_filetype_png:before,.o_filetype_tiff:before,.o_filetype_webp:before,.o_filetype_gif:before,.o_filetype_ico:before,.o_filetype_jpeg:before,.o_filetype_bmp:before,.o_filetype_odg:before,.o_filetype_eps:before,.o_filetype_jpg:before{content:"\f1c5"}.o_filetype_psd:before,.o_filetype_avi:before,.o_filetype_dvi:before,.o_filetype_mp4:before,.o_filetype_m4v:before,.o_filetype_webm:before,.o_filetype_ogg:before,.o_filetype_video:before,.o_filetype_mov:before,.o_filetype_mpeg:before,.o_filetype_mpg:before,.o_filetype_qt:before,.o_filetype_ra:before,.o_filetype_ram:before,.o_filetype_swf:before,.o_filetype_flv:before{content:"\f1c8"}.o_filetype_midi:before,.o_filetype_audio:before,.o_filetype_mp3:before,.o_filetype_m3u:before,.o_filetype_wav:before{content:"\f1c7"}.o_filetype_ps:before,.o_filetype_pdf:before{content:"\f1c1"}.o_filetype_key:before,.o_filetype_odp:before,.o_filetype_ppt:before,.o_filetype_pptx:before{content:"\f1c4"}.o_filetype_odf:before,.o_filetype_rtf:before,.o_filetype_readme:before,.o_filetype_README:before,.o_filetype_log:before,.o_filetype_txt:before,.o_filetype_htm:before,.o_filetype_html:before{content:"\f0f6"}.o_filetype_odt:before,.o_filetype_pages:before,.o_filetype_doc:before,.o_filetype_docx:before{content:"\f1c2"}.o_icon_share_social:before{content:"\f14d"}.o_icon_apple:before{content:"\f179"}.o_icon_facebook:before{content:"\f082"}.o_icon_twitter:before{content:"\f081"}.o_icon_google:before{content:"\f0d4"}.o_icon_delicious:before{content:"\f1a5"}.o_icon_digg:before{content:"\f1a6"}.o_icon_mailto:before{content:"\f199"}.o_icon_link:before{content:"\f0c1"}.o_icon_yahoo:before{content:"\f19e"}a.o_icon:hover,a.o_icon:focus{text-decoration:none}img.o_emoticons_angel{background:url(../light/images/emoticons/smiley-angel.png);width:16px;height:16px}img.o_emoticons_angry{background:url(../light/images/emoticons/smiley-mad.png);width:16px;height:16px}img.o_emoticons_blushing{background:url(../light/images/emoticons/smiley-red.png);width:16px;height:16px}img.o_emoticons_confused{background:url(../light/images/emoticons/smiley-confuse.png);width:16px;height:16px}img.o_emoticons_cool{background:url(../light/images/emoticons/smiley-cool.png);width:16px;height:16px}img.o_emoticons_cry{background:url(../light/images/emoticons/smiley-cry.png);width:16px;height:16px}img.o_emoticons_devil{background:url(../light/images/emoticons/smiley-evil.png);width:16px;height:16px}img.o_emoticons_grin{background:url(../light/images/emoticons/smiley-grin.png);width:16px;height:16px}img.o_emoticons_kiss{background:url(../light/images/emoticons/smiley-kiss.png);width:16px;height:16px}img.o_emoticons_ohoh{background:url(../light/images/emoticons/smiley-eek.png);width:16px;height:16px}img.o_emoticons_sad{background:url(../light/images/emoticons/smiley-sad.png);width:16px;height:16px}img.o_emoticons_sick{background:url(../light/images/emoticons/smiley-sad-blue.png);width:16px;height:16px}img.o_emoticons_smile{background:url(../light/images/emoticons/smiley.png);width:16px;height:16px}img.o_emoticons_tongue{background:url(../light/images/emoticons/smiley-razz.png);width:16px;height:16px}img.o_emoticons_ugly{background:url(../light/images/emoticons/smiley-money.png);width:16px;height:16px}img.o_emoticons_weird{background:url(../light/images/emoticons/smiley-nerd.png);width:16px;height:16px}img.o_emoticons_wink{background:url(../light/images/emoticons/smiley-wink.png);width:16px;height:16px}img.o_emoticons_worried{background:url(../light/images/emoticons/smiley-roll-blue.png);width:16px;height:16px}img.o_emoticons_up{background:url(../light/images/emoticons/thumb-up.png);width:16px;height:16px}img.o_emoticons_down{background:url(../light/images/emoticons/thumb.png);width:16px;height:16px}.o_block_bottom,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry,.o_header_with_buttons,.o_search_result{margin-bottom:1em}.o_block_top,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry{margin-top:1em}.o_block_large_bottom,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-bottom:2em}.o_block_large_top,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-top:2em}.o_scrollblock{overflow-x:auto;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_button_group{text-align:center}.o_button_group a,.o_button_group input,.o_button_group button,.o_button_group .btn-group{margin-right:5px;margin-bottom:0.5em}.o_button_group a:last-child,.o_button_group input:last-child,.o_button_group button:last-child,.o_button_group .btn-group:last-child{margin-right:0}.o_button_group .btn-group a,.o_button_group .btn-group input,.o_button_group .btn-group button{margin-right:0;margin-bottom:0}.o_button_group .dropdown-menu{text-align:left}.o_button_group_left{text-align:left}.o_button_group_right{text-align:right}.o_button_group_top{margin-top:0}.o_header_with_buttons:before,.o_header_with_buttons:after{content:" ";display:table}.o_header_with_buttons:after{clear:both}.o_header_with_buttons h1,.o_header_with_buttons h3,.o_header_with_buttons h4,.o_header_with_buttons .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_header_with_buttons h2{display:inline-block}.o_header_with_buttons .o_button_group{margin-top:10px;margin-bottom:0;float:right}.panel-heading.o_header_with_buttons{margin-bottom:0}.o_xsmall{font-size:12px}.o_small,.o_comments .o_comment_wrapper h5,.o_comments .o_comment_wrapper .o_comment,.o_bc_meta,.tooltip,.o_noti,.o_block_with_datecomp .o_meta,.o_togglebox_wrapper div.o_togglebox_content .o_hide,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label,.o_course_run .o_toc .o_entry{font-size:12px}.o_large{font-size:18px}.o_xlarge{font-size:18px}.o_disabled{color:#777 !important;cursor:default}.o_disabled:hover{color:#777 !important}.o_dimmed{opacity:0.4;filter:alpha(opacity=40)}.o_selected{font-weight:bold}.o_deleted{text-decoration:line-through}.o_clickable{cursor:pointer}.o_ochre{color:#c8a959}.o_blue{color:#12223F}.o_undecorated:hover,.o_undecorated:focus,.o_disabled:hover,#o_main_wrapper #o_toplink:hover,#o_footer_powered a:hover,#o_share a:hover,#o_share_social_container a:hover,.o_toolbar .o_tools_container a:hover,.o_button_toggle:hover,.o_im_message_group .o_im_from:hover,.o_noti .o_label:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover,.o_catalog .o_level .o_meta .o_title a:hover,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover,.o_repo_details .o_social .o_comments:hover,.o_login .o_login_register:hover,.o_disabled:focus,#o_main_wrapper #o_toplink:focus,#o_footer_powered a:focus,#o_share a:focus,#o_share_social_container a:focus,.o_toolbar .o_tools_container a:focus,.o_button_toggle:focus,.o_im_message_group .o_im_from:focus,.o_noti .o_label:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:focus,.o_catalog .o_level .o_meta .o_title a:focus,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:focus,.o_repo_details .o_social .o_comments:focus,.o_login .o_login_register:focus{text-decoration:none}.o_copy_code{overflow-x:auto;overflow-y:auto;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}.o_nowrap{white-space:nowrap}.o_titled_wrapper .o_content{margin-top:20px}.o_video{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_image{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_with_hyphens{-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}h1{color:#428bca}h2{color:#428bca}h3{color:#428bca}h4,.o_cal .fc-header-title h2{color:#428bca}h5{color:#428bca}h5{color:#428bca}fieldset legend{color:#333}html{position:relative;min-height:100%}body{min-height:100%;margin-bottom:80px}#o_main_wrapper{background:#fff;z-index:3}#o_main_wrapper #o_main_container{background:#fff}#o_main_wrapper #o_main_container #o_main_left{float:left;z-index:2;position:relative;background:#fff}#o_main_wrapper #o_main_container #o_main_left #o_main_left_content{padding:0 0 0 15px}#o_main_wrapper #o_main_container #o_main_left #o_main_left_toggle{position:absolute;display:none;right:0;top:70px;margin-right:-30px;font-size:25px;line-height:35px;text-align:center;width:30px;height:35px;z-index:3;border:1px solid #ddd;border-left:none;border-bottom-right-radius:4px;border-top-right-radius:4px;background-color:#fbfbfb;-webkit-box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);color:#428bca}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas{background:#fbfbfb;-webkit-box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);min-width:250px}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas #o_main_left_content{padding:0 0 0 0}#o_main_wrapper #o_main_container #o_main_right{float:right;z-index:2;position:relative;background:inherit}#o_main_wrapper #o_main_container #o_main_right #o_main_right_content{padding:0 15px 0 0}#o_main_wrapper #o_main_container #o_main_center{position:relative;z-index:1;background:inherit}#o_main_wrapper #o_main_container #o_main_center h2:first-child{margin-top:0}@media screen and (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center{margin-left:0 !important}}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:0 15px}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content #o_main_center_content_inner{padding-bottom:15px}#o_main_wrapper #o_toplink{position:absolute;bottom:0;right:15px;text-align:center;z-index:3}@media (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:15px}} #o_back_wrapper,#o_preview_wrapper{margin-top:10px}#o_back_wrapper.o_toolbar .o_breadcrumb .breadcrumb,#o_preview_wrapper.o_toolbar .o_breadcrumb .breadcrumb{font-size:14px}#o_footer_wrapper{position:absolute;bottom:0;width:100%;height:70px;overflow:hidden;background-color:#f5f5f5;color:#999;line-height:16px;font-size:12px}#o_footer_wrapper a{color:#999}#o_footer_wrapper a:hover{color:#000}#o_footer_container{position:relative;padding-top:10px;min-height:70px;background:#f5f5f5;z-index:1}#o_footer_user{position:absolute;left:15px;top:10px;z-index:1}#o_footer_user #o_counter{white-space:nowrap}#o_footer_user #o_username{white-space:nowrap;margin-right:1em}#o_footer_version{position:absolute;right:15px;top:10px;text-align:right;z-index:1}@media (max-width: 767px){#o_footer_version{padding-top:10px;text-align:left}} #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{max-width:150px}.o_navbar .o_navbar_tabs li a{padding-right:30px}.o_navbar .o_navbar_tabs li a:first-child span{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.o_navbar .o_navbar_tabs .o_icon-fw{position:absolute;top:15px;left:0.5em;padding-top:3px;width:1em;height:1em;display:none}.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 li>a>span{display:none}@media (min-width: 768px){.o_navbar .o_navbar_tools li.o_portrait>a>span{display:inline}}.o_navbar #o_navbar_tools_personal .o_navbar_tool a,.o_navbar #o_navbar_tools_permanent .o_navbar_tool a{padding-right:5px}.o_navbar #o_navbar_tools_personal #o_navbar_my_menu a,.o_navbar #o_navbar_tools_permanent #o_navbar_my_menu a{padding-left:45px}.o_navbar #o_navbar_tools_personal #o_navbar_my_menu .o_portrait,.o_navbar #o_navbar_tools_permanent #o_navbar_my_menu .o_portrait{position:absolute;left:7px;top:10px}.o_navbar #o_navbar_tools_personal .o_logout,.o_navbar #o_navbar_tools_permanent .o_logout{color:#d9534f}.o_navbar.o_navbar-offcanvas .o_navbar_tab_close{top:10px;right:10px}.o_navbar.o_navbar-offcanvas .o_navbar-right a{padding:3px 20px;color:#777}.o_navbar.o_navbar-offcanvas .o_navbar-right a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-right a:focus{color:#fff;background-color:transparent}.o_navbar.o_navbar-offcanvas .o_navbar-right a.o_logout{color:#d9534f}.o_navbar.o_navbar-offcanvas .o_navbar-right a.o_logout:hover,.o_navbar.o_navbar-offcanvas .o_navbar-right a.o_logout:focus{color:#c9302c}.o_navbar.o_navbar-offcanvas .o_navbar-right a .o_icon-lg{font-size:1.0em}.o_navbar.o_navbar-offcanvas .o_navbar-right .divider{height:1px;margin:9px 0;overflow:hidden;background-color:none}.o_navbar.o_navbar-offcanvas .o_navbar-right .dropdown-header{padding-left:15px}.o_navbar.o_navbar-offcanvas .o_navbar-right .dropdown-toggle{display:none}.o_navbar.o_navbar-offcanvas .o_navbar-right .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-right .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_dropdown_tab{position:relative}.o_dropdown_tab>a:first-child{padding-right:30px}.o_dropdown_tab>a:first-child .o_icon-fw{display:none;position:absolute;top:0;left:10px;padding-top:3px;line-height:20px}.o_dropdown_tab>a:first-child span{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.o_dropdown_tab .o_navbar_tab_close{position:absolute;top:0px;right:10px;padding-left:0px;padding-right:0px;color:#d9534f;background-color:inherit}.o_dropdown_tab .o_navbar_tab_close:focus,.o_dropdown_tab .o_navbar_tab_close:hover{color:#c9302c;background-color:inherit}#o_navbar_more .dropdown-menu .divider:last-child{display:none}@media (min-width: 768px){#o_navbar_more .dropdown-menu{max-width:300px}}@media (max-width: 767px){#o_navbar_more>li{position:inherit}#o_navbar_more .dropdown-menu{left:0px;right:0px}#o_navbar_more .dropdown-menu a,#o_navbar_more .dropdown-menu i{line-height:30px}#o_navbar_more .dropdown-menu .o_navbar_tab_close{line-height:inherit}}.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}} diff --git a/src/main/webapp/static/themes/light/theme_ie_completions.css b/src/main/webapp/static/themes/light/theme_ie_completions.css index ec656ed117d..62bbde0338c 100644 --- a/src/main/webapp/static/themes/light/theme_ie_completions.css +++ b/src/main/webapp/static/themes/light/theme_ie_completions.css @@ -1,3 +1,3 @@ -.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_lifecycle.o_active:hover{color:#2b542c}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_desc{margin-top:0.5em}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_bookmark{position:absolute;top:-1px;right:15px}@media (min-width: 768px) and (max-width: 991px){.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:80px;margin:0 120px}}@media (max-width: 767px){.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:50px;margin:0 0 0 75px;padding:0 0 0 1em}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title{line-height:50px}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a{border-right:37px solid transparent;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_author,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_bookmark,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_lifecycle,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_desc{display:none}}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_go_xs{position:absolute;top:0;right:0;padding:0 1em;height:50px;width:37px;line-height:50px;color:#fff;background-color:#428bca}.o_coursetable.o_rendertype_classic .o_rating_explanation{display:none}.o_coursetable.o_rendertype_classic .o_start,.o_coursetable.o_rendertype_classic .o_book{white-space:nowrap}.o_coursetable.o_rendertype_classic .o_repoentry_type{color:#555}.o_coursetable.o_rendertype_classic .o_repoentry_ac{color:#555}.o_catalog .o_level{position:relative;margin-bottom:10px;padding:0;border-top:1px solid #428bca;border-bottom:1px solid #428bca}.o_catalog .o_level .o_visual{height:180px}.o_catalog .o_level .o_meta{position:relative;min-height:180px;height:180px;overflow:hidden;margin:0 0 0 180px;padding:1em 0.5em 0.5em 2em}.o_catalog .o_level .o_meta .o_title{margin:0}.o_catalog .o_level .o_meta .o_title a{display:block;color:#428bca}.o_catalog .o_level .o_meta .o_title a:hover{color:#3071a9}.o_catalog .o_level .o_meta .o_desc{padding:1em 0 0.5em 0}@media (min-width: 768px) and (max-width: 991px){.o_catalog .o_level .o_visual{height:120px}.o_catalog .o_level .o_meta{min-height:120px;height:120px;margin:0 0 0 120px}}@media (max-width: 767px){.o_catalog .o_level .o_visual{height:75px}.o_catalog .o_level .o_meta{min-height:75px;height:75px;margin:0 0 0 75px;padding:0 0 0 1em}.o_catalog .o_level .o_meta .o_title{line-height:75px}.o_catalog .o_level .o_meta .o_desc{display:none}}.o_catalog .o_sublevels{position:relative;margin-bottom:20px}.o_catalog .o_sublevels:before,.o_catalog .o_sublevels:after{content:" ";display:table}.o_catalog .o_sublevels:after{clear:both}.o_catalog .o_sublevels .o_sublevel{position:relative;float:left;margin:0 20px 20px 0;width:180px}.o_catalog .o_sublevels .o_sublevel:last-child{margin-right:0}.o_catalog .o_sublevels .o_sublevel .o_visual{border:1px solid #428bca;position:relative;height:180px}.o_catalog .o_sublevels .o_sublevel .o_meta{position:absolute;left:0;bottom:0;width:100%;border:1px solid #428bca;border-top:0;background-color:rgba(255,255,255,0.8)}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title{margin:0;text-align:center;line-height:2em;height:2em;width:100%;overflow:hidden}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a{display:block;color:#428bca}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover{color:#3071a9}@media (min-width: 768px) and (max-width: 991px){.o_catalog .o_sublevels .o_sublevel{width:120px;margin:0 10px 10px 0}.o_catalog .o_sublevels .o_sublevel .o_visual{height:120px}.o_catalog .o_sublevels .o_sublevel .o_title{font-size:90%}}@media (max-width: 767px){.o_catalog .o_sublevels .o_sublevel{width:120px;margin:0 1px 1px 0}.o_catalog .o_sublevels .o_sublevel .o_visual{height:120px;width:120px}.o_catalog .o_sublevels .o_sublevel .o_title{font-size:90%}} +.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_lifecycle.o_active{color:#3c763d}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_lifecycle.o_active:hover{color:#2b542c}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_desc{margin-top:0.5em}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_bookmark{position:absolute;top:-1px;right:15px}@media (min-width: 768px) and (max-width: 991px){.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:80px;margin:0 120px}}@media (max-width: 767px){.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:50px;margin:0 0 0 75px;padding:0 0 0 1em}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title{line-height:50px}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a{border-right:37px solid transparent;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_author,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_bookmark,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_lifecycle,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_desc{display:none}}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_go_xs{position:absolute;top:0;right:0;padding:0 1em;height:50px;width:37px;line-height:50px;color:#fff;background-color:#428bca}.o_coursetable.o_rendertype_classic .o_rating_explanation{display:none}.o_coursetable.o_rendertype_classic .o_start,.o_coursetable.o_rendertype_classic .o_book{white-space:nowrap}.o_coursetable.o_rendertype_classic .o_repoentry_type{color:#555}.o_coursetable.o_rendertype_classic .o_repoentry_ac{color:#555}.o_catalog .o_level{position:relative;margin-bottom:10px;padding:0;border-top:1px solid #428bca;border-bottom:1px solid #428bca}.o_catalog .o_level .o_visual{height:180px}.o_catalog .o_level .o_meta{position:relative;min-height:180px;height:180px;overflow:hidden;margin:0 0 0 180px;padding:1em 0.5em 0.5em 2em}.o_catalog .o_level .o_meta .o_title{margin:0}.o_catalog .o_level .o_meta .o_title a{display:block;color:#428bca}.o_catalog .o_level .o_meta .o_title a:hover{color:#3071a9}.o_catalog .o_level .o_meta .o_desc{padding:1em 0 0.5em 0}@media (min-width: 768px) and (max-width: 991px){.o_catalog .o_level .o_visual{height:120px}.o_catalog .o_level .o_meta{min-height:120px;height:120px;margin:0 0 0 120px}}@media (max-width: 767px){.o_catalog .o_level .o_visual{height:75px}.o_catalog .o_level .o_meta{min-height:75px;height:75px;margin:0 0 0 75px;padding:0 0 0 1em}.o_catalog .o_level .o_meta .o_title{line-height:75px}.o_catalog .o_level .o_meta .o_desc{display:none}}.o_catalog .o_sublevels{position:relative;margin-bottom:20px}.o_catalog .o_sublevels:before,.o_catalog .o_sublevels:after{content:" ";display:table}.o_catalog .o_sublevels:after{clear:both}.o_catalog .o_sublevels .o_sublevel{position:relative;float:left;margin:0 20px 20px 0;width:180px}.o_catalog .o_sublevels .o_sublevel:last-child{margin-right:0}.o_catalog .o_sublevels .o_sublevel .o_visual{border:1px solid #428bca;position:relative;height:180px}.o_catalog .o_sublevels .o_sublevel .o_meta{position:absolute;left:0;bottom:0;width:100%;border:1px solid #428bca;border-top:0;background-color:rgba(255,255,255,0.8)}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title{margin:0;text-align:center;line-height:2em;height:2em;width:100%;overflow:hidden}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a{display:block;color:#428bca}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover{color:#3071a9}@media (min-width: 768px) and (max-width: 991px){.o_catalog .o_sublevels .o_sublevel{width:120px;margin:0 10px 10px 0}.o_catalog .o_sublevels .o_sublevel .o_visual{height:120px}.o_catalog .o_sublevels .o_sublevel .o_title{font-size:90%}}@media (max-width: 767px){.o_catalog .o_sublevels .o_sublevel{width:120px;margin:0 1px 1px 0}.o_catalog .o_sublevels .o_sublevel .o_visual{height:120px;width:120px}.o_catalog .o_sublevels .o_sublevel .o_title{font-size:90%}} .o_repo_details{position:relative}.o_repo_details .o_lead .o_media{float:right;margin-left:2em;margin-bottom:2em}.o_repo_details .o_lead h1 i{display:none}.o_repo_details .o_overview i{margin-right:0.5em}.o_repo_details .o_overview div{margin-bottom:0.25em}.o_repo_details .o_start,.o_repo_details .o_book{margin:2em 0}.o_repo_details .o_social:before,.o_repo_details .o_social:after{content:" ";display:table}.o_repo_details .o_social:after{clear:both}.o_repo_details .o_social .o_rating_wrapper{float:left}.o_repo_details .o_social .o_comments{margin-left:1em}@media (max-width: 767px){.o_repo_details .o_lead p{font-size:16px}.o_repo_details .o_lead .o_media{margin-top:0}}@media (max-width: 613px){.o_repo_details .o_subcolumn{width:100%}} .badge.o_midpub{background-color:#3c763d}.badge.o_midwarn{background-color:#8a6d3b}.badge.o_midlock{background-color:#31708f}.badge.o_miderr{background-color:#a94442}.badge.o_middel{background-color:#777}.o_course_editor_legend .badge{font-size:80%}.o_course_editor_legend .badge:before{content:none}.o_passed{color:#3c763d;font-weight:bold}.o_passed a:hover{color:#2b542c}.o_passed th{color:#333}.o_failed{color:#a94442;font-weight:bold}.o_failed a:hover{color:#66512c}.o_failed th{color:#333}.o_unknown{color:#8a6d3b;font-weight:bold}.o_unknown a:hover{color:#66512c}.o_unknown th{color:#333}.o_noinfo{color:#777}.o_course_run .o_toc .o_entry .o_shorttitle{border-bottom:1px solid #777}.o_course_run .o_toc .o_entry .o_displaytitle{margin-top:5px;color:#777}.o_course_run .o_toc .o_entry .o_objectives{margin-top:10px;font-style:italic}.o_course_run.o_titled_wrapper>h2 i{display:none}.o_st_peekview ul li{margin-bottom:0.5em}.o_cl_line{margin-bottom:10px;padding-bottom:5px}.o_cl_line.o_even{background-color:#f9f9f9}.o_cmembers .o_cmember{margin:12px 0}.o_cmembers .o_cmember .o_portrait{margin-right:10px}.o_cmembers .o_cmember .o_portrait img{width:50px;height:50px}.o_cmembers .o_cmember .o_cmember_info_wrapper{line-height:50px}.o_cmembers .o_cmember .o_cmember_info_wrapper .o_mail{margin-left:6px}table.table.o_qti_item_kprim>thead>tr>th,table.table.o_qti_item_kprim>tbody>tr>td{border:none}td.o_qti_item_kprim_input,th.o_qti_item_kprim_input{text-align:center}td.o_qti_item_kprim_input .radio,th.o_qti_item_kprim_input .radio{display:inline}div.o_qti_menu_section{margin-top:10px}div.o_qti_menu_item a,div.o_qti_menu_section a{text-decoration:none}div.o_qti_menu_item{padding:.1em}div.o_qti_menu_item_active{padding:.1em;font-weight:bold}div.o_qti_item_itemfeedback{background-color:#ffffff;border-color:#000000}div.o_qti_item_choice_option_flow{display:inline-block;padding:.5em;border:1px solid transparent}.d3chart .bar_green{fill:#5cb85c}.d3chart .bar_red{fill:#d9534f}.d3chart .bar_grey{fill:lightgrey}div.o_qti_statistics ul{list-style-type:none;padding:0;margin:0;font-size:90%}div.o_qti_statistics ul strong{font-weight:normal}div.o_qti_statistics ul li{padding-left:48px;margin-left:0;margin-bottom:10px}div.o_qti_statistics ul li.o_qti_statistics-ncorrect:before{font-size:125%;content:'\2A2F\00A0\00A0'}div.o_qti_statistics ul li.o_qti_statistics-correct:before{font-size:125%;content:'\2713\00A0\00A0'}div.o_qti_statistics ul li.o_qti_statistics-kplus:before{font-size:125%;content:'\2713\00A0\2A2F\00A0\00A0'}div.o_qti_statistics ul li.o_qti_statistics-kminus:before{font-size:125%;content:'\2A2F\00A0\2713\00A0\00A0'}div.o_qti_statistics ul li img{vertical-align:top}div.o_qti_statistics table.o_qti_statistics_figures tr{float:left}div.o_qti_statistics table.o_qti_statistics_figures tr:nth-child(2n+1){clear:left;padding-right:20px}div.o_qti_statistics table.o_qti_statistics_figures td{width:200px;padding-left:0}div.o_qti_statistics table.o_qti_statistics_figures td+td{width:100px}div.o_qti_statistics .o_qti_statistics_answer{background:#F5F5F5;padding:1px 2px;width:90%}div.o_qti_statistics div.o_qti_statistics_legend{padding-top:10px;width:470px;border:1px solid #ddd;border-radius:4px}div.o_qti_statistics div.o_qti_statistics_legend ul li .bar_green{background-color:#9dd53a}div.o_qti_statistics div.o_qti_statistics_legend ul li .bar_red{background-color:#f85032}div.o_qti_statistics div.o_qti_statistics_legend ul li .bar_grey{background-color:lightgrey}div.o_qti_metadatas .panel-body{border-top:none}.o_qti_menu_item_attempts:after,.o_qti_menu_item_attempts_marked:after{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.o_qti_menu_item_attempts:after{content:"\f11d"}.o_qti_menu_item_attempts_marked:after{content:"\f024";color:#428bca}.onyx_iframe{width:100%;height:100%;border:none;min-height:60em}.o_qti_print div.o_qti_statistics{width:680px}@media print{div.o_qti_statistics{width:680px}}#o_dev_tool #o_dev_tool_mode{width:1em;height:1em;float:left;border:1px solid #000;margin-right:5px}a.o_dev{position:absolute;left:0;top:0;z-index:4000;background:#f0ad4e;border:1px solid #d59645;border-top:none;border-left:none;border-radius:0 0 4px 0;color:#fff}a.o_dev:hover{color:#d9534f}.o_dev_w{margin:1px}.o_dev_w .o_dev_h{color:#000;font-size:8px;line-height:10px;margin:0}.o_dev_w .o_dev_h span{background:#f4c37d;border:1px solid #f0ad4e;border-bottom:0}.o_dev_w .o_dev_c{position:relative;border:1px dotted #eee}.o_dev_w .o_dev_c .o_dev_i{position:absolute;top:0px;left:24px;height:auto;width:auto;padding:5px;border:1px solid black;display:none;margin:0px;z-index:999;font-size:11px;background-color:#BBF}.o_dev_w.o_dev_m>.o_dev_c{border:1px solid #f0ad4e;margin:0px;background-color:#f8e9d4}.o_wikimod_nav .o_noti{margin:0}.o_wikimod_editform_wrapper{margin-top:30px}.o_wiki-file-deleted{text-decoration:line-through}.o_ep_icon_map:before{content:"\f0b1"}.o_ep_icon_collection:before{content:"\f0b1"}.o_ep_icon_page:before{content:"\f016"}.o_ep_icon_struct:before{content:"\f1b3"}.o_ep_icon_liveblog:before{content:"\f0a1"}.o_artefact_closed:before{content:"\f023"}.o_portfolio_toc .o_ep_link{float:right;margin-right:0px}.o_portfolio_toc .o_ep_commentlink{float:right;margin-right:10%}.o_portfolio_toc li.level1{font-size:1.2em;margin:1.2em 0 0.2em 0;border-bottom:1px solid #ddd}.o_portfolio_toc li.level2{padding-left:20px;font-size:1.1em;border-bottom:1px dotted #ddd}.o_portfolio_toc li.level3{padding-left:40px}.o_eportfolio_page .o_eportfolio_structure>h5{border-bottom:1px solid #ddd;margin-top:1.2em}.o_eportfolio_maps .panel{font-family:'Century Gothic', 'Apple Gothic', sans-serif;box-shadow:3px 3px 4px rgba(20,20,20,0.4)}.o_eportfolio_maps .panel-heading{padding:5px 10px}.o_eportfolio_maps h4,.o_eportfolio_maps .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps h2{padding:11px 15px;background:rgba(255,255,230,0.7) none;border-radius:6px}.o_eportfolio_maps .table>tbody>tr>td{border-top:none}.o_eportfolio_maps .panel-body{border-top:none}.o_eportfolio_maps .panel>.panel-body+.table{border-top:none}.panel-footer .o_ep_options{display:inline-block}.o_eportfolio_map{padding:0 20px 2px 3px;border-radius:6px 10px 6px 0;font-family:'Century Gothic', 'Apple Gothic', sans-serif}.o_map_header{padding-left:5px}.o_eportfolio_map ul.nav-tabs li:not(.active) a{background-color:rgba(240,240,240,0.7);border-radius:4px 4px 0 0}.o_eportfolio_edit{border-radius:4px 4px 0 0}.o_ep_actualpage,.o_eportfolio_edit{padding:15px;background-color:#fff}.o_ep_content{margin-top:15px}.o_ep_filter .o_date.form-inline .form-group,.o_ep_filter .o_date.navbar-form .form-group,.o_ep_filter .o_date.o_navbar-form .form-group{margin-left:8px}.o_eportfolio_share_policy_wrapper{border:1px solid #ddd;border-radius:4px}.o_eportfolio_share_header{padding:10px 15px;border-bottom:1px solid #ddd;background-color:#f5f5f5}.o_eportfolio_share_policy{padding:10px 15px}.o_map-default{background:#fafafa;background:#fafafa -webkit-gradient(linear, 37% 20%, 53% 100%, from(#fafafa), to(#efefef));background:#fafafa -moz-linear-gradient(43% 71% 101deg, #efefef, #fafafa);background:#fafafa -o-linear-gradient(#fafafa, #efefef);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa', EndColorStr='#efefef');border:1px solid #efefef;border-left:3px solid rgba(188,188,188,0.8)}.o_eportfolio_maps .o_map-default h4,.o_eportfolio_maps .o_map-default .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-default h2{color:#444;background:none}.o_eportfolio_maps .o_map-default .panel-body,.o_eportfolio_maps .o_map-default td,.o_eportfolio_maps .o_map-default a{color:#000}.o_map-comic{background:#a2c3e8 none;font-family:'Comic Sans MS', 'Comic Sans', fantasy;border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_map-leather{background-color:#957352;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(248,248,248,0.7)), color-stop(100%, rgba(193,193,193,0.5))),url("../light/images/portfolio/white-leather-tile.jpg");background-image:-webkit-linear-gradient(top, rgba(248,248,248,0.7), rgba(193,193,193,0.5)),url("../light/images/portfolio/white-leather-tile.jpg");background-image:-moz-linear-gradient(top, rgba(248,248,248,0.7), rgba(193,193,193,0.5)),url("../light/images/portfolio/white-leather-tile.jpg");background-image:-ms-linear-gradient(top, rgba(248,248,248,0.7), rgba(193,193,193,0.5)),url("../light/images/portfolio/white-leather-tile.jpg");background-image:-o-linear-gradient(top, rgba(248,248,248,0.7), rgba(193,193,193,0.5)),url("../light/images/portfolio/white-leather-tile.jpg");background-image:linear-gradient(top, rgba(248,248,248,0.7), rgba(193,193,193,0.5)),url("../light/images/portfolio/white-leather-tile.jpg");font-family:Palatino, Georgia, serif;border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-leather h4,.o_eportfolio_maps .o_map-leather .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-leather h2{background:rgba(243,230,225,0.3) none}.o_eportfolio_maps .o_map-leather .panel-body,.o_eportfolio_maps .o_map-leather td{color:#333}.o_eportfolio_maps .o_map-leather a{color:#fad9a4}.o_eportfolio_map.o_map-leather .o_map_header h4,.o_eportfolio_map.o_map-leather .o_map_header .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_map.o_map-leather .o_map_header h2,.o_eportfolio_map.o_map-leather .o_map_header p,.o_eportfolio_map.o_map-leather .o_map_header a,.o_eportfolio_map.o_map-leather .o_map_header span,.o_eportfolio_map.o_map-leather .o_map_header label{color:#333}.o_map-epmst-green{background-color:#ecf69a;border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-green h4,.o_eportfolio_maps .o_map-epmst-green .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-green h2{color:#444}.o_eportfolio_maps .o_map-epmst-green .panel-body,.o_eportfolio_maps .o_map-epmst-green td,.o_eportfolio_maps .o_map-epmst-green a{color:#000}.o_map-epmst-green2{background:#99e44d;background:#99e44d -webkit-gradient(linear, 37% 20%, 53% 100%, from(#99e44d), to(#cbf1a5));background:#99e44d -moz-linear-gradient(43% 71% 101deg, #cbf1a5, #99e44d);background:#99e44d -o-linear-gradient(#99e44d, #cbf1a5);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#99e44d', EndColorStr='#cbf1a5');border:1px solid #bbb;border-left:3px solid rgba(136,136,136,0.8)}.o_eportfolio_maps .o_map-epmst-green2 h4,.o_eportfolio_maps .o_map-epmst-green2 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-green2 h2{color:#555}.o_eportfolio_maps .o_map-epmst-green2 .panel-body,.o_eportfolio_maps .o_map-epmst-green2 td,.o_eportfolio_maps .o_map-epmst-green2 a{color:#000}.o_map-epmst-green3{background:#dff0c1;background:#dff0c1 -webkit-gradient(linear, 37% 20%, 53% 100%, from(#dff0c1), to(#a0d346));background:#dff0c1 -moz-linear-gradient(43% 71% 101deg, #a0d346, #dff0c1);background:#dff0c1 -o-linear-gradient(#dff0c1, #a0d346);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#dff0c1', EndColorStr='#a0d346');border:1px solid #bbb;border-left:3px solid rgba(136,136,136,0.8)}.o_eportfolio_maps .o_map-epmst-green3 h4,.o_eportfolio_maps .o_map-epmst-green3 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-green3 h2{color:#555}.o_eportfolio_maps .o_map-epmst-green3 .panel-body,.o_eportfolio_maps .o_map-epmst-green3 td,.o_eportfolio_maps .o_map-epmst-green3 a{color:#000}.o_map-epmst-green4{background-color:#d7dbb5;border:1px solid #bbb;border-left:3px solid rgba(136,136,136,0.8)}.o_eportfolio_maps .o_map-epmst-green4 h4,.o_eportfolio_maps .o_map-epmst-green4 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-green4 h2{color:#555}.o_eportfolio_maps .o_map-epmst-green4 .panel-body,.o_eportfolio_maps .o_map-epmst-green4 td,.o_eportfolio_maps .o_map-epmst-green4 a{color:#000}.o_map-epmst-red{background:#ffba71;background:#ffba71 -webkit-gradient(linear, 37% 20%, 53% 100%, from(#ffba71), to(#ffba99));background:#ffba71 -moz-linear-gradient(43% 71% 101deg, #ffba99, #ffba71);background:#ffba71 -o-linear-gradient(#ffba71, #ffba99);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffba71', EndColorStr='#ffba99');border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-red h4,.o_eportfolio_maps .o_map-epmst-red .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-red h2{color:#444}.o_eportfolio_maps .o_map-epmst-red .panel-body,.o_eportfolio_maps .o_map-epmst-red td,.o_eportfolio_maps .o_map-epmst-red a{color:#000}.o_map-epmst-red2{background:#ff9772;background:#ff9772 -webkit-gradient(linear, 37% 20%, 53% 100%, from(#ff9772), to(#ff9780));background:#ff9772 -moz-linear-gradient(43% 71% 101deg, #ff9780, #ff9772);background:#ff9772 -o-linear-gradient(#ff9772, #ff9780);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#ff9772', EndColorStr='#ff9780');border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-red2 h4,.o_eportfolio_maps .o_map-epmst-red2 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-red2 h2{color:#444}.o_eportfolio_maps .o_map-epmst-red2 .panel-body,.o_eportfolio_maps .o_map-epmst-red2 td,.o_eportfolio_maps .o_map-epmst-red2 a{color:#000}.o_map-epmst-red3{background:#e8afbb;background:#e8afbb -webkit-gradient(linear, 37% 20%, 53% 100%, from(#e8afbb), to(#e8afa0));background:#e8afbb -moz-linear-gradient(43% 71% 101deg, #e8afa0, #e8afbb);background:#e8afbb -o-linear-gradient(#e8afbb, #e8afa0);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#e8afbb', EndColorStr='#e8afa0');border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-red3 h4,.o_eportfolio_maps .o_map-epmst-red3 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-red3 h2{color:#444}.o_eportfolio_maps .o_map-epmst-red3 .panel-body,.o_eportfolio_maps .o_map-epmst-red3 td,.o_eportfolio_maps .o_map-epmst-red3 a{color:#000}.o_map-epmst-red4{background:#ffa800;background:#ffa800 -webkit-gradient(linear, 37% 20%, 53% 100%, from(#ffa800), to(#ffaf00));background:#ffa800 -moz-linear-gradient(43% 71% 101deg, #ffaf00, #ffa800);background:#ffa800 -o-linear-gradient(#ffa800, #ffaf00);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffa800', EndColorStr='#ffaf00');border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-red4 h4,.o_eportfolio_maps .o_map-epmst-red4 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-red4 h2{color:#444}.o_eportfolio_maps .o_map-epmst-red4 .panel-body,.o_eportfolio_maps .o_map-epmst-red4 td,.o_eportfolio_maps .o_map-epmst-red4 a{color:#000}.o_map-epmst-blue{background:#00d2f8;background:#00d2f8 -webkit-gradient(linear, 37% 20%, 53% 100%, from(#00d2f8), to(#4a9ead));background:#00d2f8 -moz-linear-gradient(43% 71% 101deg, #4a9ead, #00d2f8);background:#00d2f8 -o-linear-gradient(#00d2f8, #4a9ead);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#00d2f8', EndColorStr='#4a9ead');border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-blue h4,.o_eportfolio_maps .o_map-epmst-blue .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-blue h2{color:#444}.o_eportfolio_maps .o_map-epmst-blue .panel-body,.o_eportfolio_maps .o_map-epmst-blue td,.o_eportfolio_maps .o_map-epmst-blue a{color:#000}.o_map-epmst-blue2{background-color:#c4f6ff;border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-blue2 h4,.o_eportfolio_maps .o_map-epmst-blue2 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-blue2 h2{color:#444}.o_eportfolio_maps .o_map-epmst-blue2 .panel-body,.o_eportfolio_maps .o_map-epmst-blue2 td,.o_eportfolio_maps .o_map-epmst-blue2 a{color:#000}.o_map-epmst-blue3{background-color:#b3e2f7;border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-blue3{box-shadow:3px 3px 4px rgba(20,20,20,0.4)}.o_eportfolio_maps .o_map-epmst-blue3 h4,.o_eportfolio_maps .o_map-epmst-blue3 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-blue3 h2{color:#444}.o_eportfolio_maps .o_map-epmst-blue3 .panel-body,.o_eportfolio_maps .o_map-epmst-blue3 td,.o_eportfolio_maps .o_map-epmst-blue3 a{color:#000}.o_map-epmst-blue4{background:#dee7f7;background:#dee7f7 -webkit-gradient(linear, 37% 20%, 53% 100%, from(#dee7f7), to(#c1e9fd));background:#dee7f7 -moz-linear-gradient(43% 71% 101deg, #c1e9fd, #dee7f7);background:#dee7f7 -o-linear-gradient(#dee7f7, #c1e9fd);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#dee7f7', EndColorStr='#c1e9fd');border:1px solid #888;border-left:3px solid rgba(85,85,85,0.8)}.o_eportfolio_maps .o_map-epmst-blue4 h4,.o_eportfolio_maps .o_map-epmst-blue4 .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_eportfolio_maps .o_map-epmst-blue4 h2{color:#444}.o_eportfolio_maps .o_map-epmst-blue4 .panel-body,.o_eportfolio_maps .o_map-epmst-blue4 td,.o_eportfolio_maps .o_map-epmst-blue4 a{color:#000}.o_userbulk_changedcell{font-style:italic;font-weight:bold}body.o_dmz{background:transparent}body.o_dmz #o_bg{position:absolute;top:0;left:0;width:100%;height:100%;background:url("../light/images/learn-bg.jpg") 0px 50px/cover;background:linear-gradient(to right, rgba(255,255,255,0.1) 0.2%, rgba(255,255,255,0.6) 60%, rgba(255,255,255,0.8) 100%),url("../light/images/learn-bg.jpg");background-size:cover, cover;background-position:0px 50px,0px 50px}body.o_dmz #o_main_wrapper,body.o_dmz #o_main_wrapper #o_main_container{background:transparent}.o_login{padding-bottom:20px;padding-left:10%;padding-right:10%;text-align:right}.o_login .o_login_intro{padding-left:10%}.o_login .o_login_intro h1{margin-bottom:40px;color:#428bca}.o_login .o_login_intro .lead{color:#333}.o_login .o_login_intro .lead h1,.o_login .o_login_intro .lead h2,.o_login .o_login_intro .lead h3,.o_login .o_login_intro .lead h4,.o_login .o_login_intro .lead .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_login .o_login_intro .lead h2,.o_login .o_login_intro .lead h5{margin-bottom:20px;color:#428bca}.o_login .o_login_messages,.o_login .o_login_box{display:inline-block;width:400px;text-align:left}.o_login .o_login_messages .o_infomessage_wrapper{background:rgba(255,255,255,0.5);border:1px solid transparent;border-radius:4px;padding:6px 12px}.o_login .o_login_messages .o_infomessage_wrapper .o_info,.o_login .o_login_messages .o_infomessage_wrapper .o_warning,.o_login .o_login_messages .o_infomessage_wrapper .o_note{margin:0}.o_login .o_login_box{padding-top:10px}.o_login .o_login_providers{margin-bottom:6px;border-radius:4px;-webkit-box-shadow:0px 1px 10px -1px rgba(0,0,0,0.3);box-shadow:0px 1px 10px -1px rgba(0,0,0,0.3)}.o_login .o_login_providers a span{display:block;font-size:9px;padding-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_login .o_login_provider{background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:0px 1px 10px -1px rgba(0,0,0,0.3);box-shadow:0px 1px 10px -1px rgba(0,0,0,0.3)}.o_login .o_login_form{position:relative;padding:10px 12px}.o_login .o_login_form .o_login_pwd{position:absolute;bottom:2em;right:12px}.o_login .o_login_form .o_form .o_desc{margin:0 0 30px 0;padding:0;border-left:0;background-color:transparent}.o_login .o_login_register{display:block;line-height:2em;font-size:18px;text-align:center;color:#fff;background-color:#5bc0de;border-color:#46b8da;border-radius:4px;margin-top:16px;padding:10px 12px}.o_login .o_login_register:hover,.o_login .o_login_register:focus,.o_login .o_login_register:active,.o_login .o_login_register.active,.open>.o_login .o_login_register.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.o_login .o_login_register:active,.o_login .o_login_register.active,.open>.o_login .o_login_register.dropdown-toggle{background-image:none}.o_login .o_login_register.disabled,.o_login .o_login_register.disabled:hover,.o_login .o_login_register.disabled:focus,.o_login .o_login_register.disabled:active,.o_login .o_login_register.disabled.active,.o_login .o_login_register[disabled],.o_login .o_login_register[disabled]:hover,.o_login .o_login_register[disabled]:focus,.o_login .o_login_register[disabled]:active,.o_login .o_login_register[disabled].active,fieldset[disabled] .o_login .o_login_register,fieldset[disabled] .o_login .o_login_register:hover,fieldset[disabled] .o_login .o_login_register:focus,fieldset[disabled] .o_login .o_login_register:active,fieldset[disabled] .o_login .o_login_register.active{background-color:#5bc0de;border-color:#46b8da}.o_login .o_login_register .badge{color:#5bc0de;background-color:#fff}.o_login .o_login_register small{font-size:14px}.o_login .o_login_social{position:relative;padding:10px 12px}.o_login .o_login_social li{padding:10px 12px}.o_login .o_login_social li>a{display:block;line-height:2em;text-align:center;font-size:18px;border-radius:4px;padding:10px 12px}.o_login .o_login_social a.o_sel_auth_facebook{color:white;background-color:#4568b2}.o_login .o_login_social a.o_sel_auth_twitter{color:white;background-color:#2cc5ff}.o_login .o_login_social a.o_sel_auth_google{color:white;background-color:#e15f4f}.o_login .o_login_social a.o_sel_auth_linkedin{color:white;background-color:#0181bd}.o_login .o_login_social a.o_sel_auth_adfs{color:#19a6e1;background-color:#1a1a1a}@media (max-width: 767px){body.o_dmz #o_bg{background:none;display:none}.o_login{padding:0}.o_login .o_login_intro{padding:0;text-align:left}.o_login .o_login_box_wrapper{text-align:center;padding:0}.o_login .o_login_box{padding-left:0;padding-right:0}.o_login .o_login_box .o_login_providers,.o_login .o_login_box .o_login_provider{-webkit-box-shadow:none;box-shadow:none}.o_login .o_login_messages,.o_login .o_login_box{width:100%;display:block}}.o_home_main h1{text-align:center}.o_home_main .o_icon_rss{line-height:20px;vertical-align:middle}.o_showall{font-size:12px;text-align:right;margin-bottom:5px;margin-top:10px}.o_portlet{position:relative;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_portlet .o_header{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:6px 12px;border-bottom:1px solid #ddd;background-color:#f5f5f5;border-top-right-radius:4px;border-top-left-radius:4px}.o_portlet .o_content{padding:6px 12px}.o_portlet .o_portlet_table{margin:-12px;margin-bottom:-6px;margin-top:0}.o_portlet .o_table_empty.o_info{padding:6px}.o_portlet .o_toolbox{position:absolute;top:-1px;right:-1px;z-index:2;background-color:#fff;border:1px solid #faebcc;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;padding:6px 12px}.o_portlet .o_toolbox div{display:inline}.o_portlet .o_edit_shim{position:absolute;height:100%;width:100%;z-index:1;background:#fcf8e3;opacity:0.8}.o_inactive .o_header a{float:right;margin-left:12px;margin-top:10px}.o_portlet_dyk_q{margin-top:5px;font-style:italic}.o_portlet_dyk_a{margin:5px 0}.o_portlet_dyk_next{margin:5px 0;text-align:right}.o_library_icon:before{content:"\f19c"}.o_library ul{list-style:none;margin:0 0 15px 0;padding:0}.o_library ul ul{margin:0}.o_library_overview .o_library_newest_files ul li{float:left;margin-right:15px}.o_library_item{margin-bottom:10px;position:relative}.o_library_item .o_library_visual,.o_library_item .o_library_extra,.o_library_item .o_library_meta{margin-top:15px}.o_library_item .o_library_visual{float:left;background-color:#fff;border-radius:4px;border:1px solid #ddd}.o_library_item .o_library_visual .o_thumbnail_available,.o_library_item .o_library_visual .o_thumbnail_unavailable{background-size:146px auto;width:150px !important;height:150px !important;background-repeat:no-repeat;background-position:50% 50%}.o_library_item .o_library_visual .o_thumbnail_available:before,.o_library_item .o_library_visual .o_thumbnail_unavailable:before{content:none}.o_library_item .o_library_visual .o_thumbnail_available{background-size:146px auto}.o_library_item .o_library_visual .o_thumbnail_unavailable{display:none}.o_library_item .o_library_extra{float:right;width:200px}.o_library_item .o_library_meta{clear:both}.o_library_item .o_library_meta .o_library_desc{padding-bottom:10px}.o_library_item .o_library_meta small{display:block;word-wrap:break-word}.o_library_item h4,.o_library_item .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_library_item h2{margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:normal}.o_library_item .btn{display:block;margin-bottom:0.5em}.o_library_item .o_comments{display:inline-block}.o_library_item .table{table-layout:fixed;word-wrap:break-word;margin-bottom:0}.o_library_item p.o_library_show_more{text-align:right;margin:0;padding-top:20px}.o_library_item .o_library_more{padding-top:20px;display:none}.o_library_folder{margin-top:-20px}.o_ratings_and_comments .o_rating_title,.o_ratings_and_comments .o_rating_explanation{display:none}@media (min-width: 768px){.o_library_item .o_library_meta{clear:none;margin-left:150px;margin-right:200px;padding:0 10px}.o_library_item .o_library_more{display:none}.o_library_item .o_library_more table tbody{vertical-align:top}.o_library_item .o_library_more table tr,.o_library_item .o_library_more table th,.o_library_item .o_library_more table td{display:inline-block}.o_library_item .o_library_more table tr{width:49%}.o_library_item .o_library_more table th{width:30%}.o_library_item .o_library_more table td{width:70%}}.o_library_item_compact .o_library_extra{width:auto}.o_library_item_compact .o_library_meta{padding:0 10px 0 0;margin:0;overflow:hidden}.o_library_item_compact .btn{display:inline-block}.o_library_item_compact h4,.o_library_item_compact .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_library_item_compact h2{overflow:hidden;margin-right:70px}.o_library_item_compact h4 a,.o_library_item_compact .o_cal .fc-header-title h2 a,.o_cal .fc-header-title .o_library_item_compact h2 a{text-overflow:ellipsis;white-space:nowrap}.o_library_item_compact p.o_library_show_more{padding:20px;position:absolute;top:0;right:0}span.o_translation_i18nitem{position:relative !important}span.o_translation_i18nitem a.o_translation_i18nitem_launcher{position:absolute !important;z-index:100 !important;width:18px !important;height:20px !important;top:0 !important;left:5px !important;background:#fff;border:1px solid #428bca !important;border-radius:3px;text-align:center;padding:0 !important}.o_user_infos{position:relative}.o_user_infos .o_user_portrait{position:absolute;top:0;left:15px;width:100px;height:100px}.o_user_infos .o_user_infos_inner{margin:0 30px 0 100px}.o_user_infos .o_user_infos_inner table{margin:0 30px 15px 30px}.o_members_pagination{text-align:center}.o_visitingcard .o_portrait_avatar,.o_visitingcard .o_portrait_dummy,.o_visitingcard .o_portrait_dummy_female_big,.o_visitingcard .o_portrait_dummy_male_big,.o_visitingcard .o_portrait_anonymous{width:66px;height:66px;margin-right:10px}@media (max-width: 767px){.o_visitingcard .o_portrait_avatar,.o_visitingcard .o_portrait_dummy,.o_visitingcard .o_portrait_dummy_female_big,.o_visitingcard .o_portrait_dummy_male_big,.o_visitingcard .o_portrait_anonymous{width:50px;height:50px;margin:5px 5px 0 0}}.ui-widget{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:100%}.ui-widget-header{border-top:none;border-left:none;border-right:none;border-bottom:1px solid #eee;background:#fff;font-weight:bold}.ui-icon,.ui-widget-content .ui-icon,.ui-widget-header .ui-icon,.ui-state-default .ui-icon,.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-state-active .ui-icon,.ui-state-highlight .ui-icon,.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background:none;background-image:none}.ui-dialog{-webkit-box-shadow:0px 1px 8px -1px rgba(0,0,0,0.35);box-shadow:0px 1px 8px -1px rgba(0,0,0,0.35);background-color:#fefefe}.ui-dialog .ui-widget-header .ui-dialog-title{color:#428bca;font-weight:500;font-family:inherit;line-height:1.1}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close:before{content:"\f00d" !important}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;font-size:120%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ui-dialog .ui-widget-header .ui-button.ui-corner-all{border:none !important;background:#fff !important}.ui-dialog .ui-widget-content{border-color:#fff;padding:5px}.ui-dialog .ui-dialog-titlebar{padding:4px 30px 4px 7px;margin:-2px -2px 0 -2px;background-color:#eee}.ui-dialog.ui-corner-all{border-radius:4px}.ui-dialog.ui-widget-content{border:1px solid transparent}.ui-dialog.o_modal-ui div.ui-dialog-buttonpane{display:none}.ui-datepicker{z-index:2000 !important;-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)}.ui-datepicker .ui-widget-header .ui-corner-all,.ui-datepicker .ui-widget-header .ui-datepicker-next.ui-corner-all{border:none !important;background:#fff !important}.ui-datepicker .ui-widget-header .ui-icon.ui-icon-circle-triangle-e:before{content:"\f061";font-weight:normal;color:black}.ui-datepicker .ui-widget-header .ui-icon.ui-icon-circle-triangle-w:before{content:"\f060";font-weight:normal;color:black}.ui-datepicker .ui-widget-header .ui-icon.ui-icon-circle-triangle-e,.ui-datepicker .ui-widget-header .ui-icon.ui-icon-circle-triangle-w{font-family:'FontAwesome';display:inline-block;background-image:none;background-position:0 0;font-weight:normal;text-indent:0;color:white}.ui-datepicker .ui-widget-header .ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-widget-header .ui-datepicker .ui-datepicker-next-hover{top:2px}.ui-datepicker .ui-state-default{background:#eee}.ui-datepicker .ui-state-highlight,.ui-datepicker .ui-widget-content .ui-state-highlight{border:1px solid #357ebd;background:#428bca;color:#fff}.ui-datepicker.ui-corner-all{border-radius:4px}.ui-datepicker.ui-widget-content{border:1px solid transparent}label.mce-label{display:inline;max-width:150px;margin-bottom:0;font-weight:normal}@media print{a[href]:after{content:""}#o_header_wrapper,#o_offcanvas_right,#o_navbar_wrapper,#o_footer_wrapper,#o_toplink,#o_main_left,#o_main_right,#o_main_toolbar,#jsMath_PrintWarning,.o_noti,.o_opener,.o_hide,.o_noprint{display:none !important}.o_print_break_avoid{page-break-inside:avoid}.o_print_break_before{page-break-before:always}body.o_dmz{background:white !important}} \ No newline at end of file diff --git a/src/test/java/org/olat/course/certificate/manager/CertificatesManagerTest.java b/src/test/java/org/olat/course/certificate/manager/CertificatesManagerTest.java index 8ab92e3d068..1d244037145 100644 --- a/src/test/java/org/olat/course/certificate/manager/CertificatesManagerTest.java +++ b/src/test/java/org/olat/course/certificate/manager/CertificatesManagerTest.java @@ -23,6 +23,7 @@ import java.io.File; import java.net.URISyntaxException; import java.net.URL; import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.UUID; @@ -33,6 +34,7 @@ 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.CertificateLight; import org.olat.course.certificate.CertificateTemplate; import org.olat.course.certificate.CertificatesManager; import org.olat.course.certificate.model.CertificateInfos; @@ -93,15 +95,86 @@ public class CertificatesManagerTest extends OlatTestCase { 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); + //need to sleep + sleep(2000); + + //check if the pdf exists / flush cache, reload the entry with the updated path + dbInstance.commitAndCloseSession(); + Certificate reloadCertificate = certificatesManager.getCertificateById(certificate.getKey()); + VFSLeaf certificateFile = certificatesManager.getCertificateLeaf(reloadCertificate); Assert.assertNotNull(certificateFile); Assert.assertTrue(certificateFile.exists()); } + @Test + public void loadCertificate() { + Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-1"); + RepositoryEntry entry = JunitTestHelper.deployDemoCourse(identity); + dbInstance.commitAndCloseSession(); + + CertificateInfos certificateInfos = new CertificateInfos(identity, 5.0f, Boolean.TRUE); + Certificate certificate = certificatesManager.generateCertificate(certificateInfos, entry, null, null); + Assert.assertNotNull(certificate); + dbInstance.commitAndCloseSession(); + + //full + Certificate reloadedCertificate = certificatesManager.getCertificateById(certificate.getKey()); + Assert.assertNotNull(reloadedCertificate); + Assert.assertEquals(certificate, reloadedCertificate); + Assert.assertNotNull(reloadedCertificate.getUuid()); + Assert.assertEquals(certificate.getUuid(), reloadedCertificate.getUuid()); + Assert.assertEquals(entry.getDisplayname(), reloadedCertificate.getCourseTitle()); + Assert.assertEquals(identity, reloadedCertificate.getIdentity()); + + //light + CertificateLight reloadedLight = certificatesManager.getCertificateLightById(certificate.getKey()); + Assert.assertNotNull(reloadedLight); + Assert.assertEquals(certificate.getKey(), reloadedLight.getKey()); + Assert.assertEquals(entry.getDisplayname(), reloadedLight.getCourseTitle()); + Assert.assertEquals(identity.getKey(), reloadedLight.getIdentityKey()); + Assert.assertEquals(entry.getOlatResource().getKey(), reloadedLight.getOlatResourceKey()); + + //uuid + Certificate reloadedUuid = certificatesManager.getCertificateByUuid(certificate.getUuid()); + Assert.assertNotNull(reloadedUuid); + Assert.assertEquals(certificate, reloadedUuid); + Assert.assertEquals(entry.getDisplayname(), reloadedUuid.getCourseTitle()); + Assert.assertEquals(identity, reloadedUuid.getIdentity()); + + //boolean + boolean has = certificatesManager.hasCertificate(identity, entry.getOlatResource().getKey()); + Assert.assertTrue(has); + } + + @Test + public void loadLastCertificate() { + Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-1"); + RepositoryEntry entry = JunitTestHelper.deployDemoCourse(identity); + dbInstance.commitAndCloseSession(); + + CertificateInfos certificateInfos = new CertificateInfos(identity, 5.0f, Boolean.TRUE); + Certificate certificate = certificatesManager.generateCertificate(certificateInfos, entry, null, null); + Assert.assertNotNull(certificate); + dbInstance.commitAndCloseSession(); + + //per resource + Certificate reloadedCertificate = certificatesManager.getLastCertificate(identity, entry.getOlatResource().getKey()); + Assert.assertNotNull(reloadedCertificate); + Assert.assertEquals(certificate, reloadedCertificate); + + //all + List<CertificateLight> allCertificates = certificatesManager.getLastCertificates(identity); + Assert.assertNotNull(allCertificates); + Assert.assertEquals(1, allCertificates.size()); + CertificateLight allCertificate = allCertificates.get(0); + Assert.assertEquals(certificate.getKey(), allCertificate.getKey()); + Assert.assertEquals(entry.getDisplayname(), allCertificate.getCourseTitle()); + Assert.assertEquals(identity.getKey(), allCertificate.getIdentityKey()); + Assert.assertEquals(entry.getOlatResource().getKey(), allCertificate.getOlatResourceKey()); + } + @Test public void certificateNotifications_courseCoach() throws URISyntaxException { Identity owner = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-2"); @@ -179,4 +252,92 @@ public class CertificatesManagerTest extends OlatTestCase { Assert.assertEquals(1, participantNotifications.size()); Assert.assertTrue(participantNotifications.contains(certificate1)); } + + @Test + public void uploadCertificate() throws URISyntaxException { + Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-1"); + RepositoryEntry entry = JunitTestHelper.deployDemoCourse(identity); + dbInstance.commitAndCloseSession(); + + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.YEAR, 2012); + Date creationDate = cal.getTime(); + URL certificateUrl = CertificatesManagerTest.class.getResource("template.pdf"); + Assert.assertNotNull(certificateUrl); + File certificateFile = new File(certificateUrl.toURI()); + + Certificate certificate = certificatesManager.uploadCertificate(identity, creationDate, entry.getOlatResource(), certificateFile); + Assert.assertNotNull(certificate); + Assert.assertNotNull(certificate.getKey()); + Assert.assertNotNull(certificate.getUuid()); + Assert.assertEquals(entry.getDisplayname(), certificate.getCourseTitle()); + Assert.assertEquals(identity, certificate.getIdentity()); + + dbInstance.commitAndCloseSession(); + + //double check + Certificate reloadedCertificate = certificatesManager.getCertificateById(certificate.getKey()); + Assert.assertNotNull(reloadedCertificate); + Assert.assertNotNull(reloadedCertificate.getUuid()); + Assert.assertEquals(entry.getDisplayname(), reloadedCertificate.getCourseTitle()); + Assert.assertEquals(identity, reloadedCertificate.getIdentity()); + Assert.assertEquals(entry.getOlatResource().getKey(), reloadedCertificate.getArchivedResourceKey()); + Assert.assertEquals(creationDate, reloadedCertificate.getCreationDate()); + + //the file + VFSLeaf savedCertificateFile = certificatesManager.getCertificateLeaf(reloadedCertificate); + Assert.assertNotNull(savedCertificateFile); + Assert.assertTrue(savedCertificateFile.exists()); + + //load last + Certificate lastCertificate = certificatesManager.getLastCertificate(identity, entry.getOlatResource().getKey()); + Assert.assertNotNull(lastCertificate); + Assert.assertEquals(certificate, lastCertificate); + } + + @Test + public void uploadStandalone() throws URISyntaxException { + Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cer-1"); + dbInstance.commitAndCloseSession(); + + String courseTitle = "Unkown course"; + Long resourceKey = 4l; + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.YEAR, 2012); + Date creationDate = cal.getTime(); + URL certificateUrl = CertificatesManagerTest.class.getResource("template.pdf"); + Assert.assertNotNull(certificateUrl); + File certificateFile = new File(certificateUrl.toURI()); + + Certificate certificate = certificatesManager + .uploadStandaloneCertificate(identity, creationDate, courseTitle, resourceKey, certificateFile); + Assert.assertNotNull(certificate); + Assert.assertNotNull(certificate.getKey()); + Assert.assertNotNull(certificate.getUuid()); + Assert.assertEquals(courseTitle, certificate.getCourseTitle()); + Assert.assertEquals(identity, certificate.getIdentity()); + + dbInstance.commitAndCloseSession(); + + //load by id + Certificate reloadedCertificate = certificatesManager.getCertificateById(certificate.getKey()); + Assert.assertNotNull(reloadedCertificate); + Assert.assertNotNull(reloadedCertificate.getUuid()); + Assert.assertEquals(courseTitle, reloadedCertificate.getCourseTitle()); + Assert.assertEquals(identity, reloadedCertificate.getIdentity()); + Assert.assertEquals(resourceKey, reloadedCertificate.getArchivedResourceKey()); + Assert.assertEquals(creationDate, reloadedCertificate.getCreationDate()); + + //load last + Certificate lastCertificate = certificatesManager.getLastCertificate(identity, resourceKey); + Assert.assertNotNull(lastCertificate); + Assert.assertEquals(certificate.getKey(), lastCertificate.getKey()); + Assert.assertEquals(reloadedCertificate, lastCertificate); + } } diff --git a/src/test/java/org/olat/restapi/CertificationTest.java b/src/test/java/org/olat/restapi/CertificationTest.java index af41aef45a2..b3a5abc97ab 100644 --- a/src/test/java/org/olat/restapi/CertificationTest.java +++ b/src/test/java/org/olat/restapi/CertificationTest.java @@ -118,6 +118,9 @@ public class CertificationTest extends OlatJerseyTestCase { Assert.assertEquals(200, response.getStatusLine().getStatusCode()); EntityUtils.consume(response.getEntity()); + //async process mean sleep a little + sleep(2000); + //check certificate Certificate certificate = certificatesManager.getLastCertificate(assessedIdentity, entry.getOlatResource().getKey()); Assert.assertNotNull(certificate); -- GitLab