From a4b615e3500c7b7f5570093e4f83cbaaee48bc43 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 21 Nov 2014 12:23:16 +0100 Subject: [PATCH] OO-1323,OO-1254: add user search to the coaching tool, add reference to certificate too --- .../org/olat/basesecurity/BaseSecurity.java | 2 + .../basesecurity/BaseSecurityManager.java | 7 + .../persistence/_spring/core_persistence.xml | 1 + .../configuration/AbstractSpringModule.java | 4 + .../components/panel/SimpleStackedPanel.java | 5 + .../assessment/AssessmentMainController.java | 2 +- .../IdentityAssessmentEditController.java | 5 + .../course/certificate/CertificateEvent.java | 19 ++ .../certificate/CertificatesManager.java | 23 +- .../manager/CertificatesManagerImpl.java | 25 +- ...ssessedIdentityCertificatesController.java | 7 +- .../ui/DownloadCertificateCellRenderer.java | 4 +- .../olat/modules/coach/CoachUIFactory.java | 72 ------ .../olat/modules/coach/CoachingModule.java | 42 ++-- .../olat/modules/coach/CoachingService.java | 7 + .../modules/coach/_spring/coachContext.xml | 96 +------- .../modules/coach/manager/CoachingDAO.java | 61 +++++ .../coach/manager/CoachingServiceImpl.java | 13 + .../EfficiencyStatementIdentityStatEntry.java | 121 ++++++++++ .../coach/model/IdentityResourceKey.java | 64 +++++ .../modules/coach/ui/CoachMainController.java | 150 +++++++++++- .../modules/coach/ui/CourseController.java | 58 ++++- ...fficiencyStatementEntryTableDataModel.java | 38 ++- .../modules/coach/ui/GroupController.java | 56 ++++- .../coach/ui/StudentCoursesController.java | 63 ++++- .../coach/ui/StudentListController.java | 2 +- .../modules/coach/ui/UserListController.java | 222 ++++++++++++++++++ .../coach/ui/UserSearchController.java | 110 +++++++++ .../olat/modules/coach/ui/UserSearchForm.java | 175 ++++++++++++++ .../modules/coach/ui/_content/user_list.html | 4 + .../coach/ui/_i18n/LocalStrings_de.properties | 9 +- .../_spring/userPropertiesContext.xml | 22 ++ .../database/mysql/alter_10_0_0_to_10_1_0.sql | 90 +++++++ .../database/mysql/setupDatabase.sql | 37 ++- .../oracle/alter_10_0_0_to_10_1_0.sql | 90 +++++++ .../database/oracle/setupDatabase.sql | 24 +- .../postgresql/alter_10_0_0_to_10_1_0.sql | 93 ++++++++ .../database/postgresql/setupDatabase.sql | 25 +- .../static/themes/light/modules/_navbar.scss | 4 + .../static/themes/light/modules/_toolbar.scss | 2 +- src/main/webapp/static/themes/light/theme.css | 2 +- .../themes/light/theme_ie_completions.css | 2 +- .../qpool/ItemFileResourceValidatorTest.java | 19 ++ .../modules/coach/CoachingServiceTest.java | 63 +++-- 44 files changed, 1655 insertions(+), 285 deletions(-) delete mode 100644 src/main/java/org/olat/modules/coach/CoachUIFactory.java create mode 100644 src/main/java/org/olat/modules/coach/model/EfficiencyStatementIdentityStatEntry.java create mode 100644 src/main/java/org/olat/modules/coach/model/IdentityResourceKey.java create mode 100644 src/main/java/org/olat/modules/coach/ui/UserListController.java create mode 100644 src/main/java/org/olat/modules/coach/ui/UserSearchController.java create mode 100644 src/main/java/org/olat/modules/coach/ui/UserSearchForm.java create mode 100644 src/main/java/org/olat/modules/coach/ui/_content/user_list.html diff --git a/src/main/java/org/olat/basesecurity/BaseSecurity.java b/src/main/java/org/olat/basesecurity/BaseSecurity.java index f6b8b957e5b..fb3b6077832 100644 --- a/src/main/java/org/olat/basesecurity/BaseSecurity.java +++ b/src/main/java/org/olat/basesecurity/BaseSecurity.java @@ -502,6 +502,8 @@ public interface BaseSecurity { public List<Identity> getVisibleIdentitiesByPowerSearch(String login, Map<String, String> userProperties, boolean userPropertiesAsIntersectionSearch, SecurityGroup[] groups, PermissionOnResourceable[] permissionOnResources, String[] authProviders, Date createdAfter, Date createdBefore); + public int countIdentitiesByPowerSearch(SearchIdentityParams params); + /** * Like the following method but compact * @param params diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java index d4842207400..7d5dc96cbff 100644 --- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java +++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java @@ -1340,6 +1340,13 @@ public class BaseSecurityManager extends BasicManager implements BaseSecurity { DBQuery dbq = createIdentitiesByPowerQuery(new SearchIdentityParams(login, userproperties, userPropertiesAsIntersectionSearch, groups, permissionOnResources, authProviders, createdAfter, createdBefore, userLoginAfter, userLoginBefore, status), false); return dbq.list(); } + + @Override + public int countIdentitiesByPowerSearch(SearchIdentityParams params) { + DBQuery dbq = createIdentitiesByPowerQuery(params, true); + Number count = (Number)dbq.uniqueResult(); + return count.intValue(); + } @Override public List<Identity> getIdentitiesByPowerSearch(SearchIdentityParams params, int firstResult, int maxResults) { diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml index 2f3accee64d..cdf3d253275 100644 --- a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml +++ b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml @@ -126,6 +126,7 @@ <class>org.olat.modules.coach.model.EfficiencyStatementGroupStatEntry</class> <class>org.olat.modules.coach.model.EfficiencyStatementCourseStatEntry</class> <class>org.olat.modules.coach.model.EfficiencyStatementStudentStatEntry</class> + <class>org.olat.modules.coach.model.EfficiencyStatementIdentityStatEntry</class> <class>org.olat.modules.qpool.model.PoolImpl</class> <class>org.olat.modules.qpool.model.PoolToItem</class> <class>org.olat.modules.qpool.model.PoolItemShortView</class> diff --git a/src/main/java/org/olat/core/configuration/AbstractSpringModule.java b/src/main/java/org/olat/core/configuration/AbstractSpringModule.java index e3506b69806..183b9c7336b 100644 --- a/src/main/java/org/olat/core/configuration/AbstractSpringModule.java +++ b/src/main/java/org/olat/core/configuration/AbstractSpringModule.java @@ -58,6 +58,10 @@ public abstract class AbstractSpringModule implements GenericEventListener, Init moduleConfigProperties = new PersistedProperties(coordinatorManager, this, secured); } + public AbstractSpringModule(CoordinatorManager coordinatorManager, String path, boolean secured) { + moduleConfigProperties = new PersistedProperties(coordinatorManager, this, path, secured); + } + @Value("${userdata.dir}") private String userDataDirectory; diff --git a/src/main/java/org/olat/core/gui/components/panel/SimpleStackedPanel.java b/src/main/java/org/olat/core/gui/components/panel/SimpleStackedPanel.java index eaa05a446ea..9a8f48e67a6 100644 --- a/src/main/java/org/olat/core/gui/components/panel/SimpleStackedPanel.java +++ b/src/main/java/org/olat/core/gui/components/panel/SimpleStackedPanel.java @@ -94,6 +94,7 @@ public class SimpleStackedPanel extends AbstractComponent implements StackedPane * * @param newContent the newContent. if null, then the panel will be empty */ + @Override public void setContent(Component newContent) { stackList.clear(); if (newContent != null) { @@ -107,12 +108,14 @@ public class SimpleStackedPanel extends AbstractComponent implements StackedPane /** * @param newContent may not be null */ + @Override public void pushContent(Component newContent) { stackList.add(newContent); curContent = newContent; setDirty(true); } + @Override public void popContent() { int stackHeight = stackList.size(); if (stackHeight < 1) throw new AssertException("stack was empty!"); @@ -129,6 +132,7 @@ public class SimpleStackedPanel extends AbstractComponent implements StackedPane /** * @see org.olat.core.gui.components.Component#getExtendedDebugInfo() */ + @Override public String getExtendedDebugInfo() { StringBuilder sb = new StringBuilder(); int size = stackList.size(); @@ -140,6 +144,7 @@ public class SimpleStackedPanel extends AbstractComponent implements StackedPane return "stacksize:" + size + ", active:" + sb.toString(); } + @Override public ComponentRenderer getHTMLRendererSingleton() { return RENDERER; } diff --git a/src/main/java/org/olat/course/assessment/AssessmentMainController.java b/src/main/java/org/olat/course/assessment/AssessmentMainController.java index 952a0eef86d..b71ed133abd 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentMainController.java +++ b/src/main/java/org/olat/course/assessment/AssessmentMainController.java @@ -982,7 +982,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen private Map<Long, CertificateLight> getCertificates(ICourse course) { Map<Long, CertificateLight> certificates = new ConcurrentHashMap<>(); OLATResource resource = course.getCourseEnvironment().getCourseGroupManager().getCourseResource(); - List<CertificateLight> certificateList = certificatesManager.getCertificates(resource); + List<CertificateLight> certificateList = certificatesManager.getLastCertificates(resource); for(CertificateLight certificate:certificateList) { CertificateLight currentCertificate = certificates.get(certificate.getIdentityKey()); if(currentCertificate == null || currentCertificate.getCreationDate().before(certificate.getCreationDate())) { diff --git a/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java b/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java index ab66e3b9a77..35ec4aedc56 100644 --- a/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java +++ b/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java @@ -121,6 +121,7 @@ public class IdentityAssessmentEditController extends BasicController { if(courseConfig.isAutomaticCertificationEnabled() || courseConfig.isManualCertificationEnabled()) { certificateCtrl = new AssessedIdentityCertificatesController(ureq, wControl, assessedUserCourseEnvironment); identityAssessmentVC.put("certificateInfos", certificateCtrl.getInitialComponent()); + listenTo(certificateCtrl); } BusinessControl bc = getWindowControl().getBusinessControl(); @@ -167,6 +168,10 @@ public class IdentityAssessmentEditController extends BasicController { doIdentityAssessmentOverview(ureq, true); fireEvent(ureq, Event.DONE_EVENT); } + } else if(source == certificateCtrl) { + if(event == Event.CHANGED_EVENT) { + fireEvent(ureq, Event.CHANGED_EVENT); + } } } diff --git a/src/main/java/org/olat/course/certificate/CertificateEvent.java b/src/main/java/org/olat/course/certificate/CertificateEvent.java index bb12662d0de..2131cfbaf42 100644 --- a/src/main/java/org/olat/course/certificate/CertificateEvent.java +++ b/src/main/java/org/olat/course/certificate/CertificateEvent.java @@ -1,3 +1,22 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ package org.olat.course.certificate; import org.olat.core.util.event.MultiUserEvent; diff --git a/src/main/java/org/olat/course/certificate/CertificatesManager.java b/src/main/java/org/olat/course/certificate/CertificatesManager.java index 6517e51d060..029ccf98c2a 100644 --- a/src/main/java/org/olat/course/certificate/CertificatesManager.java +++ b/src/main/java/org/olat/course/certificate/CertificatesManager.java @@ -34,6 +34,7 @@ import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.VFSLeaf; import org.olat.course.ICourse; import org.olat.course.certificate.model.CertificateInfos; +import org.olat.group.BusinessGroup; import org.olat.repository.RepositoryEntry; import org.olat.resource.OLATResource; @@ -99,12 +100,30 @@ public interface CertificatesManager { public VFSLeaf getCertificateLeaf(Certificate certificate); - + /** + * Return the last certificates of the user. + * @param identity + * @return A list of certificates + */ public List<CertificateLight> getLastCertificates(IdentityRef identity); + /** + * Return the last certificates of all users f the specified course. + * @param resourceKey The resource primary key of the course. + * @return A list of certificates + */ + public List<CertificateLight> getLastCertificates(OLATResource resourceKey); + + /** + * Return the last certificates of all users and all courses linked + * to this group. + * @param businessGroup + * @return A list of certificates + */ + public List<CertificateLight> getLastCertificates(BusinessGroup businessGroup); + public List<Certificate> getCertificatesForNotifications(Identity identity, RepositoryEntry entry, Date lastNews); - public List<CertificateLight> getCertificates(OLATResource resourceKey); public boolean hasCertificate(IdentityRef identity, Long resourceKey); 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 28ed611d3bd..74a879fd08a 100644 --- a/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java +++ b/src/main/java/org/olat/course/certificate/manager/CertificatesManagerImpl.java @@ -102,6 +102,7 @@ import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; +import org.olat.group.manager.BusinessGroupRelationDAO; import org.olat.group.model.SearchBusinessGroupParams; import org.olat.modules.vitero.model.GroupRole; import org.olat.repository.RepositoryEntry; @@ -150,6 +151,8 @@ public class CertificatesManagerImpl implements CertificatesManager, MessageList @Autowired private BusinessGroupService businessGroupService; @Autowired + private BusinessGroupRelationDAO businessGroupRelationDao; + @Autowired private CoordinatorManager coordinatorManager; @Resource(name="certificateQueue") @@ -467,15 +470,33 @@ public class CertificatesManagerImpl implements CertificatesManager, MessageList } @Override - public List<CertificateLight> getCertificates(OLATResource resource) { + public List<CertificateLight> getLastCertificates(OLATResource resource) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificatelight cer") - .append(" where cer.olatResourceKey=:resourceKey"); + .append(" where cer.olatResourceKey=:resourceKey and cer.last=true"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), CertificateLight.class) .setParameter("resourceKey", resource.getKey()) .getResultList(); } + + @Override + public List<CertificateLight> getLastCertificates(BusinessGroup businessGroup) { + List<BusinessGroup> groups = Collections.singletonList(businessGroup); + List<RepositoryEntry> entries = businessGroupRelationDao.findRepositoryEntries(groups, 0, -1); + List<Long> resourceKeys = new ArrayList<>(entries.size()); + for(RepositoryEntry entry:entries) { + resourceKeys.add(entry.getOlatResource().getKey()); + } + + StringBuilder sb = new StringBuilder(); + sb.append("select cer from certificatelight cer") + .append(" where cer.olatResourceKey in (:resourceKeys) and cer.last=true"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), CertificateLight.class) + .setParameter("resourceKeys", resourceKeys) + .getResultList(); + } @Override public boolean isRecertificationAllowed(Identity identity, RepositoryEntry entry) { 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 15fc037d94a..461282be7d5 100644 --- a/src/main/java/org/olat/course/certificate/ui/AssessedIdentityCertificatesController.java +++ b/src/main/java/org/olat/course/certificate/ui/AssessedIdentityCertificatesController.java @@ -155,7 +155,7 @@ public class AssessedIdentityCertificatesController extends BasicController impl protected void event(UserRequest ureq, Controller source, Event event) { if(confirmCertificateCtrl == source) { if(DialogBoxUIFactory.isYesEvent(event)) { - doGenerateCertificate(); + doGenerateCertificate(ureq); } } else if(confirmDeleteCtrl == source) { if(DialogBoxUIFactory.isYesEvent(event)) { @@ -186,7 +186,7 @@ public class AssessedIdentityCertificatesController extends BasicController impl RepositoryEntry courseEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); if(certificatesManager.isRecertificationAllowed(assessedIdentity, courseEntry)) { //don't need to confirm - doGenerateCertificate(); + doGenerateCertificate(ureq); } else { String title = translate("confirm.certificate.title"); String text = translate("confirm.certificate.text"); @@ -194,7 +194,7 @@ public class AssessedIdentityCertificatesController extends BasicController impl } } - private void doGenerateCertificate() { + private void doGenerateCertificate(UserRequest ureq) { ICourse course = CourseFactory.loadCourse(resource); CourseNode rootNode = course.getRunStructure().getRootNode(); Identity assessedIdentity = assessedUserCourseEnv.getIdentityEnvironment().getIdentity(); @@ -214,6 +214,7 @@ public class AssessedIdentityCertificatesController extends BasicController impl certificatesManager.generateCertificate(certificateInfos, courseEntry, template, result); loadList(); showInfo("msg.certificate.pending"); + fireEvent(ureq, Event.CHANGED_EVENT); } public static class Links { 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 0fe9f0beb59..4e2124d8e5a 100644 --- a/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java +++ b/src/main/java/org/olat/course/certificate/ui/DownloadCertificateCellRenderer.java @@ -82,9 +82,9 @@ public class DownloadCertificateCellRenderer implements CustomCellRenderer, Flex private void render(StringOutput sb, CertificateLight certificate, Identity identity, Locale locale) { String name = Formatter.getInstance(locale).formatDate(certificate.getCreationDate()); 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>"); + 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>"); + 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> ") diff --git a/src/main/java/org/olat/modules/coach/CoachUIFactory.java b/src/main/java/org/olat/modules/coach/CoachUIFactory.java deleted file mode 100644 index f221ff0f0ee..00000000000 --- a/src/main/java/org/olat/modules/coach/CoachUIFactory.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.modules.coach; - -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.WindowControl; -import org.olat.modules.coach.ui.CourseListController; -import org.olat.modules.coach.ui.GroupListController; -import org.olat.modules.coach.ui.StudentListController; - -/** - * - * Description:<br> - * UI factory for the coach site (used by the controller creator configured - * in coachContext.xml) - * - * <P> - * Initial Date: 8 févr. 2012 <br> - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - */ -public class CoachUIFactory { - - /** - * Return a controller which shows an overview over all students the user tutored. - * tutoring. - * @param ureq - * @param wControl - * @return The students overview controller - */ - public static Controller createStudentsController(UserRequest ureq, WindowControl wControl) { - return new StudentListController(ureq, wControl); - } - - /** - * Return a controller which shows an overview of all groups the user tutored. - * @param ureq - * @param wControl - * @return The groups overview controller - */ - public static Controller createGroupsController(UserRequest ureq, WindowControl wControl) { - return new GroupListController(ureq, wControl); - } - - /** - * Return a controller which shows an overview of all courses the user tutored. - * @param ureq - * @param wControl - * @return The courses overview controller - */ - public static Controller createCoursesController(UserRequest ureq, WindowControl wControl) { - return new CourseListController(ureq, wControl); - } -} diff --git a/src/main/java/org/olat/modules/coach/CoachingModule.java b/src/main/java/org/olat/modules/coach/CoachingModule.java index b635599214b..6e859b879a7 100644 --- a/src/main/java/org/olat/modules/coach/CoachingModule.java +++ b/src/main/java/org/olat/modules/coach/CoachingModule.java @@ -20,11 +20,14 @@ package org.olat.modules.coach; import org.olat.NewControllerFactory; -import org.olat.core.configuration.AbstractOLATModule; +import org.olat.core.configuration.AbstractSpringModule; import org.olat.core.configuration.ConfigOnOff; -import org.olat.core.configuration.PersistedProperties; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.modules.coach.site.CoachContextEntryControllerCreator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; /** * @@ -35,12 +38,15 @@ import org.olat.modules.coach.site.CoachContextEntryControllerCreator; * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class CoachingModule extends AbstractOLATModule implements ConfigOnOff { +@Service +public class CoachingModule extends AbstractSpringModule implements ConfigOnOff { + @Value("${coaching.enabled:true}") private boolean enabled; - private CoachingModule() { - // + @Autowired + public CoachingModule(CoordinatorManager coordinatorManager) { + super(coordinatorManager, "com.frentix.olat.coach.CoachingModule", false); } @Override @@ -63,27 +69,19 @@ public class CoachingModule extends AbstractOLATModule implements ConfigOnOff { NewControllerFactory.getInstance().addContextEntryControllerCreator("CoachSite", new CoachContextEntryControllerCreator()); - //portfolio enabled/disabled - String enabledObj = getStringPropertyValue("coaching.enabled", true); - if(StringHelper.containsNonWhitespace(enabledObj)) { - enabled = "true".equals(enabledObj); - } - } - - @Override - public void setPersistedProperties(PersistedProperties persistedProperties) { - this.moduleConfigProperties = persistedProperties; - - } - - @Override - protected void initDefaultProperties() { - enabled = getBooleanConfigParameter("coaching.enabled", true); + updateProperties(); } @Override protected void initFromChangedProperties() { - init(); + updateProperties(); + } + + private void updateProperties() { + String enabledObj = getStringPropertyValue("coaching.enabled", true); + if(StringHelper.containsNonWhitespace(enabledObj)) { + enabled = "true".equals(enabledObj); + } } diff --git a/src/main/java/org/olat/modules/coach/CoachingService.java b/src/main/java/org/olat/modules/coach/CoachingService.java index ef203b325bc..13739b189e9 100644 --- a/src/main/java/org/olat/modules/coach/CoachingService.java +++ b/src/main/java/org/olat/modules/coach/CoachingService.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import org.olat.basesecurity.IdentityRef; import org.olat.core.id.Identity; import org.olat.course.assessment.UserEfficiencyStatement; import org.olat.group.BusinessGroup; @@ -49,6 +50,12 @@ public interface CoachingService { public List<StudentStatEntry> getStudentsStatistics(Identity coach); + + public List<StudentStatEntry> getUsersStatistics(List<? extends IdentityRef> identities); + + public List<RepositoryEntry> getUserCourses(Identity student, int firstResult, int maxResults); + + public List<CourseStatEntry> getCoursesStatistics(Identity coach); public List<GroupStatEntry> getGroupsStatistics(Identity coach); diff --git a/src/main/java/org/olat/modules/coach/_spring/coachContext.xml b/src/main/java/org/olat/modules/coach/_spring/coachContext.xml index b920035b305..8d81425363c 100644 --- a/src/main/java/org/olat/modules/coach/_spring/coachContext.xml +++ b/src/main/java/org/olat/modules/coach/_spring/coachContext.xml @@ -1,38 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context.xsd"> - - <context:component-scan base-package="org.olat.modules.coach" /> - - <bean id="coachingModule" class="org.olat.modules.coach.CoachingModule" init-method="init" - depends-on=""> - <property name="persistedProperties"> - <bean class="org.olat.core.configuration.PersistedProperties" scope="prototype" init-method="init" destroy-method="destroy"> - <constructor-arg index="0" ref="coordinatorManager"/> - <constructor-arg index="1" ref="coachingModule" /> - <constructor-arg index="2" value="com.frentix.olat.coach.CoachingModule" /> - <constructor-arg index="3" value="false" /> - </bean> - </property> - </bean> - - <!-- default configuration --> - <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> - <property name="targetObject" ref="coachingModule" /> - <property name="targetMethod" value="init" /> - <property name="arguments"> - <value> - coaching.enabled=${site.coaching.enable} - </value> - </property> - </bean> - - <!-- the REST API menu-entry --> + http://www.springframework.org/schema/beans/spring-beans.xsd"> + + <!-- Admin. --> <bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> <property name="order" value="7208" /> <property name="navigationKey" value="coachingAdmin" /> @@ -50,68 +22,6 @@ </list> </property> <property name="parentTreeNodeIdentifier" value="modulesParent" /> - </bean> - - - <!-- students menu item --> - <bean class="org.olat.core.extensions.action.GenericActionExtension" id="coach.menupoint.student" init-method="initExtensionPoints" > - <property name="order" value="701" /> - <property name="navigationKey" value="students" /> - <property name="actionController"> - <bean class=" org.olat.core.gui.control.creator.FactoryControllerCreator" scope="prototype"> - <property name="factoryName" value="org.olat.modules.coach.CoachUIFactory"/> - <property name="factoryMethod" value="createStudentsController"/> - </bean> - </property> - <property name="translationPackage" value="org.olat.modules.coach.ui" /> - <property name="i18nActionKey" value="students.menu.title"/> - <property name="i18nDescriptionKey" value="students.menu.title.alt"/> - <property name="extensionPoints"> - <list> - <value>org.olat.modules.coach.ui.CoachMainController</value> - </list> - </property> </bean> - - <!-- students menu item --> - <bean class="org.olat.core.extensions.action.GenericActionExtension" id="coach.menupoint.group" init-method="initExtensionPoints" > - <property name="order" value="702" /> - <property name="navigationKey" value="groups" /> - <property name="actionController"> - <bean class=" org.olat.core.gui.control.creator.FactoryControllerCreator" scope="prototype"> - <property name="factoryName" value="org.olat.modules.coach.CoachUIFactory"/> - <property name="factoryMethod" value="createGroupsController"/> - </bean> - </property> - <property name="translationPackage" value="org.olat.modules.coach.ui" /> - <property name="i18nActionKey" value="groups.menu.title"/> - <property name="i18nDescriptionKey" value="groups.menu.title.alt"/> - <property name="extensionPoints"> - <list> - <value>org.olat.modules.coach.ui.CoachMainController</value> - </list> - </property> - </bean> - - <!-- students menu item --> - <bean class="org.olat.core.extensions.action.GenericActionExtension" id="coach.menupoint.course" init-method="initExtensionPoints" > - <property name="order" value="703" /> - <property name="navigationKey" value="courses" /> - <property name="actionController"> - <bean class=" org.olat.core.gui.control.creator.FactoryControllerCreator" scope="prototype"> - <property name="factoryName" value="org.olat.modules.coach.CoachUIFactory"/> - <property name="factoryMethod" value="createCoursesController"/> - </bean> - </property> - <property name="translationPackage" value="org.olat.modules.coach.ui" /> - <property name="i18nActionKey" value="courses.menu.title"/> - <property name="i18nDescriptionKey" value="courses.menu.title.alt"/> - <property name="extensionPoints"> - <list> - <value>org.olat.modules.coach.ui.CoachMainController</value> - </list> - </property> - </bean> - </beans> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java b/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java index f7b26509e04..d4107983b00 100644 --- a/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java +++ b/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java @@ -26,6 +26,7 @@ import java.util.List; import javax.persistence.Query; import javax.persistence.TypedQuery; +import org.olat.basesecurity.IdentityRef; import org.olat.basesecurity.IdentityShort; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; @@ -268,6 +269,44 @@ public class CoachingDAO extends BasicManager { return stats; } + public List<StudentStatEntry> getUsersStatistics(List<? extends IdentityRef> identities) { + if(identities == null || identities.isEmpty()) return Collections.emptyList(); + + StringBuilder query = new StringBuilder(); + // 0 1 2 3 4 5 + query.append("select s.studentKey, count(s.repoKey), sum(s.passed), sum(s.failed), sum(s.notAttempted), count(s.initialLaunchKey)") + .append(" from coachstatisticsidentity as s ") + .append(" where s.studentKey in (:identitiesKey)") + .append(" group by s.studentKey"); + + List<Long> identityKeys = getIdentityKeys(identities); + List<Object[]> rawStats = dbInstance.getCurrentEntityManager() + .createQuery(query.toString(), Object[].class) + .setParameter("identitiesKey", identityKeys) + .getResultList(); + + List<StudentStatEntry> stats = new ArrayList<StudentStatEntry>(); + for(Object[] rawStat:rawStats) { + StudentStatEntry entry = new StudentStatEntry(); + entry.setStudentKey((Long)rawStat[0]); + entry.setCountRepo(((Number)rawStat[1]).intValue()); + entry.setCountPassed(((Number)rawStat[2]).intValue()); + entry.setCountFailed(((Number)rawStat[3]).intValue()); + entry.setCountNotAttempted(((Number)rawStat[4]).intValue()); + entry.setInitialLaunch(((Number)rawStat[5]).intValue()); + stats.add(entry); + } + return stats; + } + + private List<Long> getIdentityKeys(List<? extends IdentityRef> identities) { + List<Long> identityKeys = new ArrayList<>(identities.size()); + for(IdentityRef ref:identities) { + identityKeys.add(ref.getKey()); + } + return identityKeys; + } + public List<Long> getStudents(Identity coach, RepositoryEntry entry) { StringBuilder sb = new StringBuilder(); sb.append("select distinct(participant.identity.key) from repoentrytogroup as relGroup ") @@ -306,4 +345,26 @@ public class CoachingDAO extends BasicManager { List<RepositoryEntry> courses = dbQuery.getResultList(); return courses; } + + public List<RepositoryEntry> getUserCourses(IdentityRef student, int firstResult, int maxResults) { + StringBuilder sb = new StringBuilder(); + sb.append("select distinct(re) from ").append(RepositoryEntry.class.getName()).append(" as re ") + .append(" inner join re.groups as relGroup ") + .append(" inner join relGroup.group as baseGroup") + .append(" inner join baseGroup.members as participant on participant.role='participant'") + .append(" where participant.identity.key=:studentKey"); + + TypedQuery<RepositoryEntry> dbQuery = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), RepositoryEntry.class) + .setParameter("studentKey", student.getKey()); + if(firstResult >= 0) { + dbQuery.setFirstResult(firstResult); + } + if(maxResults > 0) { + dbQuery.setMaxResults(maxResults); + } + + List<RepositoryEntry> courses = dbQuery.getResultList(); + return courses; + } } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/manager/CoachingServiceImpl.java b/src/main/java/org/olat/modules/coach/manager/CoachingServiceImpl.java index ac7a2a40fe2..53cd7a5267c 100644 --- a/src/main/java/org/olat/modules/coach/manager/CoachingServiceImpl.java +++ b/src/main/java/org/olat/modules/coach/manager/CoachingServiceImpl.java @@ -27,6 +27,7 @@ import java.util.Map; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.IdentityRef; import org.olat.basesecurity.IdentityShort; import org.olat.core.id.Identity; import org.olat.course.assessment.UserEfficiencyStatement; @@ -87,6 +88,18 @@ public class CoachingServiceImpl implements CoachingService { public List<StudentStatEntry> getStudentsStatistics(Identity coach) { return coachingDao.getStudentsStatistics(coach); } + + + + @Override + public List<StudentStatEntry> getUsersStatistics(List<? extends IdentityRef> identities) { + return coachingDao.getUsersStatistics(identities); + } + + @Override + public List<RepositoryEntry> getUserCourses(Identity student, int firstResult, int maxResults) { + return coachingDao.getUserCourses(student, firstResult, maxResults); + } @Override public List<CourseStatEntry> getCoursesStatistics(Identity coach) { diff --git a/src/main/java/org/olat/modules/coach/model/EfficiencyStatementIdentityStatEntry.java b/src/main/java/org/olat/modules/coach/model/EfficiencyStatementIdentityStatEntry.java new file mode 100644 index 00000000000..030a9762480 --- /dev/null +++ b/src/main/java/org/olat/modules/coach/model/EfficiencyStatementIdentityStatEntry.java @@ -0,0 +1,121 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.coach.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * + * Initial date: 28.02.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="coachstatisticsidentity") +@Table(name="o_as_eff_statement_identity_v") +public class EfficiencyStatementIdentityStatEntry { + + @Id + @Column(name="st_id", nullable=false, unique=true, insertable=false, updatable=false) + private Long statementKey; + + @Column(name="re_id", nullable=false, unique=false, insertable=false, updatable=false) + private Long repoKey; + @Column(name="student_id", nullable=false, unique=false, insertable=false, updatable=false) + private Long studentKey; + + @Column(name="st_score", nullable=false, unique=false, insertable=false, updatable=false) + private Float score; + @Column(name="st_passed", nullable=false, unique=false, insertable=false, updatable=false) + private int passed; + @Column(name="st_failed", nullable=false, unique=false, insertable=false, updatable=false) + private int failed; + @Column(name="st_not_attempted", nullable=false, unique=false, insertable=false, updatable=false) + private int notAttempted; + + @Column(name="pg_id", nullable=false, unique=false, insertable=false, updatable=false) + private Long initialLaunchKey; + + public Long getStatementKey() { + return statementKey; + } + + public void setStatementKey(Long statementKey) { + this.statementKey = statementKey; + } + + public Long getRepoKey() { + return repoKey; + } + + public void setRepoKey(Long repoKey) { + this.repoKey = repoKey; + } + + public Long getStudentKey() { + return studentKey; + } + + public void setStudentKey(Long studentKey) { + this.studentKey = studentKey; + } + + public Float getScore() { + return score; + } + + public void setScore(Float score) { + this.score = score; + } + + public int getPassed() { + return passed; + } + + public void setPassed(int passed) { + this.passed = passed; + } + + public int getFailed() { + return failed; + } + + public void setFailed(int failed) { + this.failed = failed; + } + + public int getNotAttempted() { + return notAttempted; + } + + public void setNotAttempted(int notAttempted) { + this.notAttempted = notAttempted; + } + + public Long getInitialLaunchKey() { + return initialLaunchKey; + } + + public void setInitialLaunchKey(Long initialLaunchKey) { + this.initialLaunchKey = initialLaunchKey; + } +} diff --git a/src/main/java/org/olat/modules/coach/model/IdentityResourceKey.java b/src/main/java/org/olat/modules/coach/model/IdentityResourceKey.java new file mode 100644 index 00000000000..c1a20783daa --- /dev/null +++ b/src/main/java/org/olat/modules/coach/model/IdentityResourceKey.java @@ -0,0 +1,64 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.coach.model; + +/** + * + * Initial date: 21.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class IdentityResourceKey { + + private final Long identityKey; + private final Long resourceKey; + + public IdentityResourceKey(Long identityKey, Long resourceKey) { + this.identityKey = identityKey; + this.resourceKey = resourceKey; + } + + public Long getIdentityKey() { + return identityKey; + } + + public Long getResourceKey() { + return resourceKey; + } + + @Override + public int hashCode() { + return (identityKey == null ? 98268 : identityKey.hashCode()) + + (resourceKey == null ? -2634785 : resourceKey.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof IdentityResourceKey) { + IdentityResourceKey key = (IdentityResourceKey)obj; + return identityKey != null && identityKey.equals(key.getIdentityKey()) + && resourceKey != null && resourceKey.equals(key.getResourceKey()); + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/ui/CoachMainController.java b/src/main/java/org/olat/modules/coach/ui/CoachMainController.java index 82f5998d24f..585d238330b 100644 --- a/src/main/java/org/olat/modules/coach/ui/CoachMainController.java +++ b/src/main/java/org/olat/modules/coach/ui/CoachMainController.java @@ -21,15 +21,29 @@ package org.olat.modules.coach.ui; import java.util.List; +import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.panel.Panel; +import org.olat.core.gui.components.tree.GenericTreeModel; +import org.olat.core.gui.components.tree.GenericTreeNode; +import org.olat.core.gui.components.tree.MenuTree; +import org.olat.core.gui.components.tree.TreeModel; +import org.olat.core.gui.components.tree.TreeNode; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.MainLayoutBasicController; import org.olat.core.gui.control.generic.dtabs.Activateable2; -import org.olat.core.gui.control.generic.layout.GenericMainController; +import org.olat.core.id.OLATResourceable; +import org.olat.core.id.Roles; +import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; -import org.olat.core.util.event.GenericEventListener; +import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; +import org.olat.core.util.resource.OresHelper; +import org.olat.core.util.tree.TreeHelper; +import org.olat.util.logging.activity.LoggingResourceable; /** * @@ -40,27 +54,141 @@ import org.olat.core.util.event.GenericEventListener; * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class CoachMainController extends GenericMainController implements GenericEventListener, Activateable2 { +public class CoachMainController extends MainLayoutBasicController implements Activateable2 { + + private Panel content; + private MenuTree menu; + + private GroupListController groupListCtrl; + private UserSearchController userSearchCtrl; + private CourseListController courseListCtrl; + private StudentListController studentListCtrl; + private LayoutMain3ColsController columnLayoutCtr; public CoachMainController(UserRequest ureq, WindowControl control) { super(ureq, control); - init(ureq); - addCssClassToMain("o_coaching"); - getMenuTree().setRootVisible(false); + + menu = new MenuTree(null, "coachMenu", this); + menu.setExpandSelectedNode(false); + menu.setRootVisible(false); + menu.setTreeModel(buildTreeModel(ureq)); + + content = new Panel("content"); + + columnLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), menu, content, "coaching"); + columnLayoutCtr.addCssClassToMain("o_coaching"); + listenTo(columnLayoutCtr); // auto dispose later + putInitialPanel(columnLayoutCtr.getInitialComponent()); } @Override - protected Controller handleOwnMenuTreeEvent(Object uobject, UserRequest ureq) { - return null; + protected void event(UserRequest ureq, Component source, Event event) { + if (source == menu) { + if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED)) { + TreeNode selTreeNode = menu.getSelectedNode(); + String cmd = (String)selTreeNode.getUserObject(); + selectMenuItem(ureq, cmd); + } + } } - + @Override - public void event(Event event) { + protected void doDispose() { // } @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { - super.activate(ureq, entries, state); + if(entries == null || entries.isEmpty()) { + selectMenuItem(ureq, "students"); + } else { + ContextEntry currentEntry = entries.get(0); + String cmd = currentEntry.getOLATResourceable().getResourceableTypeName(); + Controller selectedCtrl = selectMenuItem(ureq, cmd); + if(selectedCtrl instanceof Activateable2) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + ((Activateable2)selectedCtrl).activate(ureq, subEntries, currentEntry.getTransientState()); + } else if(selectedCtrl == null) { + selectMenuItem(ureq, "students"); + } + } + } + + private Controller selectMenuItem(UserRequest ureq, String cmd) { + Controller selectedCtrl = null; + if("students".equals(cmd)) { + if(studentListCtrl == null) { + studentListCtrl = new StudentListController(ureq, getWindowControl()); + listenTo(studentListCtrl); + } + selectedCtrl = studentListCtrl; + } else if("groups".equals(cmd)) { + if(groupListCtrl == null) { + groupListCtrl = new GroupListController(ureq, getWindowControl()); + listenTo(groupListCtrl); + } + selectedCtrl = groupListCtrl; + } else if("courses".equals(cmd)) { + if(courseListCtrl == null) { + courseListCtrl = new CourseListController(ureq, getWindowControl()); + listenTo(courseListCtrl); + } + selectedCtrl = courseListCtrl; + } else if("search".equals(cmd)) { + if(userSearchCtrl == null) { + userSearchCtrl = new UserSearchController(ureq, getWindowControl()); + listenTo(userSearchCtrl); + } + selectedCtrl = userSearchCtrl; + } + + if(selectedCtrl != null) { + TreeNode selTreeNode = TreeHelper.findNodeByUserObject(cmd, menu.getTreeModel().getRootNode()); + if (selTreeNode != null && !selTreeNode.getIdent().equals(menu.getSelectedNodeId())) { + menu.setSelectedNodeId(selTreeNode.getIdent()); + } + columnLayoutCtr.setCol3(selectedCtrl.getInitialComponent()); + + //history + OLATResourceable ores = OresHelper.createOLATResourceableInstance(cmd, 0l); + ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapBusinessPath(ores)); + WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ores, null, getWindowControl()); + addToHistory(ureq, bwControl); + } + return selectedCtrl; + } + + private TreeModel buildTreeModel(UserRequest ureq) { + GenericTreeModel gtm = new GenericTreeModel(); + GenericTreeNode root = new GenericTreeNode(); + gtm.setRootNode(root); + + GenericTreeNode students = new GenericTreeNode(); + students.setUserObject("students"); + students.setTitle(translate("students.menu.title")); + students.setAltText(translate("students.menu.title.alt")); + root.addChild(students); + + GenericTreeNode groups = new GenericTreeNode(); + groups.setUserObject("groups"); + groups.setTitle(translate("groups.menu.title")); + groups.setAltText(translate("groups.menu.title.alt")); + root.addChild(groups); + + GenericTreeNode courses = new GenericTreeNode(); + courses.setUserObject("courses"); + courses.setTitle(translate("courses.menu.title")); + courses.setAltText(translate("courses.menu.title.alt")); + root.addChild(courses); + + Roles roles = ureq.getUserSession().getRoles(); + if(roles.isUserManager() || roles.isOLATAdmin()) { + GenericTreeNode search = new GenericTreeNode(); + search.setUserObject("search"); + search.setTitle(translate("search.menu.title")); + search.setAltText(translate("search.menu.title.alt")); + root.addChild(search); + } + return gtm; } } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/ui/CourseController.java b/src/main/java/org/olat/modules/coach/ui/CourseController.java index bb732d2bc72..76b42e2e8b1 100644 --- a/src/main/java/org/olat/modules/coach/ui/CourseController.java +++ b/src/main/java/org/olat/modules/coach/ui/CourseController.java @@ -20,6 +20,8 @@ package org.olat.modules.coach.ui; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.olat.NewControllerFactory; import org.olat.core.gui.UserRequest; @@ -31,7 +33,6 @@ import org.olat.core.gui.components.table.ColumnDescriptor; import org.olat.core.gui.components.table.CustomRenderColumnDescriptor; import org.olat.core.gui.components.table.DefaultColumnDescriptor; import org.olat.core.gui.components.table.TableController; -import org.olat.core.gui.components.table.TableDataModel; import org.olat.core.gui.components.table.TableEvent; import org.olat.core.gui.components.table.TableGuiConfiguration; import org.olat.core.gui.components.text.TextComponent; @@ -48,10 +49,17 @@ import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.resource.OresHelper; +import org.olat.course.certificate.CertificateEvent; +import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificatesManager; +import org.olat.course.certificate.ui.DownloadCertificateCellRenderer; import org.olat.modules.coach.CoachingService; import org.olat.modules.coach.model.CourseStatEntry; import org.olat.modules.coach.model.EfficiencyStatementEntry; +import org.olat.modules.coach.model.IdentityResourceKey; import org.olat.modules.coach.ui.EfficiencyStatementEntryTableDataModel.Columns; import org.olat.modules.coach.ui.ToolbarController.Position; import org.olat.repository.RepositoryEntry; @@ -67,7 +75,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class CourseController extends BasicController implements Activateable2 { +public class CourseController extends BasicController implements Activateable2, GenericEventListener { private final Link backLink, next, previous; private final Link nextCourse, previousCourse; @@ -76,6 +84,7 @@ public class CourseController extends BasicController implements Activateable2 { private final TableController tableCtr; private final VelocityContainer mainVC; private final VelocityContainer courseDetailsVC; + private EfficiencyStatementEntryTableDataModel model; private CloseableModalController cmc; private ContactController contactCtrl; @@ -88,6 +97,8 @@ public class CourseController extends BasicController implements Activateable2 { private final CourseStatEntry courseStat; @Autowired private CoachingService coachingService; + @Autowired + private CertificatesManager certificatesManager; public CourseController(UserRequest ureq, WindowControl wControl, RepositoryEntry course, CourseStatEntry courseStat, int index, int numOfCourses) { super(ureq, wControl); @@ -104,7 +115,8 @@ public class CourseController extends BasicController implements Activateable2 { tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("student.name", Columns.studentName.ordinal(), "select", getLocale())); tableCtr.addColumnDescriptor(false, new DefaultColumnDescriptor("table.header.course.name", Columns.repoName.ordinal(), "select", getLocale())); tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("table.header.passed", Columns.passed.ordinal(), translate("passed.true"), translate("passed.false"))); - + tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.certificate", Columns.certificate.ordinal(), null, getLocale(), + ColumnDescriptor.ALIGNMENT_LEFT, new DownloadCertificateCellRenderer())); tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", Columns.score.ordinal(), "select", getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.lastScoreDate", Columns.lastModification.ordinal(), "select", getLocale())); @@ -161,6 +173,30 @@ public class CourseController extends BasicController implements Activateable2 { mainVC.put("courseDetails", courseDetailsVC); setDetailsToolbarVisible(false); 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(course.getOlatResource().getKey().equals(ce.getResourceKey())) { + updateCertificate(ce.getCertificateKey()); + } + } + } + + private void updateCertificate(Long certificateKey) { + CertificateLight certificate = certificatesManager.getCertificateLightById(certificateKey); + model.putCertificate(certificate); } public CourseStatEntry getEntry() { @@ -169,7 +205,16 @@ public class CourseController extends BasicController implements Activateable2 { public List<EfficiencyStatementEntry> loadModel() { List<EfficiencyStatementEntry> entries = coachingService.getCourse(getIdentity(), course); - TableDataModel<EfficiencyStatementEntry> model = new EfficiencyStatementEntryTableDataModel(entries); + + Long resourceKey = course.getOlatResource().getKey(); + List<CertificateLight> certificates = certificatesManager.getLastCertificates(course.getOlatResource()); + ConcurrentMap<IdentityResourceKey, CertificateLight> certificateMap = new ConcurrentHashMap<>(); + for(CertificateLight certificate:certificates) { + IdentityResourceKey key = new IdentityResourceKey(certificate.getIdentityKey(), resourceKey); + certificateMap.put(key, certificate); + } + + model = new EfficiencyStatementEntryTableDataModel(entries, certificateMap); tableCtr.setTableDataModel(model); return entries; } @@ -180,11 +225,6 @@ public class CourseController extends BasicController implements Activateable2 { hasChanged = false; } } - - @Override - protected void doDispose() { - // - } @Override protected void event(UserRequest ureq, Component source, Event event) { diff --git a/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java b/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java index 6748e1f724f..de29c54a064 100644 --- a/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java +++ b/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java @@ -21,11 +21,14 @@ package org.olat.modules.coach.ui; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.olat.core.gui.components.table.TableDataModel; import org.olat.course.assessment.UserEfficiencyStatement; +import org.olat.course.certificate.CertificateLight; import org.olat.modules.coach.model.EfficiencyStatementEntry; -import org.olat.modules.coach.ui.ProgressValue; +import org.olat.modules.coach.model.IdentityResourceKey; import org.olat.repository.RepositoryEntry; /** @@ -40,14 +43,28 @@ import org.olat.repository.RepositoryEntry; public class EfficiencyStatementEntryTableDataModel implements TableDataModel<EfficiencyStatementEntry> { private List<EfficiencyStatementEntry> group; + private ConcurrentMap<IdentityResourceKey, CertificateLight> certificateMap; - public EfficiencyStatementEntryTableDataModel(List<EfficiencyStatementEntry> group) { + public EfficiencyStatementEntryTableDataModel(List<EfficiencyStatementEntry> group, + ConcurrentMap<IdentityResourceKey, CertificateLight> certificateMap) { this.group = group; + this.certificateMap = certificateMap; + } + + public boolean contains(IdentityResourceKey key) { + return certificateMap == null ? false : certificateMap.containsKey(key); + } + + public void putCertificate(CertificateLight certificate) { + if(certificateMap != null && certificate != null) { + IdentityResourceKey key = new IdentityResourceKey(certificate.getIdentityKey(), certificate.getOlatResourceKey()); + certificateMap.put(key, certificate); + } } @Override public int getColumnCount() { - return 5; + return 6; } @Override @@ -74,6 +91,14 @@ public class EfficiencyStatementEntryTableDataModel implements TableDataModel<Ef UserEfficiencyStatement s = entry.getUserEfficencyStatement(); return s == null ? null : s.getPassed(); } + case certificate: { + CertificateLight certificate = null; + if(certificateMap != null) { + IdentityResourceKey key = new IdentityResourceKey(entry.getStudentKey(), entry.getCourse().getOlatResource().getKey()); + certificate = certificateMap.get(key); + } + return certificate; + } case progress: { UserEfficiencyStatement s = entry.getUserEfficencyStatement(); if(s == null || s.getTotalNodes() == null) { @@ -112,7 +137,8 @@ public class EfficiencyStatementEntryTableDataModel implements TableDataModel<Ef @Override public EfficiencyStatementEntryTableDataModel createCopyWithEmptyList() { - return new EfficiencyStatementEntryTableDataModel(new ArrayList<EfficiencyStatementEntry>()); + return new EfficiencyStatementEntryTableDataModel(new ArrayList<EfficiencyStatementEntry>(), + new ConcurrentHashMap<IdentityResourceKey, CertificateLight>()); } public static enum Columns { @@ -120,9 +146,9 @@ public class EfficiencyStatementEntryTableDataModel implements TableDataModel<Ef repoName, score, passed, + certificate, progress, - lastModification, - ; + lastModification; public static Columns getValueAt(int ordinal) { if(ordinal >= 0 && ordinal < values().length) { diff --git a/src/main/java/org/olat/modules/coach/ui/GroupController.java b/src/main/java/org/olat/modules/coach/ui/GroupController.java index 603837b2a74..70db4ab5455 100644 --- a/src/main/java/org/olat/modules/coach/ui/GroupController.java +++ b/src/main/java/org/olat/modules/coach/ui/GroupController.java @@ -21,6 +21,8 @@ package org.olat.modules.coach.ui; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.olat.NewControllerFactory; import org.olat.core.gui.UserRequest; @@ -32,7 +34,6 @@ import org.olat.core.gui.components.table.ColumnDescriptor; import org.olat.core.gui.components.table.CustomRenderColumnDescriptor; import org.olat.core.gui.components.table.DefaultColumnDescriptor; import org.olat.core.gui.components.table.TableController; -import org.olat.core.gui.components.table.TableDataModel; import org.olat.core.gui.components.table.TableEvent; import org.olat.core.gui.components.table.TableGuiConfiguration; import org.olat.core.gui.components.text.TextComponent; @@ -49,12 +50,19 @@ import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.resource.OresHelper; +import org.olat.course.certificate.CertificateEvent; +import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificatesManager; +import org.olat.course.certificate.ui.DownloadCertificateCellRenderer; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; import org.olat.modules.coach.CoachingService; import org.olat.modules.coach.model.EfficiencyStatementEntry; import org.olat.modules.coach.model.GroupStatEntry; +import org.olat.modules.coach.model.IdentityResourceKey; import org.olat.modules.coach.ui.EfficiencyStatementEntryTableDataModel.Columns; import org.olat.modules.coach.ui.ToolbarController.Position; import org.springframework.beans.factory.annotation.Autowired; @@ -69,7 +77,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class GroupController extends BasicController implements Activateable2 { +public class GroupController extends BasicController implements Activateable2, GenericEventListener { private final Link backLink, next, previous; private final Link nextGroup, previousGroup; @@ -78,6 +86,7 @@ public class GroupController extends BasicController implements Activateable2 { private final TableController tableCtr; private final VelocityContainer mainVC; private final VelocityContainer groupDetailsVC; + private EfficiencyStatementEntryTableDataModel model; private final ToolbarController toolbar; private EfficiencyStatementDetailsController statementCtrl; @@ -90,6 +99,8 @@ public class GroupController extends BasicController implements Activateable2 { private CoachingService coachingService; @Autowired private BusinessGroupService groupManager; + @Autowired + private CertificatesManager certificatesManager; public GroupController(UserRequest ureq, WindowControl wControl, GroupStatEntry groupStatistic, int index, int numOfGroups) { super(ureq, wControl); @@ -107,6 +118,8 @@ public class GroupController extends BasicController implements Activateable2 { tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("table.header.passed", Columns.passed.ordinal(), translate("passed.true"), translate("passed.false"))); tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", Columns.score.ordinal(),"select", getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); + tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.certificate", Columns.certificate.ordinal(), null, getLocale(), + ColumnDescriptor.ALIGNMENT_LEFT, new DownloadCertificateCellRenderer())); tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.lastScoreDate", Columns.lastModification.ordinal(), "select", getLocale())); listenTo(tableCtr); @@ -164,6 +177,31 @@ public class GroupController extends BasicController implements Activateable2 { setDetailsToolbarVisible(false); 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; + IdentityResourceKey key = new IdentityResourceKey(ce.getOwnerKey(), ce.getResourceKey()); + if(model.contains(key)) { + updateCertificate(ce.getCertificateKey()); + } + } + } + + private void updateCertificate(Long certificateKey) { + CertificateLight certificate = certificatesManager.getCertificateLightById(certificateKey); + model.putCertificate(certificate); } public GroupStatEntry getEntry() { @@ -172,7 +210,14 @@ public class GroupController extends BasicController implements Activateable2 { private List<EfficiencyStatementEntry> loadModel() { List<EfficiencyStatementEntry> allGroup = coachingService.getGroup(group); - TableDataModel<EfficiencyStatementEntry> model = new EfficiencyStatementEntryTableDataModel(allGroup); + + List<CertificateLight> certificates = certificatesManager.getLastCertificates(group); + ConcurrentMap<IdentityResourceKey, CertificateLight> certificateMap = new ConcurrentHashMap<>(); + for(CertificateLight certificate:certificates) { + IdentityResourceKey key = new IdentityResourceKey(certificate.getIdentityKey(), certificate.getOlatResourceKey()); + certificateMap.put(key, certificate); + } + model = new EfficiencyStatementEntryTableDataModel(allGroup, certificateMap); tableCtr.setTableDataModel(model); return allGroup; } @@ -184,11 +229,6 @@ public class GroupController extends BasicController implements Activateable2 { } } - @Override - protected void doDispose() { - // - } - @Override protected void event(UserRequest ureq, Component source, Event event) { if (source == next) { diff --git a/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java b/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java index 19d1b312814..2b99c208f44 100644 --- a/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java +++ b/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java @@ -21,6 +21,8 @@ package org.olat.modules.coach.ui; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.olat.NewControllerFactory; import org.olat.core.gui.UserRequest; @@ -32,7 +34,6 @@ import org.olat.core.gui.components.table.ColumnDescriptor; import org.olat.core.gui.components.table.CustomRenderColumnDescriptor; import org.olat.core.gui.components.table.DefaultColumnDescriptor; import org.olat.core.gui.components.table.TableController; -import org.olat.core.gui.components.table.TableDataModel; import org.olat.core.gui.components.table.TableEvent; import org.olat.core.gui.components.table.TableGuiConfiguration; import org.olat.core.gui.components.text.TextComponent; @@ -50,12 +51,19 @@ import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.mail.ContactList; import org.olat.core.util.mail.ContactMessage; import org.olat.core.util.resource.OresHelper; +import org.olat.course.certificate.CertificateEvent; +import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificatesManager; +import org.olat.course.certificate.ui.DownloadCertificateCellRenderer; import org.olat.modules.co.ContactFormController; import org.olat.modules.coach.CoachingService; import org.olat.modules.coach.model.EfficiencyStatementEntry; +import org.olat.modules.coach.model.IdentityResourceKey; import org.olat.modules.coach.model.StudentStatEntry; import org.olat.modules.coach.ui.EfficiencyStatementEntryTableDataModel.Columns; import org.olat.modules.coach.ui.ToolbarController.Position; @@ -73,7 +81,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class StudentCoursesController extends BasicController implements Activateable2 { +public class StudentCoursesController extends BasicController implements Activateable2, GenericEventListener { private final Link backLink, next, previous; private final Link nextStudent, previousStudent; @@ -82,6 +90,7 @@ public class StudentCoursesController extends BasicController implements Activat private final TableController tableCtr; private final VelocityContainer mainVC; private final VelocityContainer detailsVC; + private EfficiencyStatementEntryTableDataModel model; private CloseableModalController cmc; private ContactFormController contactCtrl; @@ -91,17 +100,22 @@ public class StudentCoursesController extends BasicController implements Activat private boolean hasChanged = false; private final Identity student; + private final boolean fullAccess; private final StudentStatEntry statEntry; @Autowired private UserManager userManager; @Autowired private CoachingService coachingService; + @Autowired + private CertificatesManager certificatesManager; - public StudentCoursesController(UserRequest ureq, WindowControl wControl, StudentStatEntry statEntry, Identity student, int index, int numOfStudents) { + public StudentCoursesController(UserRequest ureq, WindowControl wControl, StudentStatEntry statEntry, + Identity student, int index, int numOfStudents, boolean fullAccess) { super(ureq, wControl); this.student = student; this.statEntry = statEntry; + this.fullAccess = fullAccess; TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setTableEmptyMessage(translate("error.no.found")); @@ -114,6 +128,8 @@ public class StudentCoursesController extends BasicController implements Activat tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("table.header.passed", Columns.passed.ordinal(), translate("passed.true"), translate("passed.false"))); tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", Columns.score.ordinal(), "select", getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); + tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.certificate", Columns.certificate.ordinal(), null, getLocale(), + ColumnDescriptor.ALIGNMENT_LEFT, new DownloadCertificateCellRenderer(student))); tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.progress", Columns.progress.ordinal(), null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, new ProgressRenderer(true, getTranslator()))); tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.lastScoreDate", Columns.lastModification.ordinal(), "select", getLocale())); @@ -172,6 +188,30 @@ public class StudentCoursesController extends BasicController implements Activat setDetailsToolbarVisible(false); 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(student.getKey().equals(ce.getOwnerKey())) { + updateCertificate(ce.getCertificateKey()); + } + } + } + + private void updateCertificate(Long certificateKey) { + CertificateLight certificate = certificatesManager.getCertificateLightById(certificateKey); + model.putCertificate(certificate); } public StudentStatEntry getEntry() { @@ -179,10 +219,18 @@ public class StudentCoursesController extends BasicController implements Activat } private List<EfficiencyStatementEntry> loadModel() { - List<RepositoryEntry> courses = coachingService.getStudentsCourses(getIdentity(), student, 0, -1); + List<RepositoryEntry> courses = fullAccess ? coachingService.getUserCourses(student, 0, -1) + : coachingService.getStudentsCourses(getIdentity(), student, 0, -1); List<EfficiencyStatementEntry> statements = coachingService.getEfficencyStatements(student, courses); + + List<CertificateLight> certificates = certificatesManager.getLastCertificates(student); + ConcurrentMap<IdentityResourceKey, CertificateLight> certificateMap = new ConcurrentHashMap<>(); + for(CertificateLight certificate:certificates) { + IdentityResourceKey key = new IdentityResourceKey(student.getKey(), certificate.getOlatResourceKey()); + certificateMap.put(key, certificate); + } - TableDataModel<EfficiencyStatementEntry> model = new EfficiencyStatementEntryTableDataModel(statements); + model = new EfficiencyStatementEntryTableDataModel(statements, certificateMap); tableCtr.setTableDataModel(model); return statements; } @@ -195,11 +243,6 @@ public class StudentCoursesController extends BasicController implements Activat } } - @Override - protected void doDispose() { - // - } - @Override protected void event(UserRequest ureq, Component source, Event event) { if (source == next) { diff --git a/src/main/java/org/olat/modules/coach/ui/StudentListController.java b/src/main/java/org/olat/modules/coach/ui/StudentListController.java index 13a8e960b38..987f4fa1956 100644 --- a/src/main/java/org/olat/modules/coach/ui/StudentListController.java +++ b/src/main/java/org/olat/modules/coach/ui/StudentListController.java @@ -211,7 +211,7 @@ public class StudentListController extends BasicController implements Activateab WindowControl bwControl = addToHistory(ureq, ores, null); int index = tableCtr.getIndexOfSortedObject(studentStat); - studentCtrl = new StudentCoursesController(ureq, bwControl, studentStat, student, index, tableCtr.getRowCount()); + studentCtrl = new StudentCoursesController(ureq, bwControl, studentStat, student, index, tableCtr.getRowCount(), false); listenTo(studentCtrl); content.setContent(studentCtrl.getInitialComponent()); diff --git a/src/main/java/org/olat/modules/coach/ui/UserListController.java b/src/main/java/org/olat/modules/coach/ui/UserListController.java new file mode 100644 index 00000000000..c477da66c58 --- /dev/null +++ b/src/main/java/org/olat/modules/coach/ui/UserListController.java @@ -0,0 +1,222 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.coach.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.IdentityRef; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.panel.Panel; +import org.olat.core.gui.components.table.ColumnDescriptor; +import org.olat.core.gui.components.table.CustomRenderColumnDescriptor; +import org.olat.core.gui.components.table.DefaultColumnDescriptor; +import org.olat.core.gui.components.table.TableController; +import org.olat.core.gui.components.table.TableDataModel; +import org.olat.core.gui.components.table.TableEvent; +import org.olat.core.gui.components.table.TableGuiConfiguration; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; +import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; +import org.olat.core.id.UserConstants; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; +import org.olat.core.util.resource.OresHelper; +import org.olat.modules.coach.CoachingService; +import org.olat.modules.coach.model.StudentStatEntry; +import org.olat.modules.coach.ui.StudentsTableDataModel.Columns; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 20.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class UserListController extends BasicController implements Activateable2 { + + private final Link back; + private final Panel content; + private final VelocityContainer listVC; + private final TableController tableCtr; + private StudentCoursesController studentCtrl; + + private boolean hasChanged; + private List<IdentityRef> identityRefs; + private final Map<Long,String> identityFullNameMap = new HashMap<Long,String>(); + + @Autowired + private BaseSecurity securityManager; + @Autowired + private CoachingService coachingService; + + public UserListController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + + TableGuiConfiguration tableConfig = new TableGuiConfiguration(); + tableConfig.setTableEmptyMessage(translate("error.no.found")); + tableConfig.setDownloadOffered(true); + tableConfig.setPreferencesOffered(true, "userListController"); + + tableCtr = new TableController(tableConfig, ureq, getWindowControl(), null, null, null, null, true, getTranslator()); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("student.name", Columns.name.ordinal(), "select", getLocale())); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.countCourses", Columns.countCourse.ordinal(), null, getLocale())); + tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.login", Columns.initialLaunch.ordinal(), null, getLocale(), + ColumnDescriptor.ALIGNMENT_LEFT, new LightIconRenderer())); + tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.passed", Columns.countPassed.ordinal(), null, getLocale(), + ColumnDescriptor.ALIGNMENT_LEFT, new ProgressRenderer(false, getTranslator()))); + listenTo(tableCtr); + + listVC = createVelocityContainer("user_list"); + listVC.put("userList", tableCtr.getInitialComponent()); + back = LinkFactory.createLinkBack(listVC, this); + listVC.put("back", back); + + content = new Panel("studentList"); + content.setContent(listVC); + putInitialPanel(content); + } + + @Override + protected void doDispose() { + // + } + + private void reloadModel() { + if(hasChanged) { + loadModel(); + hasChanged = false; + } + } + + private void loadModel() { + List<StudentStatEntry> stats = coachingService.getUsersStatistics(identityRefs); + TableDataModel<StudentStatEntry> model = new StudentsTableDataModel(stats, identityFullNameMap); + tableCtr.setTableDataModel(model); + } + + public void loadModel(List<Identity> identities) { + List<IdentityRef> refs = new ArrayList<>(identities.size()); + for(Identity identity:identities) { + String fullName = identity.getUser().getProperty(UserConstants.FIRSTNAME, getLocale()) + " " + identity.getUser().getProperty(UserConstants.LASTNAME, getLocale()); + identityFullNameMap.put(identity.getKey(), fullName); + refs.add(new IdentityRefImpl(identity.getKey())); + } + identityRefs = refs; + loadModel(); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == back) { + fireEvent(ureq, Event.BACK_EVENT); + } + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == tableCtr) { + if(event instanceof TableEvent) { + TableEvent e = (TableEvent) event; + if("select".equals(e.getActionId())) { + StudentStatEntry studentStat = (StudentStatEntry)tableCtr.getTableDataModel().getObject(e.getRowId()); + selectStudent(ureq, studentStat); + } + } + } else if(event == Event.BACK_EVENT) { + reloadModel(); + content.setContent(listVC); + removeAsListenerAndDispose(studentCtrl); + studentCtrl = null; + addToHistory(ureq); + } else if (source == studentCtrl) { + if(event == Event.CHANGED_EVENT) { + hasChanged = true; + } else if("next.student".equals(event.getCommand())) { + nextStudent(ureq); + } else if("previous.student".equals(event.getCommand())) { + previousStudent(ureq); + } + } + super.event(ureq, source, event); + } + + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + //do nothing + } + + protected void previousStudent(UserRequest ureq) { + StudentStatEntry currentEntry = studentCtrl.getEntry(); + int previousIndex = tableCtr.getIndexOfSortedObject(currentEntry) - 1; + if(previousIndex < 0 || previousIndex >= tableCtr.getRowCount()) { + previousIndex = tableCtr.getRowCount() - 1; + } + StudentStatEntry previousEntry = (StudentStatEntry)tableCtr.getSortedObjectAt(previousIndex); + selectStudent(ureq, previousEntry); + } + + protected void nextStudent(UserRequest ureq) { + StudentStatEntry currentEntry = studentCtrl.getEntry(); + int nextIndex = tableCtr.getIndexOfSortedObject(currentEntry) + 1; + if(nextIndex < 0 || nextIndex >= tableCtr.getRowCount()) { + nextIndex = 0; + } + StudentStatEntry nextEntry = (StudentStatEntry)tableCtr.getSortedObjectAt(nextIndex); + selectStudent(ureq, nextEntry); + } + + protected void selectStudent(UserRequest ureq, StudentStatEntry studentStat) { + removeAsListenerAndDispose(studentCtrl); + Identity student = securityManager.loadIdentityByKey(studentStat.getStudentKey()); + OLATResourceable ores = OresHelper.createOLATResourceableInstance(Identity.class, student.getKey()); + WindowControl bwControl = addToHistory(ureq, ores, null); + + int index = tableCtr.getIndexOfSortedObject(studentStat); + studentCtrl = new StudentCoursesController(ureq, bwControl, studentStat, student, index, tableCtr.getRowCount(), true); + + listenTo(studentCtrl); + content.setContent(studentCtrl.getInitialComponent()); + } + + private static class IdentityRefImpl implements IdentityRef { + private final Long key; + + public IdentityRefImpl(Long key) { + this.key = key; + } + + @Override + public Long getKey() { + return key; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/ui/UserSearchController.java b/src/main/java/org/olat/modules/coach/ui/UserSearchController.java new file mode 100644 index 00000000000..41fa0af3bf0 --- /dev/null +++ b/src/main/java/org/olat/modules/coach/ui/UserSearchController.java @@ -0,0 +1,110 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.coach.ui; + +import java.util.List; +import java.util.Map; + +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.SearchIdentityParams; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.panel.StackedPanel; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.id.Identity; +import org.olat.modules.coach.CoachingService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 20.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class UserSearchController extends BasicController { + + private final UserSearchForm searchForm; + private final UserListController userListCtrl; + private final StackedPanel mainPanel; + + @Autowired + private BaseSecurity securityManager; + @Autowired + private CoachingService coachingService; + + public UserSearchController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + + //search form + searchForm = new UserSearchForm(ureq, getWindowControl()); + listenTo(searchForm); + userListCtrl = new UserListController(ureq, getWindowControl()); + listenTo(userListCtrl); + + mainPanel = putInitialPanel(searchForm.getInitialComponent()); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(searchForm == source) { + if(event == Event.DONE_EVENT) { + doSearch(); + } + } else if(userListCtrl == source) { + if(event == Event.BACK_EVENT) { + mainPanel.popContent(); + } + } + super.event(ureq, source, event); + } + + private void doSearch() { + String login = searchForm.getLogin(); + Map<String,String> searchProps = searchForm.getSearchProperties(); + + SearchIdentityParams params = new SearchIdentityParams(); + params.setLogin(login); + params.setUserProperties(searchProps); + params.setUserPropertiesAsIntersectionSearch(true); + params.setStatus(Identity.STATUS_VISIBLE_LIMIT); + + long count = securityManager.countIdentitiesByPowerSearch(params); + if(count > 501) { + showWarning("error.search.form.too.many"); + } else { + List<Identity> identities = securityManager.getIdentitiesByPowerSearch(params, 0, 501); + userListCtrl.loadModel(identities); + mainPanel.pushContent(userListCtrl.getInitialComponent()); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/ui/UserSearchForm.java b/src/main/java/org/olat/modules/coach/ui/UserSearchForm.java new file mode 100644 index 00000000000..5e8dea65ec2 --- /dev/null +++ b/src/main/java/org/olat/modules/coach/ui/UserSearchForm.java @@ -0,0 +1,175 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.coach.ui; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.olat.basesecurity.BaseSecurityModule; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.EmailProperty; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 20.11.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class UserSearchForm extends FormBasicController { + + private static final String PROPS_IDENTIFIER = UserSearchForm.class.getName(); + + private final boolean adminProps; + private FormLink searchButton; + + private TextElement login; + private List<UserPropertyHandler> userPropertyHandlers; + private Map <String,FormItem>propFormItems; + + @Autowired + private UserManager userManager; + @Autowired + private BaseSecurityModule securityModule; + + /** + * @param name + * @param cancelbutton + * @param isAdmin if true, no field must be filled in at all, otherwise + * validation takes place + */ + public UserSearchForm(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); + adminProps = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + login = uifactory.addTextElement("login", "search.form.login", 128, "", formLayout); + login.setVisible(adminProps); + + userPropertyHandlers = userManager.getUserPropertyHandlersFor(PROPS_IDENTIFIER, adminProps); + + propFormItems = new HashMap<String,FormItem>(); + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler != null) { + FormItem fi = userPropertyHandler.addFormItem(getLocale(), null, getClass().getCanonicalName(), false, formLayout); + // DO NOT validate email field => see OLAT-3324, OO-155, OO-222 + if (userPropertyHandler instanceof EmailProperty && fi instanceof TextElement) { + TextElement textElement = (TextElement)fi; + textElement.setItemValidatorProvider(null); + } + + propFormItems.put(userPropertyHandler.getName(), fi); + } + } + + FormLayoutContainer buttonCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonCont); + uifactory.addFormSubmitButton("search", buttonCont); + } + + public String getLogin() { + return login.isVisible() && StringHelper.containsNonWhitespace(login.getValue()) + ? login.getValue() : null; + } + + public Map<String,String> getSearchProperties() { + Map<String, String> userPropertiesSearch = new HashMap<String, String>(); + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler != null) { + FormItem ui = propFormItems.get(userPropertyHandler.getName()); + String uiValue = userPropertyHandler.getStringValue(ui); + if (StringHelper.containsNonWhitespace(uiValue) && !uiValue.equals("-")) { + userPropertiesSearch.put(userPropertyHandler.getName(), uiValue); + } + } + } + return userPropertiesSearch; + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + return validate() & super.validateFormLogic(ureq); + } + + private boolean validate() { + boolean atLeastOne = false; + if(login.isVisible()) { + atLeastOne = StringHelper.containsNonWhitespace(login.getValue()); + } + + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler != null) { + FormItem ui = propFormItems.get(userPropertyHandler.getName()); + String uiValue = userPropertyHandler.getStringValue(ui); + if (StringHelper.containsNonWhitespace(uiValue) && !uiValue.equals("-")) { + atLeastOne = true; + } + } + } + + if(!atLeastOne) { + showWarning("error.search.form.notempty"); + } + return atLeastOne; + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == searchButton) { + if(validate()) { + fireEvent (ureq, Event.DONE_EVENT); + } + } + } + + @Override + protected void formOK(UserRequest ureq) { + fireEvent (ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent (ureq, Event.CANCELLED_EVENT); + } + + @Override + protected void doDispose() { + // + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/ui/_content/user_list.html b/src/main/java/org/olat/modules/coach/ui/_content/user_list.html new file mode 100644 index 00000000000..9daccce8a41 --- /dev/null +++ b/src/main/java/org/olat/modules/coach/ui/_content/user_list.html @@ -0,0 +1,4 @@ +<div class="o_block"> + $r.render("back") +</div> +$r.render("userList") \ No newline at end of file diff --git a/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties index 7fe6e56a0b0..4aeb1e54dae 100644 --- a/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties @@ -35,6 +35,7 @@ passed.false=$org.olat.course.assessment\:passed.false table.header.show=$org.olat.course.assessment\:table.header.show table.header.lastScoreDate=$org.olat.course.assessment\:table.header.lastScoreDate table.action.show=$org.olat.course.assessment\:table.action.show +table.header.certificate=Zertifikat details.statement=Leistungnachweis details.assessment=Bewertungswerkzeug error.no.found=Nichts gefunden @@ -50,4 +51,10 @@ previous.group=\$:previous open.course=\$:open next.course=\$:next previous.course=\$:previous -tooltip.of={0} von {1} \ No newline at end of file +tooltip.of={0} von {1} +search=Suchen +search.menu.title=Benutzersuche +search.menu.title.alt=Benutzersuche +search.form.login=Benutzername +error.search.form.notempty=$org.olat.admin.user:error.search.form.notempty +error.search.form.too.many=Zu viele Treffer wurde gefunden. Bitte beschränken Sie ihre Suche. \ No newline at end of file diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml index 7ee620bcf3c..674343e9bbc 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml +++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml @@ -992,6 +992,28 @@ </bean> </entry> + <entry key="org.olat.modules.coach.ui.UserSearchForm"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Properties used in the user search of the coaching tool." /> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyEmail" /> + <ref bean="userPropertyZipCode" /> + <ref bean="userPropertyRegion" /> + <ref bean="userPropertyCity" /> + <ref bean="userPropertyCountry" /> + <ref bean="userPropertyInstitutionalName" /> + <ref bean="userPropertyInstitutionalUserIdentifier" /> + <ref bean="userPropertyInstitutionalEmail" /> + <ref bean="userPropertyOrgUnit" /> + <ref bean="userPropertyStudySubject" /> + </list> + </property> + </bean> + </entry> + <!-- Default configuration in case nothing else matches. --> 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 8c71525607c..0d59add2f9f 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 @@ -36,7 +36,97 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid); alter table o_gp_business add column allowtoleave boolean default 1; +-- coaching +create or replace view o_as_eff_statement_identity_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = 1 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +create or replace view o_as_eff_statement_students_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = 1 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +create or replace view o_as_eff_statement_courses_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_re.displayname as re_name, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = 1 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_re.displayname, sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +create or replace view o_as_eff_statement_groups_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_re.displayname as re_name, + sg_bg.group_id as bg_id, + sg_bg.groupname as bg_name, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = 1 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_gp_business as sg_bg on (sg_bg.fk_group_id=togroup.fk_group_id) + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_re.displayname, sg_bg.group_id, sg_bg.groupname, + sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +--clean up drop view o_qp_item_shared_v; drop view o_qp_item_pool_v; drop view o_qp_item_author_v; diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 22d7a92a2b9..b17d9a63f0a 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1524,7 +1524,26 @@ create view o_gp_contactext_v as ( ); -- coaching -create view o_as_eff_statement_students_v as ( +create or replace view o_as_eff_statement_identity_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = 1 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +create or replace view o_as_eff_statement_students_v as ( select sg_re.repositoryentry_id as re_id, sg_coach.fk_identity_id as tutor_id, @@ -1537,15 +1556,15 @@ create view o_as_eff_statement_students_v as ( pg_initial_launch.id as pg_id from o_repositoryentry as sg_re inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) - inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) group by sg_re.repositoryentry_id, sg_coach.fk_identity_id, sg_participant.fk_identity_id, - sg_statement.id, sg_statement.score, pg_initial_launch.id + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id ); -create view o_as_eff_statement_courses_v as ( +create or replace view o_as_eff_statement_courses_v as ( select sg_re.repositoryentry_id as re_id, sg_re.displayname as re_name, @@ -1559,15 +1578,15 @@ create view o_as_eff_statement_courses_v as ( pg_initial_launch.id as pg_id from o_repositoryentry as sg_re inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) - inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) group by sg_re.repositoryentry_id, sg_re.displayname, sg_coach.fk_identity_id, sg_participant.fk_identity_id, - sg_statement.id, sg_statement.score, pg_initial_launch.id + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id ); -create view o_as_eff_statement_groups_v as ( +create or replace view o_as_eff_statement_groups_v as ( select sg_re.repositoryentry_id as re_id, sg_re.displayname as re_name, @@ -1584,13 +1603,13 @@ create view o_as_eff_statement_groups_v as ( from o_repositoryentry as sg_re inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) inner join o_gp_business as sg_bg on (sg_bg.fk_group_id=togroup.fk_group_id) - inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) group by sg_re.repositoryentry_id, sg_re.displayname, sg_bg.group_id, sg_bg.groupname, sg_coach.fk_identity_id, sg_participant.fk_identity_id, - sg_statement.id, sg_statement.score, pg_initial_launch.id + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id ); -- instant messaging 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 26b280af8c6..8400f10795c 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 @@ -36,6 +36,96 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid); alter table o_gp_business add allowtoleave number default 1 not null; +--coaching +create or replace view o_as_eff_statement_identity_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = 1 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry sg_re + inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_participant.fk_identity_id, + sg_statement.passed, sg_statement.id, sg_statement.score, pg_initial_launch.id +); + +create or replace view o_as_eff_statement_students_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed > 0 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry sg_re + inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.passed, sg_statement.id, sg_statement.score, pg_initial_launch.id +); + +create or replace view o_as_eff_statement_courses_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_re.displayname as re_name, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed > 0 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry sg_re + inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_re.displayname, sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.passed, sg_statement.id, sg_statement.score, pg_initial_launch.id +); + +create or replace view o_as_eff_statement_groups_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_re.displayname as re_name, + sg_bg.group_id as bg_id, + sg_bg.groupname as bg_name, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed > 0 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry sg_re + inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_gp_business sg_bg on (sg_bg.fk_group_id=togroup.fk_group_id) + inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_re.displayname, sg_bg.group_id, sg_bg.groupname, + sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.passed, sg_statement.id, sg_statement.score, pg_initial_launch.id +); + + drop view o_qp_item_shared_v; drop view o_qp_item_pool_v; diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index 3996f9b2e23..91e0ed12935 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1575,6 +1575,24 @@ create view o_gp_contactext_v as ( (bgroup.participantsintern>0 and bg_member.g_role='participant') ); +create view o_as_eff_statement_identity_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = 1 then 1 else 0 end) as st_passed, + (case when sg_statement.passed = 0 then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry sg_re + inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_participant.fk_identity_id, + sg_statement.passed, sg_statement.id, sg_statement.score, pg_initial_launch.id +); create view o_as_eff_statement_students_v as ( select @@ -1589,7 +1607,7 @@ create view o_as_eff_statement_students_v as ( pg_initial_launch.id as pg_id from o_repositoryentry sg_re inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) - inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) @@ -1611,7 +1629,7 @@ create view o_as_eff_statement_courses_v as ( pg_initial_launch.id as pg_id from o_repositoryentry sg_re inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) - inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) @@ -1636,7 +1654,7 @@ create view o_as_eff_statement_groups_v as ( from o_repositoryentry sg_re inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) inner join o_gp_business sg_bg on (sg_bg.fk_group_id=togroup.fk_group_id) - inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) 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 e19b149045a..f8ab1e3ea8b 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 @@ -36,6 +36,99 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid); alter table o_gp_business add column allowtoleave bool not null default true; +--coaching +create view o_as_eff_statement_identity_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = true then 1 else 0 end) as st_passed, + (case when sg_statement.passed = false then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +drop view o_as_eff_statement_students_v; +create view o_as_eff_statement_students_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = true then 1 else 0 end) as st_passed, + (case when sg_statement.passed = false then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +drop view o_as_eff_statement_courses_v; +create view o_as_eff_statement_courses_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_re.displayname as re_name, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = true then 1 else 0 end) as st_passed, + (case when sg_statement.passed = false then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_re.displayname, sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + +drop view o_as_eff_statement_groups_v; +create view o_as_eff_statement_groups_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_re.displayname as re_name, + sg_bg.group_id as bg_id, + sg_bg.groupname as bg_name, + sg_coach.fk_identity_id as tutor_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = true then 1 else 0 end) as st_passed, + (case when sg_statement.passed = false then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_gp_business as sg_bg on (sg_bg.fk_group_id=togroup.fk_group_id) + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_re.displayname, sg_bg.group_id, sg_bg.groupname, + sg_coach.fk_identity_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + + drop view o_qp_item_shared_v; drop view o_qp_item_pool_v; diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index e678c844926..2fb4603d1c5 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1525,6 +1525,25 @@ create view o_gp_contactext_v as ( ); -- coaching +create view o_as_eff_statement_identity_v as ( + select + sg_re.repositoryentry_id as re_id, + sg_participant.fk_identity_id as student_id, + sg_statement.id as st_id, + (case when sg_statement.passed = true then 1 else 0 end) as st_passed, + (case when sg_statement.passed = false then 1 else 0 end) as st_failed, + (case when sg_statement.passed is null then 1 else 0 end) as st_not_attempted, + sg_statement.score as st_score, + pg_initial_launch.id as pg_id + from o_repositoryentry as sg_re + inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) + inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') + left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) + left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) + group by sg_re.repositoryentry_id, sg_participant.fk_identity_id, + sg_statement.id, sg_statement.passed, sg_statement.score, pg_initial_launch.id +); + create view o_as_eff_statement_students_v as ( select sg_re.repositoryentry_id as re_id, @@ -1538,7 +1557,7 @@ create view o_as_eff_statement_students_v as ( pg_initial_launch.id as pg_id from o_repositoryentry as sg_re inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) - inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) @@ -1560,7 +1579,7 @@ create view o_as_eff_statement_courses_v as ( pg_initial_launch.id as pg_id from o_repositoryentry as sg_re inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) - inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) @@ -1585,7 +1604,7 @@ create view o_as_eff_statement_groups_v as ( from o_repositoryentry as sg_re inner join o_re_to_group as togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) inner join o_gp_business as sg_bg on (sg_bg.fk_group_id=togroup.fk_group_id) - inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role='coach') + inner join o_bs_group_member as sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role in ('owner','coach')) inner join o_bs_group_member as sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') left join o_as_eff_statement as sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) left join o_as_user_course_infos as pg_initial_launch on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id) diff --git a/src/main/webapp/static/themes/light/modules/_navbar.scss b/src/main/webapp/static/themes/light/modules/_navbar.scss index 23b26ac10dd..33270aaaec4 100644 --- a/src/main/webapp/static/themes/light/modules/_navbar.scss +++ b/src/main/webapp/static/themes/light/modules/_navbar.scss @@ -7,6 +7,10 @@ position: relative; } } + +a.o_disabled.navbar-text { + margin:0; +} .o_navbar { position: relative; diff --git a/src/main/webapp/static/themes/light/modules/_toolbar.scss b/src/main/webapp/static/themes/light/modules/_toolbar.scss index b1567ec76b9..2dc2708d3d4 100644 --- a/src/main/webapp/static/themes/light/modules/_toolbar.scss +++ b/src/main/webapp/static/themes/light/modules/_toolbar.scss @@ -95,7 +95,7 @@ } } } - + .o_tools { margin-top: $o-toolbar-tools-margin-top-md; margin-bottom: $o-toolbar-tools-margin-bottom-md; diff --git a/src/main/webapp/static/themes/light/theme.css b/src/main/webapp/static/themes/light/theme.css index cd5c98c7309..45a12438708 100644 --- a/src/main/webapp/static/themes/light/theme.css +++ b/src/main/webapp/static/themes/light/theme.css @@ -60,7 +60,7 @@ fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100% .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_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_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}a.o_disabled.navbar-text{margin:0}.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}} body{overflow-x:hidden}.o_container_offcanvas{position:relative;max-width:1324px;-webkit-transition:all .25s ease-in-out;-moz-transition:all .25s ease-in-out;-o-transition:all .25s ease-in-out;-m-transition:all .25s ease-in-out;transition:all .25s ease-in-out}@media screen and (min-width: 1324px) and (max-width: 1574px){body.o_offcanvas_right_visible .o_container_offcanvas{left:-125px;max-width:1074px}}@media screen and (min-width: 1574px) and (max-width: 1824px){body.o_offcanvas_right_visible .o_container_offcanvas{left:-125px}}#o_offcanvas_right{position:absolute;top:0;right:-250px;width:250px;padding:15px 15px;background-color:#222;color:#777;border:1px solid #090909;-webkit-box-shadow:0px 0px 4px 3px rgba(0,0,0,0.25);box-shadow:0px 0px 4px 3px rgba(0,0,0,0.25);min-height:100%;z-index:10;display:none}#o_offcanvas_right:before,#o_offcanvas_right:after{content:" ";display:table}#o_offcanvas_right:after{clear:both}@media screen and (max-width: 767px){.row-offcanvas{position:relative;-webkit-transition:all .25s ease-out;-moz-transition:all .25s ease-out;transition:all .25s ease-out}.row-offcanvas-right{right:0}.row-offcanvas-right .sidebar-offcanvas{right:-50%}.row-offcanvas-right.active{right:50%}.row-offcanvas-left{left:0}.row-offcanvas-left .sidebar-offcanvas{left:-50%}.row-offcanvas-left.active{left:50%}.sidebar-offcanvas{position:absolute;top:0;width:50%}}.o_info,.o_form .o_info,.o_togglebox_wrapper div.o_togglebox_content,div.o_qti_item_itemfeedback{margin:20px 0;padding:20px;border-left:3px solid #777;background-color:#eee}.o_info h2,.o_info h3,.o_info h4,.o_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_info h2,.o_info h5,.o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback h2,.o_form .o_info h3,.o_togglebox_wrapper div.o_togglebox_content h3,div.o_qti_item_itemfeedback h3,.o_form .o_info h4,.o_togglebox_wrapper div.o_togglebox_content h4,div.o_qti_item_itemfeedback h4,.o_form .o_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback .o_cal .fc-header-title h2,.o_cal .fc-header-title div.o_qti_item_itemfeedback h2,.o_form .o_info h5,.o_togglebox_wrapper div.o_togglebox_content h5,div.o_qti_item_itemfeedback h5{color:#777}.o_note,.o_form .o_desc,.o_course_run .o_statusinfo,.o_course_stats .o_desc{margin:20px 0;padding:20px;border-left:3px solid #31708f;background-color:#d9edf7}.o_note h2,.o_note h3,.o_note h4,.o_note .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_note h2,.o_note h5,.o_form .o_desc h2,.o_course_run .o_statusinfo h2,.o_course_stats .o_desc h2,.o_form .o_desc h3,.o_course_run .o_statusinfo h3,.o_course_stats .o_desc h3,.o_form .o_desc h4,.o_course_run .o_statusinfo h4,.o_course_stats .o_desc h4,.o_form .o_desc .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_desc h2,.o_course_run .o_statusinfo .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_run .o_statusinfo h2,.o_course_stats .o_desc .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_stats .o_desc h2,.o_form .o_desc h5,.o_course_run .o_statusinfo h5,.o_course_stats .o_desc h5{color:#31708f}.o_important,.o_bc_empty,.o_course_run .o_no_scoreinfo{margin:20px 0;padding:20px;border-left:3px solid #f4d000;background-color:#fff1a4}.o_important h2,.o_important h3,.o_important h4,.o_important .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_important h2,.o_important h5,.o_bc_empty h2,.o_course_run .o_no_scoreinfo h2,.o_bc_empty h3,.o_course_run .o_no_scoreinfo h3,.o_bc_empty h4,.o_course_run .o_no_scoreinfo h4,.o_bc_empty .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_bc_empty h2,.o_course_run .o_no_scoreinfo .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_run .o_no_scoreinfo h2,.o_bc_empty h5,.o_course_run .o_no_scoreinfo h5{color:#f4d000}.o_success{margin:20px 0;padding:20px;border-left:3px solid #3c763d;background-color:#dff0d8}.o_success h2,.o_success h3,.o_success h4,.o_success .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_success h2,.o_success h5{color:#3c763d}.o_warning,.o_form .o_warning{margin:20px 0;padding:20px;border-left:3px solid #8a6d3b;background-color:#fcf8e3}.o_warning h2,.o_warning h3,.o_warning h4,.o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_warning h2,.o_warning h5,.o_form .o_warning h2,.o_form .o_warning h3,.o_form .o_warning h4,.o_form .o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_warning h2,.o_form .o_warning h5{color:#8a6d3b}.o_error{margin:20px 0;padding:20px;border-left:3px solid #a94442;background-color:#f2dede}.o_error h2,.o_error h3,.o_error h4,.o_error .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_error h2,.o_error h5{color:#a94442}div.o_callout_overlay{position:fixed;top:0;left:0;width:100%;height:100%;zoom:1;background:#000;opacity:0;filter:alpha(opacity=0)}.o_alert_info{position:fixed;top:-100%;left:0;display:none;z-index:2000;width:100%;text-align:center}.o_alert_info .alert{position:relative;width:auto;margin:0 auto;text-align:left;-webkit-box-shadow:0px 1px 5px -1px rgba(0,0,0,0.15);box-shadow:0px 1px 5px -1px rgba(0,0,0,0.15)}.o_alert_info .alert .o_icon_close{float:right;color:#777}.o_alert_info .alert .o_icon_close:hover{color:#555}@media (min-width: 768px){.o_alert_info .alert{width:600px}} #o_msg_sticky,#o_msg_sticky_preview{position:relative;color:#a94442;background-color:#f2dede;border:1px solid #ebccd1;padding:10px 16px 10px 60px;min-height:40px;margin:-20px 0 20px 0}#o_msg_sticky .o_icon_info_msg,#o_msg_sticky_preview .o_icon_info_msg{position:absolute;left:10px;top:5px;font-size:40px}@media (min-width: 768px){.modal .o_modal_fullwidth{width:90%}}@media (min-width: 992px){.modal .o_modal_fullwidth{width:80%}}.modal .modal-header h4,.modal .modal-header .o_cal .fc-header-title h2,.o_cal .fc-header-title .modal .modal-header h2{color:#428bca;font-weight:500;font-family:inherit;line-height:1.1}.o_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_tree a{color:#777;background-color:none}.o_tree a:hover,.o_tree a:focus{color:#333;background-color:#f8f8f8}.o_tree .o_insertion_point>a>span{padding:5px;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.o_tree .o_insertion_source>a>span.o_tree_item,.o_tree .o_insertion_source>a>span.o_dnd_item{border-bottom:solid #f90 4px;background-color:#fefbf6}.o_tree ul{margin:0;padding:0;list-style-type:none}.o_tree ul li{margin:0;padding:0;white-space:nowrap}.o_tree ul li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_tree ul li div.popover{position:absolute;left:auto;right:0}.o_tree ul li div a.o_tree_oc_l0{position:absolute;top:10px;left:-4px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l0,.o_tree ul .o_tree_level_close.b_tree_oc_l0{z-index:10}.o_tree ul li div a.o_tree_oc_l1{position:absolute;top:10px;left:11px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l1,.o_tree ul .o_tree_level_close.b_tree_oc_l1{z-index:10}.o_tree ul li div a.o_tree_oc_l2{position:absolute;top:10px;left:26px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l2,.o_tree ul .o_tree_level_close.b_tree_oc_l2{z-index:10}.o_tree ul li div a.o_tree_oc_l3{position:absolute;top:10px;left:41px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l3,.o_tree ul .o_tree_level_close.b_tree_oc_l3{z-index:10}.o_tree ul li div a.o_tree_oc_l4{position:absolute;top:10px;left:56px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l4,.o_tree ul .o_tree_level_close.b_tree_oc_l4{z-index:10}.o_tree ul li div a.o_tree_oc_l5{position:absolute;top:10px;left:71px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l5,.o_tree ul .o_tree_level_close.b_tree_oc_l5{z-index:10}.o_tree ul li div a.o_tree_oc_l6{position:absolute;top:10px;left:86px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l6,.o_tree ul .o_tree_level_close.b_tree_oc_l6{z-index:10}.o_tree ul li div a.o_tree_oc_l7{position:absolute;top:10px;left:101px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l7,.o_tree ul .o_tree_level_close.b_tree_oc_l7{z-index:10}.o_tree ul li div a.o_tree_oc_l8{position:absolute;top:10px;left:116px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l8,.o_tree ul .o_tree_level_close.b_tree_oc_l8{z-index:10}.o_tree ul li div a.o_tree_oc_l9{position:absolute;top:10px;left:131px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l9,.o_tree ul .o_tree_level_close.b_tree_oc_l9{z-index:10}.o_tree ul li div a.o_tree_oc_l10{position:absolute;top:10px;left:146px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l10,.o_tree ul .o_tree_level_close.b_tree_oc_l10{z-index:10}.o_tree ul li div a.o_tree_oc_l11{position:absolute;top:10px;left:161px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l11,.o_tree ul .o_tree_level_close.b_tree_oc_l11{z-index:10}.o_tree ul li div span.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_tree ul li div span.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_tree ul li div span.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_tree ul li div span.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_tree ul li div span.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_tree ul li div span.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_tree ul li div span.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_tree ul li div span.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_tree ul li div span.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_tree ul li div span.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_tree ul li div span.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_tree ul li div span.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_tree ul span.o_tree_leaf{display:none}.o_tree ul span.o_tree_link>input[type=checkbox]{margin-right:5px}.o_tree ul li .badge{position:absolute;font-size:70%}.o_tree ul li .badge:before{content:none}.o_tree ul li .badge.o_badge_1{top:3px;right:1px}.o_tree ul li .badge.o_badge_2{bottom:3px;right:1px}.o_tree ul li .badge.o_badge_3{top:3px;right:25px}.o_tree ul li .badge.o_badge_4{bottom:3px;right:25px}.o_tree ul li div.o_dnd_sibling{margin:0;padding:0;border-bottom:none}.o_tree ul li span.active>a{color:#428bca;background-color:none;font-weight:bold}.o_tree ul li span.active>a:hover,.o_tree ul li span.active>a:focus{color:#2a6496;background-color:#eee}.o_tree ul li span.active_parent>a{color:#777;font-weight:bold}.o_tree ul li span.active_parent>a:hover,.o_tree ul li span.active_parent>a:focus{color:#333}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l0{left:6px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l1{left:21px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l2{left:36px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l3{left:51px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l4{left:66px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l5{left:81px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l6{left:96px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l7{left:111px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l8{left:126px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l9{left:141px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l10{left:156px}.o_tree.o_tree_root_hidden ul li div .o_tree_oc_l11{left:171px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l0{padding:10px 2px 10px 20px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l1{padding:10px 2px 10px 35px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l2{padding:10px 2px 10px 50px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l3{padding:10px 2px 10px 65px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l4{padding:10px 2px 10px 80px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l5{padding:10px 2px 10px 95px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l6{padding:10px 2px 10px 110px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l7{padding:10px 2px 10px 125px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l8{padding:10px 2px 10px 140px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l9{padding:10px 2px 10px 155px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l10{padding:10px 2px 10px 170px}.o_tree.o_tree_root_hidden ul li div span.o_tree_l11{padding:10px 2px 10px 185px}.o_tree .o_dnd_item{cursor:move;z-index:100}.o_tree .o_dnd_proxy{opacity:0.4;filter:alpha(opacity=40);background-color:yellow}.o_tree .o_dnd_item.o_dnd_over{background-color:#ffff60}.o_tree .o_dnd_sibling{height:3px;width:100%}.o_tree .o_dnd_sibling.o_dnd_over{background:transparent url(../light/images/arrow_dd.png) top left no-repeat}.o_tree .o_dnd_l1{margin-left:0 !important}.o_tree .o_dnd_l2{margin-left:1em !important}.o_tree .o_dnd_l3{margin-left:2em !important}.o_tree .o_dnd_l4{margin-left:3em !important}.o_tree .o_dnd_l5{margin-left:4em !important}.o_tree .o_dnd_l6{margin-left:5em !important}.o_tree .o_dnd_l7{margin-left:6em !important}.o_tree .o_dnd_l8{margin-left:7em !important}.o_tree .o_dnd_l9{margin-left:8em !important}.o_tree .o_dnd_l10{margin-left:9em !important}.o_tree .o_dnd_l11{margin-left:10em !important}.o_offcanvas .o_tree{border:0}.o_selection_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_selection_tree ul{margin:0;padding:0;list-style-type:none}.o_selection_tree li{margin:0;padding:0;white-space:nowrap}.o_selection_tree li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_selection_tree li>div>span.o_tree_l0,.o_selection_tree li>div>div.checkbox.o_tree_l0,.o_selection_tree li>div>div.radio.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_selection_tree li>div>span.o_tree_l1,.o_selection_tree li>div>div.checkbox.o_tree_l1,.o_selection_tree li>div>div.radio.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_selection_tree li>div>span.o_tree_l2,.o_selection_tree li>div>div.checkbox.o_tree_l2,.o_selection_tree li>div>div.radio.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_selection_tree li>div>span.o_tree_l3,.o_selection_tree li>div>div.checkbox.o_tree_l3,.o_selection_tree li>div>div.radio.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_selection_tree li>div>span.o_tree_l4,.o_selection_tree li>div>div.checkbox.o_tree_l4,.o_selection_tree li>div>div.radio.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_selection_tree li>div>span.o_tree_l5,.o_selection_tree li>div>div.checkbox.o_tree_l5,.o_selection_tree li>div>div.radio.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_selection_tree li>div>span.o_tree_l6,.o_selection_tree li>div>div.checkbox.o_tree_l6,.o_selection_tree li>div>div.radio.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_selection_tree li>div>span.o_tree_l7,.o_selection_tree li>div>div.checkbox.o_tree_l7,.o_selection_tree li>div>div.radio.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_selection_tree li>div>span.o_tree_l8,.o_selection_tree li>div>div.checkbox.o_tree_l8,.o_selection_tree li>div>div.radio.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_selection_tree li>div>span.o_tree_l9,.o_selection_tree li>div>div.checkbox.o_tree_l9,.o_selection_tree li>div>div.radio.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_selection_tree li>div>span.o_tree_l10,.o_selection_tree li>div>div.checkbox.o_tree_l10,.o_selection_tree li>div>div.radio.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_selection_tree li>div>span.o_tree_l11,.o_selection_tree li>div>div.checkbox.o_tree_l11,.o_selection_tree li>div>div.radio.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_breadcrumb{position:relative}.o_breadcrumb .o_breadcrumb_close{float:right;position:relative;margin:0 0 0 15px;vertical-align:middle}.o_breadcrumb .o_breadcrumb_close a{line-height:15px;color:#d9534f}.o_breadcrumb .o_breadcrumb_close a:hover{color:#b52b27}.o_breadcrumb .o_breadcrumb_close a i{font-size:16px}.o_breadcrumb .o_breadcrumb_close a span{display:none}.o_breadcrumb .o_breadcrumb_close:before{content:none}.o_form .o_icon_mandatory{position:relative;right:0;line-height:inherit;margin-left:0.25em}.o_form .o_error{margin-top:1px;margin-bottom:0;padding:10px}.o_form hr.o_spacer_noline{border-top:1px solid transparent}.o_form .o_date{position:relative;padding-right:34px}.o_form .o_date.form-inline .form-group,.o_form .o_date.navbar-form .form-group,.o_form .o_date.o_navbar-form .form-group{margin-left:0}.o_form input.o_date_ms{width:3em}.o_form .o_date.form-inline .o_date_ms.form-group,.o_form .o_date.navbar-form .o_date_ms.form-group,.o_form .o_date.o_navbar-form .o_date_ms.form-group{margin-left:25px}.o_form .o_form_element.form-inline .o_form_element.form-group,.o_form .o_form_element.navbar-form .o_form_element.form-group,.o_form .o_form_element.o_navbar-form .o_form_element.form-group{margin-left:25px}.o_form .input-group.o_date_picker{width:16em}.o_form .o_filepreview{margin-bottom:10px}.o_form .o_fileinput{cursor:pointer;position:relative}.o_form .o_fileinput .o_fakechooser{position:relative;z-index:1}.o_form .o_fileinput .o_realchooser{position:absolute;top:0;left:0;z-index:2;opacity:0;filter:alpha(opacity=0)}.o_choice_checkrow,.o_choice_textrow{vertical-align:text-top;padding-bottom:2px}.o_choice_textrow{padding-left:1em}.o_togglecheck a{white-space:nowrap}.o_catalog .o_catalog_delete_img{position:relative;top:-0.5em}.o_button_dirty{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_button_dirty:hover,.o_button_dirty:focus,.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{background-image:none}.o_button_dirty.disabled,.o_button_dirty.disabled:hover,.o_button_dirty.disabled:focus,.o_button_dirty.disabled:active,.o_button_dirty.disabled.active,.o_button_dirty[disabled],.o_button_dirty[disabled]:hover,.o_button_dirty[disabled]:focus,.o_button_dirty[disabled]:active,.o_button_dirty[disabled].active,fieldset[disabled] .o_button_dirty,fieldset[disabled] .o_button_dirty:hover,fieldset[disabled] .o_button_dirty:focus,fieldset[disabled] .o_button_dirty:active,fieldset[disabled] .o_button_dirty.active{background-color:#f0ad4e;border-color:#eea236}.o_button_dirty .badge{color:#f0ad4e;background-color:#fff}.o_button_toggle{border:1px solid #777;border-top-right-radius:9px;border-top-left-radius:9px;border-bottom-right-radius:9px;border-bottom-left-radius:9px;background:#eee;display:inline-block;height:18px;line-height:16px;font-size:16px;text-align:left;padding:0 0.5em 0 0;margin:0}.o_button_toggle i{color:#777;text-shadow:1px 0 2px rgba(0,0,0,0.25)}.o_button_toggle span{line-height:16px;vertical-align:top;font-size:60%;color:#777;text-transform:uppercase}.o_button_toggle.o_on{text-align:right;padding:0 0 0 0.5em}.o_button_toggle.o_on i{color:#428bca;text-shadow:-1px 0 2px rgba(0,0,0,0.25)}.o_table_wrapper{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_table_wrapper.o_table_flexi .o_table_body{margin-top:20px}.o_table_wrapper.o_table_flexi .table{margin-top:20px}.o_table_wrapper.o_table_flexi .table td ul{margin:0}.o_table_wrapper.o_table_edit table tbody{border-top:solid #f90 4px;background-color:#fefbf6}.o_table_wrapper .o_table_search{max-width:50em}.o_table_wrapper .o_table_footer .o_table_pagination{text-align:center}.o_table_wrapper .o_row_selected td{background-color:#dff0d8 !important}.o_table_wrapper .o_table{margin-bottom:0}.o_table_wrapper .o_marked{font-weight:bold}.o_table_wrapper .table{margin-bottom:0}.o_table_wrapper th{color:#333}a.o_orderby,a.o_orderby:hover{color:#333;text-decoration:none}a.o_orderby.o_orderby_asc,a.o_orderby.o_orderby_desc,a.o_orderby:hover.o_orderby_asc,a.o_orderby:hover.o_orderby_desc{border-bottom:1px solid #ddd}.o_table_row_count{padding-top:6px;padding-bottom:6px;vertical-align:middle}.o_table_row_details td{background-color:white !important}.o_table_config{font-size:12px}.o_table_buttons{text-align:center}.o_table_buttons input{margin-right:1em}.o_table_buttons input:last-child{margin-right:0}.o_table_tools{margin-left:6px}.o_table_tools_indications{margin-left:10px;padding-top:3px;font-size:66%}.o_table_count{max-width:20em;float:left;padding:0 15px}.o_info .table-bordered td,o_note .table-bordered td,o_important .table-bordered td,o_warning .table-bordered td,o_error .table-bordered td{border-color:#333}.panel .o_table_layout{border-top:1px solid #ddd;padding-top:6px}.panel .o_table_count{padding:0 15px}#o_navbar_imclient #o_im_message,#o_navbar_imclient #o_im_status,#o_navbar_imclient #o_im_summary{float:left;position:relative;padding:15px 3px}#o_navbar_imclient #o_im_status{padding-left:15px}#o_navbar_imclient #o_im_summary .badge{color:#fff;background-color:#777}#o_navbar_imclient #o_im_status div.o_chelp_wrapper{right:0.5em}#o_navbar_imclient #o_im_message .o_icon_message{color:#d9534f}#o_navbar_imclient #o_im_message .o_icon_message:hover{color:#f4c37d}.o_im_load_history{margin-bottom:6px}.o_im_load_history .o_label{font-size:12px;padding-right:0.5em;line-height:1.5em;color:#777}.o_im_chat_history{height:170px;font-size:90%;border:1px solid #eee;margin:0 0 1em 0;overflow:scroll;overflow-x:auto}.o_im_message_group{padding:3px 3px 3px 40px;min-height:40px;position:relative;border-top:1px solid #eee;background:#fff}.o_im_message_group.o_odd{background:#f4f4f4}.o_im_message_group .o_portrait{position:absolute;top:3px;left:3px}.o_im_message_group .o_im_from{color:#777;font-size:12px;font-weight:bold}.o_im_message_group .o_im_from:hover{color:#5e5e5e}.o_im_message_group div.o_im_body{padding:3px 0 3px 0;font-size:12px}.o_im_message_group div.o_im_body .o_date{float:right;color:#777;font-size:9px}.o_groupchat_roster{font-size:12px}.o_groupchat_roster li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333}.o_groupchat_roster li.o_vip{color:#3c763d}.o_groupchat_roster li.o_anonymous{color:#31708f}.o_im_buddieslist .o_im_buddieslist_toggler .btn{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_im_buddieslist ul{font-size:12px}.o_im_buddieslist ul ul{padding-left:1em}.o_im_buddieslist ul a{color:#428bca}.o_im_buddieslist ul a:hover{color:#2a6496}.o_flag{position:relative;top:1px;display:inline-block;line-height:1;width:16px;height:16px;background-repeat:no-repeat;background-position:0 100%}option.o_with_flag{padding-left:23px;min-height:16px;background-repeat:no-repeat;background-position:2px 50%}.o_flag_en{background-image:url("../light/images/flags/gb.png")}.o_flag_de{background-image:url("../light/images/flags/de.png")}.o_flag_fr{background-image:url("../light/images/flags/fr.png")}.o_flag_it{background-image:url("../light/images/flags/it.png")}.o_flag_es{background-image:url("../light/images/flags/es.png")}.o_flag_da{background-image:url("../light/images/flags/dk.png")}.o_flag_cs{background-image:url("../light/images/flags/cz.png")}.o_flag_el{background-image:url("../light/images/flags/gr.png")}.o_flag_ee{background-image:url("../light/images/flags/ee.png")}.o_flag_ru{background-image:url("../light/images/flags/ru.png")}.o_flag_pl{background-image:url("../light/images/flags/pl.png")}.o_flag_zh_CN{background-image:url("../light/images/flags/cn.png")}.o_flag_zh_TW{background-image:url("../light/images/flags/tw.png")}.o_flag_lt{background-image:url("../light/images/flags/lt.png")}.o_flag_fa{background-image:url("../light/images/flags/ir.png")}.o_flag_pt_PT{background-image:url("../light/images/flags/pt.png")}.o_flag_pt_BR{background-image:url("../light/images/flags/br.png")}.o_flag_tr{background-image:url("../light/images/flags/tr.png")}.o_flag_hu{background-image:url("../light/images/flags/hu.png")}.o_flag_sq{background-image:url("../light/images/flags/al.png")}.o_flag_in{background-image:url("../light/images/flags/id.png")}.o_flag_ar{background-image:url("../light/images/flags/eg.png")}.o_flag_rm{background-image:url("../light/images/flags/rm.png")}.o_flag_af{background-image:url("../light/images/flags/za.png")}.o_flag_vi{background-image:url("../light/images/flags/vn.png")}.o_flag_mn{background-image:url("../light/images/flags/mn.png")}.o_flag_iw{background-image:url("../light/images/flags/il.png")}.o_flag_ko{background-image:url("../light/images/flags/kr.png")}.o_flag_nl_NL{background-image:url("../light/images/flags/nl.png")}.o_flag_jp{background-image:url("../light/images/flags/jp.png")}.o_flag_nb_NO{background-image:url("../light/images/flags/no.png")}.o_flag_et_EE{background-image:url("../light/images/flags/ee.png")}.o_flag_bg{background-image:url("../light/images/flags/bg.png")}.o_flag_hi_IN_ASIA{background-image:url("../light/images/flags/in.png")}.o_flag_ar_LB{background-image:url("../light/images/flags/lb.png")}.o_flag_gl_ES{background-image:url("../light/images/flags/galicia.png")}.o_flag_sk{background-image:url("../light/images/flags/sk.png")}.o_rating .o_rating_title{font-size:12px}.o_rating .o_rating_items{white-space:nowrap}.o_rating .o_rating_items .o_icon{color:#f0ad4e}.o_rating .o_rating_items .o_icon:hover{color:#428bca}.o_rating .o_rating_items .o_legend{margin-left:1em;font-size:12px;line-height:1em}.o_rating .o_rating_explanation{font-size:12px;color:#777}@media (max-width: 991px){.o_rating .o_rating_title,.o_rating .o_rating_explanation{display:none}} 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 62bbde0338c..5c6678ae5df 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{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_coursetable.o_rendertype_custom .o_table_row .o_meta .o_lifecycle{position:absolute;top:5px;right:40px;font-size:90%;line-height:1em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#777}.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/ims/qti/qpool/ItemFileResourceValidatorTest.java b/src/test/java/org/olat/ims/qti/qpool/ItemFileResourceValidatorTest.java index 77bd7cd1b0d..92a0807e2a1 100644 --- a/src/test/java/org/olat/ims/qti/qpool/ItemFileResourceValidatorTest.java +++ b/src/test/java/org/olat/ims/qti/qpool/ItemFileResourceValidatorTest.java @@ -1,3 +1,22 @@ +/** + * <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.ims.qti.qpool; import java.io.File; diff --git a/src/test/java/org/olat/modules/coach/CoachingServiceTest.java b/src/test/java/org/olat/modules/coach/CoachingServiceTest.java index d62511e9afb..8726a3316be 100644 --- a/src/test/java/org/olat/modules/coach/CoachingServiceTest.java +++ b/src/test/java/org/olat/modules/coach/CoachingServiceTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -32,10 +33,12 @@ import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.course.ICourse; @@ -101,23 +104,23 @@ public class CoachingServiceTest extends OlatTestCase { //author author = JunitTestHelper.createAndPersistIdentityAsAuthor("author_" + getUUID()); //r1 set of coach - coach10 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach11 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach12 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach13 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); + coach10 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-10"); + coach11 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-11"); + coach12 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-12"); + coach13 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-13"); //r2 set of coach - coach20 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach21 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach22 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach23 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach24 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach25 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); - coach26 = JunitTestHelper.createAndPersistIdentityAsUser("coach_" + getUUID()); + coach20 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-20"); + coach21 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-21"); + coach22 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-22"); + coach23 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-23"); + coach24 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-24"); + coach25 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-25"); + coach26 = JunitTestHelper.createAndPersistIdentityAsRndUser("coach-26"); List<Identity> students = new ArrayList<Identity>(); //r1 set of student for(int i=0; i<NUM_OF_STUDENTS; i++) { - Identity student = JunitTestHelper.createAndPersistIdentityAsUser("student_" + getUUID()); + Identity student = JunitTestHelper.createAndPersistIdentityAsRndUser("student-" + i); students.add(student); if(i == 0) { student10 = student; @@ -279,29 +282,22 @@ public class CoachingServiceTest extends OlatTestCase { } @Test - public void testStudentsStats() { + public void getStudentsStatistics() { List<StudentStatEntry> statEntries = coachingService.getStudentsStatistics(coach10); assertNotNull(statEntries); } @Test - public void testCoursesStats() { - List<CourseStatEntry> courses = coachingService.getCoursesStatistics(coach10); - assertNotNull(courses); - } - - @Test - public void testCoachCourses() { + public void getCoursesStatistics() { List<CourseStatEntry> statEntries = coachingService.getCoursesStatistics(coach10); assertNotNull(statEntries); List<Long> myCourses = coachToCourseMap.get(coach10.getKey()); assertNotNull(myCourses); - assertEquals(myCourses.size(), statEntries.size()); } @Test - public void testGroupStats() { + public void getGroupsStatistics() { List<GroupStatEntry> statEntries = coachingService.getGroupsStatistics(coach10); assertNotNull(statEntries); List<Long> myCourses = coachToGroupCourseMap.get(coach10.getKey()); @@ -311,7 +307,7 @@ public class CoachingServiceTest extends OlatTestCase { } @Test - public void testCourse() { + public void getCourse() { List<Long> myCourses = coachToCourseMap.get(coach10.getKey()); assertNotNull(myCourses); @@ -327,7 +323,7 @@ public class CoachingServiceTest extends OlatTestCase { } @Test - public void testStudentCourses() { + public void getStudentsCourses() { List<RepositoryEntry> courses = coachingService.getStudentsCourses(coach10, student10, 0, -1); assertNotNull(courses); @@ -335,6 +331,25 @@ public class CoachingServiceTest extends OlatTestCase { assertNotNull(myCourses); } + @Test + public void getUserCourses() { + List<RepositoryEntry> courses = coachingService.getUserCourses(student10, 0, -1); + Assert.assertNotNull(courses); + Assert.assertEquals(studentToCourseMap.get(student10).size(), courses.size()); + } + + @Test + public void getUsersStatistics() { + List<IdentityRef> identities = Collections.<IdentityRef>singletonList(student10); + List<StudentStatEntry> statEntries = coachingService.getUsersStatistics(identities); + Assert.assertNotNull(statEntries); + Assert.assertEquals(1, statEntries.size()); + + StudentStatEntry statEntry = statEntries.get(0); + Assert.assertEquals(student10.getKey(), statEntry.getStudentKey()); + Assert.assertEquals(studentToCourseMap.get(student10).size(), statEntry.getCountRepo()); + } + private String getUUID() { return UUID.randomUUID().toString().replace("-", ""); } -- GitLab