From c85c4df834334567bf4366b6692b89977bff3b1f Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Tue, 1 Sep 2015 08:28:39 +0200 Subject: [PATCH] OO-1645,OO-1595: refactor the personal configuration of calendars, the datamodel of imported calendars, make the description visible and editable... --- pom.xml | 1 + .../de/bps/course/nodes/den/DENManager.java | 16 +- .../delete/service/UserDeletionManager.java | 8 - .../CollaborationManagerImpl.java | 17 +- .../collaboration/CollaborationTools.java | 34 +- .../CollaborationToolsSettingsController.java | 6 +- .../commons/calendar/CalendarManagedFlag.java | 136 ++++++ .../commons/calendar/CalendarManager.java | 81 +++- .../calendar/CalendarManagerFactory.java | 59 --- .../CalendarNotificationManager.java | 2 +- .../olat/commons/calendar/CalendarUtils.java | 90 +--- .../calendar/CalendarWebDAVProvider.java | 3 +- .../olat/commons/calendar/GotoDateEvent.java | 53 --- .../olat/commons/calendar/ICalServlet.java | 268 ++++++----- .../commons/calendar/ICalTokenGenerator.java | 403 ---------------- .../commons/calendar/ImportCalendarJob.java | 4 +- .../calendar/ImportCalendarManager.java | 355 -------------- .../calendar/PersonalCalendarManager.java | 42 ++ .../commons/calendar/_content/calConfig.html | 56 --- .../calendar/_content/calIcalFeed.html | 8 - .../calendar/_content/calImportByUrl.html | 2 - .../calendar/_content/calSearchMain.html | 6 - .../calendar/_content/importEvents.html | 4 - .../calendar/_content/importedCalConfig.html | 66 --- .../calendar/_content/manageCalendars.html | 29 -- .../calendar/_i18n/LocalStrings_ar.properties | 2 +- .../calendar/_i18n/LocalStrings_bg.properties | 2 +- .../calendar/_i18n/LocalStrings_de.properties | 24 +- .../calendar/_i18n/LocalStrings_el.properties | 2 +- .../calendar/_i18n/LocalStrings_en.properties | 7 +- .../calendar/_i18n/LocalStrings_fr.properties | 5 +- .../calendar/_i18n/LocalStrings_it.properties | 2 +- .../calendar/_i18n/LocalStrings_jp.properties | 2 +- .../_i18n/LocalStrings_nl_NL.properties | 2 +- .../calendar/_i18n/LocalStrings_pl.properties | 2 +- .../_i18n/LocalStrings_pt_BR.properties | 6 +- .../calendar/_i18n/LocalStrings_ru.properties | 2 +- .../_i18n/LocalStrings_zh_CN.properties | 2 +- .../_i18n/LocalStrings_zh_TW.properties | 2 +- .../calendar/_spring/calendarContext.xml | 3 - .../manager/CalendarUserConfigurationDAO.java | 144 ++++++ .../ICalFileCalendarManager.java | 341 ++++++++++---- .../ICalFileCalendarUserDeleteManager.java | 14 +- .../manager/ImportCalendarManager.java | 207 ++++++++ .../manager/ImportToCalendarManager.java | 142 ++++++ .../calendar/manager/ImportedCalendarDAO.java | 89 ++++ .../manager/ImportedToCalendarDAO.java | 73 +++ ...{ICalToken.java => CalendarFileInfos.java} | 32 +- .../commons/calendar/model/CalendarKey.java | 70 +++ .../model/CalendarUserConfiguration.java | 193 ++++++++ .../calendar/model/ImportedCalendar.java | 186 ++++++++ .../calendar/model/ImportedToCalendar.java | 157 +++++++ .../calendar/model/KalendarComparator.java | 4 +- .../calendar/model/KalendarConfig.java | 79 ---- .../commons/calendar/model/KalendarEvent.java | 32 +- .../CalendarNotificationHandler.java | 71 +-- .../CalendarNotificationManagerImpl.java | 15 +- .../calendar/restapi/CalWebService.java | 11 +- .../commons/calendar/restapi/CalendarVO.java | 4 +- .../commons/calendar/restapi/EventVO.java | 23 +- .../restapi/UserCalendarWebService.java | 41 +- .../ui/CalendarAggregatedURLController.java | 57 +++ .../ui/CalendarColorChooserController.java | 20 +- .../ui/CalendarConfigurationController.java | 353 -------------- .../calendar/ui/CalendarController.java | 7 - .../ui/CalendarDetailsController.java | 36 +- .../ui/CalendarEntryDetailsController.java | 26 +- .../calendar/ui/CalendarEntryForm.java | 84 +++- .../calendar/ui/CalendarExportController.java | 66 --- .../ui/CalendarImportByUrlController.java | 144 ------ .../calendar/ui/CalendarImportNameForm.java | 116 ----- .../calendar/ui/CalendarImportUrlForm.java | 93 ---- .../calendar/ui/CalendarImportedRenderer.java | 56 +++ ...lendarPersonalConfigurationController.java | 440 ++++++++++++++++++ ...alendarPersonalConfigurationDataModel.java | 76 +++ .../ui/CalendarPersonalConfigurationRow.java | 131 ++++++ .../calendar/ui/CalendarPrintController.java | 4 +- .../calendar/ui/CalendarPrintMapper.java | 28 +- .../calendar/ui/CalendarToolsController.java | 93 ++++ .../ui/CalendarTypeClassRenderer.java | 69 +++ .../calendar/ui/CalendarURLController.java | 55 +++ .../ui/CopyEventToCalendarController.java | 8 +- .../calendar/ui/ExternalLinksController.java | 2 +- .../ui/ImportCalendarByUrlController.java | 132 ++++++ .../calendar/ui/ImportCalendarController.java | 197 -------- ...java => ImportCalendarFileController.java} | 71 +-- ...portedCalendarConfigurationController.java | 178 ------- .../ui/InjectCalendarFileController.java | 95 ++++ .../ui/ManageCalendarsController.java | 171 ------- .../calendar/ui/MediaLinksController.java | 2 +- .../ui/SynchronizedCalendarUrlController.java | 120 +++++ .../calendar/ui/WeeklyCalendarController.java | 317 +++++++------ .../ui/_content/calAggregatedFeed.html | 3 + .../{ => ui}/_content/calEditDetails.html | 0 .../{ => ui}/_content/calEditLinks.html | 0 .../{ => ui}/_content/calEditMain.html | 0 .../{ => ui}/_content/calExternalLinks.html | 0 .../{ => ui}/_content/calExternalMedias.html | 0 .../calendar/ui/_content/calIcalFeed.html | 3 + .../calendar/ui/_content/configuration.html | 13 + .../calendar/ui/_content/event_details.html | 7 +- .../calendar/ui/_content/indexWeekly.html | 4 - .../commons/calendar/ui/_content/tools.html | 13 + .../ui/components/FullCalendarComponent.java | 66 +-- .../FullCalendarComponentRenderer.java | 180 +++---- .../ui/components/FullCalendarElement.java | 73 ++- .../ui/components/FullCalendarMapper.java | 25 +- .../KalendarEventRenderWrapper.java | 2 +- .../ui/components/KalendarRenderWrapper.java | 146 +++++- ...AddEvent.java => CalendarGUIAddEvent.java} | 8 +- ...itEvent.java => CalendarGUIEditEvent.java} | 4 +- .../calendar/ui/events/CalendarGUIEvent.java | 42 ++ .../ui/events/CalendarGUIFormEvent.java | 48 ++ .../ui/events/CalendarGUIImportEvent.java | 46 ++ ...ent.java => CalendarGUIModifiedEvent.java} | 6 +- ...veEvent.java => CalendarGUIMoveEvent.java} | 4 +- ...tEvent.java => CalendarGUIPrintEvent.java} | 9 +- .../ui/events/CalendarGUIRemoveEvent.java | 45 ++ ...Event.java => CalendarGUISelectEvent.java} | 4 +- .../ui/events/CalendarGUISettingEvent.java | 45 ++ .../ui/events/KalendarGUIImportEvent.java | 51 -- .../persistence/_spring/core_persistence.xml | 3 + .../_spring/databaseCorecontext.xml | 4 +- .../_spring/notificationsContext.xml | 1 - .../ExtendedFlexiTableSearchController.java | 2 +- .../CloseableCalloutWindowController.java | 4 +- .../velocity/VelocityRenderDecorator.java | 6 - .../olat/core/util/prefs/db/DbStorage.java | 5 +- .../java/org/olat/course/CourseFactory.java | 7 +- .../config/ui/CourseOptionsController.java | 4 +- .../org/olat/course/nodes/CalCourseNode.java | 2 +- .../course/nodes/cal/CalRunController.java | 36 +- .../nodes/cal/CourseCalendarController.java | 25 +- .../cal/CourseCalendarPeekViewController.java | 5 +- .../course/nodes/cal/CourseCalendars.java | 57 ++- .../calendar/CourseCalendarController.java | 32 +- .../CourseLinkProviderController.java | 7 +- .../homepage/GroupInfoDisplayController.java | 8 +- .../ui/homepage/GroupInfoMainController.java | 57 ++- .../homepage/_i18n/LocalStrings_de.properties | 1 + .../homepage/_i18n/LocalStrings_en.properties | 1 + .../homepage/_i18n/LocalStrings_fr.properties | 1 + .../run/BusinessGroupMainRunController.java | 2 +- .../org/olat/home/HomeCalendarController.java | 13 +- .../org/olat/home/HomeCalendarManager.java | 143 ++++-- .../CalendarPortletRunController.java | 7 +- .../repository/course/CourseWebService.java | 10 +- .../org/olat/upgrade/OLATUpgrade_10_4_0.java | 384 +++++++++++++++ .../_spring/databaseUpgradeContext.xml | 4 + .../olat/upgrade/_spring/upgradeContext.xml | 1 + .../olat/upgrade/model/KalendarConfig.java | 71 +++ .../upgrade/model/UpgradePreferences.java | 42 ++ .../org/olat/user/UserInfoMainController.java | 36 +- .../database/mysql/alter_10_3_4_to_10_4_0.sql | 54 +++ .../database/mysql/setupDatabase.sql | 55 +++ .../oracle/alter_10_3_4_to_10_4_0.sql | 53 +++ .../database/oracle/setupDatabase.sql | 55 +++ .../postgresql/alter_10_3_4_to_10_4_0.sql | 53 +++ .../database/postgresql/setupDatabase.sql | 54 +++ src/main/webapp-tomcat/WEB-INF/web.xml | 5 +- .../static/themes/light/modules/_cal.scss | 7 + .../static/themes/light/modules/_icons.scss | 1 + src/main/webapp/static/themes/light/theme.css | 52 ++- .../themes/light/theme_ie_completions.css | 5 +- .../CalendarUserConfigurationDAOTest.java | 128 +++++ .../ICalFileCalendarManagerTest.java | 363 ++++++++++++--- .../manager/ImportedCalendarDAOTest.java | 221 +++++++++ .../manager/ImportedToCalendarDAOTest.java | 111 +++++ .../calendar/test/CalendarUtilsTest.java | 201 -------- .../java/org/olat/restapi/CalendarTest.java | 14 +- .../org/olat/restapi/CourseCalendarTest.java | 12 +- .../java/org/olat/test/AllTestsJunit4.java | 8 +- 172 files changed, 6370 insertions(+), 3899 deletions(-) create mode 100644 src/main/java/org/olat/commons/calendar/CalendarManagedFlag.java delete mode 100644 src/main/java/org/olat/commons/calendar/CalendarManagerFactory.java rename src/main/java/org/olat/commons/calendar/{notification => }/CalendarNotificationManager.java (96%) delete mode 100644 src/main/java/org/olat/commons/calendar/GotoDateEvent.java delete mode 100644 src/main/java/org/olat/commons/calendar/ICalTokenGenerator.java delete mode 100644 src/main/java/org/olat/commons/calendar/ImportCalendarManager.java create mode 100644 src/main/java/org/olat/commons/calendar/PersonalCalendarManager.java delete mode 100644 src/main/java/org/olat/commons/calendar/_content/calConfig.html delete mode 100644 src/main/java/org/olat/commons/calendar/_content/calIcalFeed.html delete mode 100644 src/main/java/org/olat/commons/calendar/_content/calImportByUrl.html delete mode 100644 src/main/java/org/olat/commons/calendar/_content/calSearchMain.html delete mode 100644 src/main/java/org/olat/commons/calendar/_content/importEvents.html delete mode 100644 src/main/java/org/olat/commons/calendar/_content/importedCalConfig.html delete mode 100644 src/main/java/org/olat/commons/calendar/_content/manageCalendars.html create mode 100644 src/main/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAO.java rename src/main/java/org/olat/commons/calendar/{ => manager}/ICalFileCalendarManager.java (75%) rename src/main/java/org/olat/commons/calendar/{ => manager}/ICalFileCalendarUserDeleteManager.java (80%) create mode 100644 src/main/java/org/olat/commons/calendar/manager/ImportCalendarManager.java create mode 100644 src/main/java/org/olat/commons/calendar/manager/ImportToCalendarManager.java create mode 100644 src/main/java/org/olat/commons/calendar/manager/ImportedCalendarDAO.java create mode 100644 src/main/java/org/olat/commons/calendar/manager/ImportedToCalendarDAO.java rename src/main/java/org/olat/commons/calendar/model/{ICalToken.java => CalendarFileInfos.java} (68%) create mode 100644 src/main/java/org/olat/commons/calendar/model/CalendarKey.java create mode 100644 src/main/java/org/olat/commons/calendar/model/CalendarUserConfiguration.java create mode 100644 src/main/java/org/olat/commons/calendar/model/ImportedCalendar.java create mode 100644 src/main/java/org/olat/commons/calendar/model/ImportedToCalendar.java delete mode 100644 src/main/java/org/olat/commons/calendar/model/KalendarConfig.java rename src/main/java/org/olat/commons/calendar/{ => notification}/CalendarNotificationHandler.java (76%) create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarAggregatedURLController.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarConfigurationController.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarExportController.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarImportByUrlController.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarImportNameForm.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarImportUrlForm.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarImportedRenderer.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationController.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationDataModel.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationRow.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarToolsController.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarTypeClassRenderer.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/CalendarURLController.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/ImportCalendarByUrlController.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/ImportCalendarController.java rename src/main/java/org/olat/commons/calendar/ui/{CalendarFileUploadController.java => ImportCalendarFileController.java} (64%) delete mode 100644 src/main/java/org/olat/commons/calendar/ui/ImportedCalendarConfigurationController.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/InjectCalendarFileController.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/ManageCalendarsController.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/SynchronizedCalendarUrlController.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/_content/calAggregatedFeed.html rename src/main/java/org/olat/commons/calendar/{ => ui}/_content/calEditDetails.html (100%) rename src/main/java/org/olat/commons/calendar/{ => ui}/_content/calEditLinks.html (100%) rename src/main/java/org/olat/commons/calendar/{ => ui}/_content/calEditMain.html (100%) rename src/main/java/org/olat/commons/calendar/{ => ui}/_content/calExternalLinks.html (100%) rename src/main/java/org/olat/commons/calendar/{ => ui}/_content/calExternalMedias.html (100%) create mode 100644 src/main/java/org/olat/commons/calendar/ui/_content/calIcalFeed.html create mode 100644 src/main/java/org/olat/commons/calendar/ui/_content/configuration.html create mode 100644 src/main/java/org/olat/commons/calendar/ui/_content/tools.html rename src/main/java/org/olat/commons/calendar/ui/events/{KalendarGUIAddEvent.java => CalendarGUIAddEvent.java} (90%) rename src/main/java/org/olat/commons/calendar/ui/events/{KalendarGUIEditEvent.java => CalendarGUIEditEvent.java} (93%) create mode 100644 src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIEvent.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIFormEvent.java create mode 100644 src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIImportEvent.java rename src/main/java/org/olat/commons/calendar/ui/events/{KalendarModifiedEvent.java => CalendarGUIModifiedEvent.java} (90%) rename src/main/java/org/olat/commons/calendar/ui/events/{KalendarGUIMoveEvent.java => CalendarGUIMoveEvent.java} (94%) rename src/main/java/org/olat/commons/calendar/ui/events/{KalendarGUIPrintEvent.java => CalendarGUIPrintEvent.java} (85%) create mode 100644 src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIRemoveEvent.java rename src/main/java/org/olat/commons/calendar/ui/events/{KalendarGUISelectEvent.java => CalendarGUISelectEvent.java} (93%) create mode 100644 src/main/java/org/olat/commons/calendar/ui/events/CalendarGUISettingEvent.java delete mode 100644 src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIImportEvent.java create mode 100644 src/main/java/org/olat/upgrade/OLATUpgrade_10_4_0.java create mode 100644 src/main/java/org/olat/upgrade/model/KalendarConfig.java create mode 100644 src/main/java/org/olat/upgrade/model/UpgradePreferences.java create mode 100644 src/main/resources/database/mysql/alter_10_3_4_to_10_4_0.sql create mode 100644 src/main/resources/database/oracle/alter_10_3_4_to_10_4_0.sql create mode 100644 src/main/resources/database/postgresql/alter_10_3_4_to_10_4_0.sql create mode 100644 src/test/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAOTest.java rename src/test/java/org/olat/commons/calendar/{ => manager}/ICalFileCalendarManagerTest.java (63%) create mode 100644 src/test/java/org/olat/commons/calendar/manager/ImportedCalendarDAOTest.java create mode 100644 src/test/java/org/olat/commons/calendar/manager/ImportedToCalendarDAOTest.java delete mode 100644 src/test/java/org/olat/commons/calendar/test/CalendarUtilsTest.java diff --git a/pom.xml b/pom.xml index 6d4857dd8cb..6a40564b5be 100644 --- a/pom.xml +++ b/pom.xml @@ -1247,6 +1247,7 @@ <artifactId>maven-surefire-plugin</artifactId> <version>2.18.1</version> <configuration> + <encoding>UTF-8</encoding> <skipTests>${skipTests}</skipTests> <argLine>-Xmx512m -Xms256m -Djava.awt.headless=true</argLine> <systemPropertyVariables> diff --git a/src/main/java/de/bps/course/nodes/den/DENManager.java b/src/main/java/de/bps/course/nodes/den/DENManager.java index a7042c0641a..888b6eaff9b 100644 --- a/src/main/java/de/bps/course/nodes/den/DENManager.java +++ b/src/main/java/de/bps/course/nodes/den/DENManager.java @@ -32,7 +32,6 @@ import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.BaseSecurityModule; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.model.KalendarEventLink; @@ -75,10 +74,9 @@ import de.bps.course.nodes.DENCourseNode; public class DENManager { private final static DENManager denManager = new DENManager(); - private CalendarManager calManager; - + private DENManager() { - calManager = CalendarManagerFactory.getInstance().getCalendarManager(); + // } /** @@ -114,6 +112,7 @@ public class DENManager { final boolean allowOverfill) { final DENStatus status = new DENStatus(); ICourse course = CourseFactory.loadCourse(ores); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); final Kalendar cal = calManager.getCourseCalendar(course).getKalendar(); OLATResourceable calRes = calManager.getOresHelperFor(cal); // reload calendar events @@ -190,6 +189,7 @@ public class DENManager { public DENStatus cancelEnroll(Identity identity, KalendarEvent event, OLATResourceable ores) { DENStatus status = new DENStatus(); ICourse course = CourseFactory.loadCourse(ores); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); Kalendar cal = calManager.getCourseCalendar(course).getKalendar(); //check if identity is enrolled if( !isEnrolledInDate(identity, event) ) { @@ -248,6 +248,7 @@ public class DENManager { */ public void persistDENSettings(List<KalendarEvent> lstEvents, OLATResourceable ores, DENCourseNode denNode) { ICourse course = CourseFactory.loadCourse(ores); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); Kalendar cal = calManager.getCourseCalendar(course).getKalendar(); String sourceNode = denNode.getIdent(); //remove deleted events @@ -332,6 +333,7 @@ public class DENManager { protected List<KalendarEvent> getDENEvents(Long courseId, String sourceNodeId) { List<KalendarEvent> denEvents = new ArrayList<KalendarEvent>(); ICourse course = CourseFactory.loadCourse(courseId); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); Kalendar cal = calManager.getCourseCalendar(course).getKalendar(); Collection<KalendarEvent> colEvents = cal.getEvents(); for( KalendarEvent event : colEvents) { @@ -487,6 +489,7 @@ public class DENManager { String[] participants = newEvent.getParticipants(); if(participants == null) return;//no users to update, cancel BaseSecurity manager = BaseSecurityManager.getInstance(); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); for( String participant : participants ) { Identity identity = manager.findIdentityByName(participant); if(identity != null) { @@ -513,6 +516,7 @@ public class DENManager { String[] participants = oldEvent.getParticipants(); if(participants == null) return;//no users to update, cancel BaseSecurity manager = BaseSecurityManager.getInstance(); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); for( String participant : participants ) { Identity identity = manager.findIdentityByName(participant); if(identity != null) { @@ -806,7 +810,7 @@ public class DENManager { MailTemplate mailTempl = new MailTemplate(subject, body, null) { @Override - public void putVariablesInMailContext(VelocityContext context, Identity identity) { + public void putVariablesInMailContext(VelocityContext context, Identity ident) { // } }; @@ -835,7 +839,7 @@ public class DENManager { String body = trans.translate("mail.participants.remove.body", bodyArgs); MailTemplate mailTempl = new MailTemplate(subject, body, null) { @Override - public void putVariablesInMailContext(VelocityContext context, Identity identity) { + public void putVariablesInMailContext(VelocityContext context, Identity ident) { // } }; diff --git a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java index 61ccbfb8835..7a006b4759f 100644 --- a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java +++ b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java @@ -41,7 +41,6 @@ import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.IdentityImpl; import org.olat.basesecurity.SecurityGroup; import org.olat.basesecurity.manager.GroupDAO; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.lifecycle.LifeCycleManager; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; @@ -94,7 +93,6 @@ public class UserDeletionManager extends BasicManager { // Flag used in user-delete to indicate that all deletable managers are initialized - private boolean managersInitialized = false; private RepositoryDeletionModule deletionModule; private RegistrationManager registrationManager; private BaseSecurity securityManager; @@ -290,12 +288,6 @@ public class UserDeletionManager extends BasicManager { // Each manager register themself as deletable // Should be better with new config concept - // FIXME: it would be better to call the mangers over a common interface which would not need to have references to all mangers here - if (!managersInitialized) { - CalendarManagerFactory.getInstance(); //the only one that left for refactoring - managersInitialized = true; - } - logInfo("Start EfficiencyStatementManager.archiveUserData for identity=" + identity); EfficiencyStatementManager.getInstance().archiveUserData(identity, getArchivFilePath(identity) ); diff --git a/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java b/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java index eb0166092d6..57a3b92575b 100644 --- a/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java +++ b/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java @@ -31,8 +31,7 @@ import javax.persistence.TypedQuery; import org.olat.basesecurity.GroupRoles; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.model.KalendarConfig; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.DBFactory; @@ -55,6 +54,8 @@ public class CollaborationManagerImpl extends BasicManager implements Collaborat @Autowired private DB dbInstance; @Autowired + private CalendarManager calendarManager; + @Autowired private BusinessGroupService businessGroupService; public String getFolderRelPath(OLATResourceable ores) { @@ -121,13 +122,11 @@ public class CollaborationManagerImpl extends BasicManager implements Collaborat @Override public KalendarRenderWrapper getCalendar(BusinessGroup businessGroup, UserRequest ureq, boolean isAdmin) { - // do not use a global translator since in the fututre a collaborationtools // may be shared among users // get the calendar - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - KalendarRenderWrapper calRenderWrapper = calManager.getGroupCalendar(businessGroup); + KalendarRenderWrapper calRenderWrapper = calendarManager.getGroupCalendar(businessGroup); boolean isOwner = businessGroupService.hasRoles(ureq.getIdentity(), businessGroup, GroupRoles.coach.name()); if (!(isAdmin || isOwner)) { // check if participants have read/write access @@ -142,14 +141,10 @@ public class CollaborationManagerImpl extends BasicManager implements Collaborat } else { calRenderWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); } - KalendarConfig config = calManager.findKalendarConfigForIdentity(calRenderWrapper.getKalendar(), ureq); + CalendarUserConfiguration config = calendarManager.findCalendarConfigForIdentity(calRenderWrapper.getKalendar(), ureq.getIdentity()); if (config != null) { - calRenderWrapper.getKalendarConfig().setCss(config.getCss()); - calRenderWrapper.getKalendarConfig().setVis(config.isVis()); + calRenderWrapper.setConfiguration(config); } - calRenderWrapper.getKalendarConfig().setResId(businessGroup.getKey()); return calRenderWrapper; } - - } diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java index 227d6c129ab..21c4fee5154 100644 --- a/src/main/java/org/olat/collaboration/CollaborationTools.java +++ b/src/main/java/org/olat/collaboration/CollaborationTools.java @@ -39,7 +39,6 @@ import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; import org.olat.basesecurity.GroupRoles; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.ui.CalendarController; import org.olat.commons.calendar.ui.WeeklyCalendarController; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; @@ -197,7 +196,6 @@ public class CollaborationTools implements Serializable { /** * Only owners have write access to the folder. */ - //fxdiff VCRP-8: collaboration tools folder access control public static final int FOLDER_ACCESS_OWNERS = 0; /** * Owners and members have write access to the folder. @@ -210,7 +208,6 @@ public class CollaborationTools implements Serializable { */ private final static String KEY_NEWS = "news"; public final static String KEY_CALENDAR_ACCESS = "cal"; - //fxdiff VCRP-8: collaboration tools folder access control public final static String KEY_FOLDER_ACCESS = "folder"; //o_clusterOK by guido @@ -397,9 +394,12 @@ public class CollaborationTools implements Serializable { * @param resourceableId * @return Configured WeeklyCalendarController */ - public CalendarController createCalendarController(UserRequest ureq, WindowControl wControl, BusinessGroup businessGroup, boolean isAdmin) { + public CalendarController createCalendarController(UserRequest ureq, WindowControl wControl, BusinessGroup businessGroup, + boolean isAdmin, boolean isMember) { + CollaborationManager collaborationManager = CoreSpringFactory.getImpl(CollaborationManager.class); KalendarRenderWrapper calRenderWrapper = collaborationManager.getCalendar(businessGroup, ureq, isAdmin); + calRenderWrapper.setPrivateEventsVisible(isAdmin || isMember); // add linking List<RepositoryEntry> repoEntries = CoreSpringFactory.getImpl(BusinessGroupService.class).findRepositoryEntries(Collections.singleton(businessGroup), 0, -1); @@ -419,10 +419,8 @@ public class CollaborationTools implements Serializable { List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); calendars.add(calRenderWrapper); - WeeklyCalendarController calendarController = new WeeklyCalendarController( - ureq, wControl, calendars, WeeklyCalendarController.CALLER_COLLAB, true); - - return calendarController; + return new WeeklyCalendarController(ureq, wControl, calendars, + WeeklyCalendarController.CALLER_COLLAB, false); } /** @@ -594,7 +592,7 @@ public class CollaborationTools implements Serializable { * Delete calendar if exists */ if (businessGroupTodelete != null) { - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); calManager.deleteGroupCalendar(businessGroupTodelete); } @@ -873,8 +871,8 @@ public class CollaborationTools implements Serializable { } } - private void archiveForum(OLATResourceable ores, String archivFilePath) { - Property forumKeyProperty = NarrowedPropertyManager.getInstance(ores).findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM); + private void archiveForum(OLATResourceable formRes, String archivFilePath) { + Property forumKeyProperty = NarrowedPropertyManager.getInstance(formRes).findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM); if (forumKeyProperty != null) { VFSContainer archiveContainer = new LocalFolderImpl(new File(archivFilePath)); String archiveForumName = "del_forum_" + forumKeyProperty.getLongValue(); @@ -884,10 +882,10 @@ public class CollaborationTools implements Serializable { } } - private void archiveWiki(OLATResourceable ores, String archivFilePath) { - VFSContainer wikiContainer = WikiManager.getInstance().getWikiRootContainer(ores); + private void archiveWiki(OLATResourceable wikiRes, String archivFilePath) { + VFSContainer wikiContainer = WikiManager.getInstance().getWikiRootContainer(wikiRes); VFSLeaf wikiZip = WikiToZipUtils.getWikiAsZip(wikiContainer); - String exportFileName = "del_wiki_" + ores.getResourceableId() + ".zip"; + String exportFileName = "del_wiki_" + wikiRes.getResourceableId() + ".zip"; File archiveDir = new File(archivFilePath); if (!archiveDir.exists()) { archiveDir.mkdir(); @@ -897,17 +895,17 @@ public class CollaborationTools implements Serializable { try { FileUtils.bcopy(wikiZip.getInputStream(), new File(fullFilePath), "archive wiki"); } catch (FileNotFoundException e) { - log.warn("Can not archive wiki repoEntry=" + ores.getResourceableId()); + log.warn("Can not archive wiki repoEntry=" + wikiRes.getResourceableId()); } catch (IOException ioe) { - log.warn("Can not archive wiki repoEntry=" + ores.getResourceableId()); + log.warn("Can not archive wiki repoEntry=" + wikiRes.getResourceableId()); } } - private void archiveFolder(OLATResourceable ores, String archiveFilePath) { + private void archiveFolder(OLATResourceable folderRes, String archiveFilePath) { OlatRootFolderImpl folderContainer = new OlatRootFolderImpl(getFolderRelPath(), null); File fFolderRoot = folderContainer.getBasefile(); if (fFolderRoot.exists()) { - String zipFileName = "del_folder_" + ores.getResourceableId() + ".zip"; + String zipFileName = "del_folder_" + folderRes.getResourceableId() + ".zip"; String fullZipFilePath = archiveFilePath + File.separator + zipFileName; ZipUtil.zipAll(fFolderRoot, new File(fullZipFilePath), true); } diff --git a/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java b/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java index 417346ef41c..08f16299538 100644 --- a/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java +++ b/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java @@ -30,7 +30,7 @@ import java.util.List; import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.CalendarModule; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -220,7 +220,7 @@ public class CollaborationToolsSettingsController extends BasicController { // notify calendar components to refresh their calendars CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf( - new KalendarModifiedEvent(), OresHelper.lookupType(CalendarManager.class) + new CalendarGUIModifiedEvent(), OresHelper.lookupType(CalendarManager.class) ); } lastCalendarEnabledState = newCalendarEnabledState; @@ -256,7 +256,7 @@ public class CollaborationToolsSettingsController extends BasicController { collabTools.saveCalendarAccess(new Long(calendarForm.getCalendarAccess())); // notify calendar components to refresh their calendars CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf( - new KalendarModifiedEvent(), OresHelper.lookupType(CalendarManager.class) + new CalendarGUIModifiedEvent(), OresHelper.lookupType(CalendarManager.class) ); //fxdiff VCRP-8: collaboration tools folder access control } else if (source == folderForm) { diff --git a/src/main/java/org/olat/commons/calendar/CalendarManagedFlag.java b/src/main/java/org/olat/commons/calendar/CalendarManagedFlag.java new file mode 100644 index 00000000000..aaab1f02321 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/CalendarManagedFlag.java @@ -0,0 +1,136 @@ +/** + * <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.commons.calendar; + +import java.util.Arrays; + +import org.olat.commons.calendar.model.KalendarEvent; +import org.olat.core.CoreSpringFactory; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; + +/** + * + * Initial date: 31.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum CalendarManagedFlag { + + all, + details(all), + subject(details, all), + description(details, all), + location(details, all), + dates(details, all), + classification(all), + links(all), + ; + + private CalendarManagedFlag[] parents; + private static final OLog log = Tracing.createLoggerFor(CalendarManagedFlag.class); + public static final CalendarManagedFlag[] EMPTY_ARRAY = new CalendarManagedFlag[0]; + + private static CalendarModule calendarModule; + + private CalendarManagedFlag() { + // + } + + private CalendarManagedFlag(CalendarManagedFlag... parents) { + if(parents == null) { + this.parents = new CalendarManagedFlag[0]; + } else { + this.parents = parents; + } + } + + public static String toString(CalendarManagedFlag[] flags) { + if(flags == null || flags.length == 0) return ""; + + StringBuilder sb = new StringBuilder(); + for(CalendarManagedFlag flag:flags) { + if(sb.length() > 0) sb.append(","); + sb.append(flag.name()); + } + return sb.toString(); + } + + public static CalendarManagedFlag[] toEnum(String flags) { + if(StringHelper.containsNonWhitespace(flags)) { + String[] flagArr = flags.split(","); + CalendarManagedFlag[] flagEnums = new CalendarManagedFlag[flagArr.length]; + + int count = 0; + for(String flag:flagArr) { + if(StringHelper.containsNonWhitespace(flag)) { + try { + CalendarManagedFlag flagEnum = valueOf(flag); + flagEnums[count++] = flagEnum; + } catch (Exception e) { + log.warn("Cannot parse this managed flag: " + flag, e); + } + } + } + + if(count != flagEnums.length) { + flagEnums = Arrays.copyOf(flagEnums, count); + } + return flagEnums; + } else { + return EMPTY_ARRAY; + } + } + + public static boolean isManaged(KalendarEvent event, CalendarManagedFlag marker) { + if(calendarModule == null) { + calendarModule = CoreSpringFactory.getImpl(CalendarModule.class); + } + if(!calendarModule.isManagedCalendars()) { + return false; + } + + if(event != null && (contains(event, marker) || contains(event, marker.parents))) { + return true; + } + return false; + } + + private static boolean contains(KalendarEvent event, CalendarManagedFlag... markers) { + if(event == null || markers == null) return false; + CalendarManagedFlag[] flags = event.getManagedFlags(); + return contains(flags, markers); + } + + private static boolean contains(CalendarManagedFlag[] flags, CalendarManagedFlag... markers) { + if(flags == null || flags.length == 0) return false; + + for(CalendarManagedFlag flag:flags) { + for(CalendarManagedFlag marker:markers) { + if(flag.equals(marker)) { + return true; + } + } + } + return false; + } + +} diff --git a/src/main/java/org/olat/commons/calendar/CalendarManager.java b/src/main/java/org/olat/commons/calendar/CalendarManager.java index 641d1bbe5d9..1a4e635e076 100644 --- a/src/main/java/org/olat/commons/calendar/CalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/CalendarManager.java @@ -26,27 +26,31 @@ package org.olat.commons.calendar; import java.io.File; +import java.io.InputStream; import java.util.Date; import java.util.List; +import java.util.Map; -import net.fortuna.ical4j.model.Calendar; - +import org.olat.basesecurity.IdentityRef; +import org.olat.commons.calendar.model.CalendarKey; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarConfig; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.model.KalendarRecurEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.core.gui.UserRequest; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.course.ICourse; import org.olat.group.BusinessGroup; +import net.fortuna.ical4j.model.Calendar; + public interface CalendarManager { public static final String TYPE_USER = "user"; public static final String TYPE_GROUP = "group"; public static final String TYPE_COURSE = "course"; + public static final String TYPE_USER_AGGREGATED = "paggregated"; public static final String ICAL_X_OLAT_LINK = "X-OLAT-LINK"; public static final String ICAL_X_OLAT_COMMENT = "X-OLAT-COMMENT"; @@ -55,6 +59,21 @@ public interface CalendarManager { public static final String ICAL_X_OLAT_SOURCENODEID = "X-OLAT-SOURCENODEID"; public static final String ICAL_X_OLAT_MANAGED = "X-OLAT-MANAGED"; public static final String ICAL_X_OLAT_EXTERNAL_ID = "X-OLAT-EXTERNAL-ID"; + public static final String ICAL_X_OLAT_EXTERNAL_SOURCE = "X-OLAT-EXTERNAL-SOURCE"; + + /** path prefix for personal iCal feed **/ + public static final String ICAL_PREFIX_AGGREGATED = "/paggregated/"; + /** path prefix for personal iCal feed **/ + public static final String ICAL_PREFIX_PERSONAL = "/user/"; + /** path prefix for course iCal feed **/ + public static final String ICAL_PREFIX_COURSE = "/course/"; + /** path prefix for group iCal feed **/ + public static final String ICAL_PREFIX_GROUP = "/group/"; + + /** Expected number of tokens in the course/group calendar link **/ + public static final int ICAL_PATH_TOKEN_LENGTH = 4; + /** Expected number of tokens in the personal calendar link **/ + public static final int ICAL_PERSONAL_PATH_TOKEN_LENGTH = 3; public static final int MAX_SUBJECT_DISPLAY_LENGTH = 30; @@ -91,7 +110,7 @@ public interface CalendarManager { public boolean deleteCalendar(String calendarType, String calendarID); /** - * Get a calendar as iCalendar file. + * Return the calendar file if it exists or null. * * @param calendarType * @param calendarID @@ -177,6 +196,8 @@ public interface CalendarManager { */ public Calendar readCalendar(String type, String calendarID); + public Calendar readCalendar(File calendarFile); + /** * Delete the calendar of the given course. * @param course @@ -198,7 +219,7 @@ public interface CalendarManager { * @param ureq * @return */ - public KalendarConfig findKalendarConfigForIdentity(Kalendar calendar, UserRequest ureq); + public CalendarUserConfiguration findCalendarConfigForIdentity(Kalendar calendar, IdentityRef identity); /** * Save the calendar configuration for a specific calendar for @@ -208,7 +229,35 @@ public interface CalendarManager { * @param calendar * @param ureq */ - public void saveKalendarConfigForIdentity(KalendarConfig kalendarConfig, Kalendar calendar, UserRequest ureq); + public void saveCalendarConfigForIdentity(KalendarRenderWrapper calendar, Identity identity); + + public CalendarUserConfiguration saveCalendarConfig(CalendarUserConfiguration configuration); + + public CalendarUserConfiguration createAggregatedCalendarConfig(Identity identity); + + public List<CalendarUserConfiguration> getCalendarUserConfigurationsList(IdentityRef identity, String... types); + + + public CalendarUserConfiguration getCalendarUserConfiguration(Long key); + + /** + * Retrieve the settings for a specific user. + * + * @param identity The user + * @param types The types of calendars (optional) + * @return + */ + public Map<CalendarKey,CalendarUserConfiguration> getCalendarUserConfigurationsMap(IdentityRef identity, String... types); + + /** + * Retrieve the token if it exists. + * + * @param calendarType + * @param calendarID + * @param userName + * @return + */ + public String getCalendarToken(String calendarType, String calendarID, String userName); /** * Add an event to given calendar and save calendar. @@ -289,7 +338,17 @@ public interface CalendarManager { * @param calendarContent * @return */ - public Kalendar buildKalendarFrom(String calendarContent, String calType, String calId); + public Kalendar buildKalendarFrom(InputStream calendarContent, String calType, String calId); + + /** + * Synchronize the event of the calendar stream to the target calendar, + * set the synchronized events as managed. + * + * @param in + * @param targetCalendar + * @return + */ + public boolean synchronizeCalendarFrom(InputStream in, String source, Kalendar targetCalendar); /** * Create Ores Helper object. @@ -297,4 +356,10 @@ public interface CalendarManager { * @return OLATResourceable for given Kalendar */ public OLATResourceable getOresHelperFor(Kalendar cal); + + + + + + } diff --git a/src/main/java/org/olat/commons/calendar/CalendarManagerFactory.java b/src/main/java/org/olat/commons/calendar/CalendarManagerFactory.java deleted file mode 100644 index bc70a57aceb..00000000000 --- a/src/main/java/org/olat/commons/calendar/CalendarManagerFactory.java +++ /dev/null @@ -1,59 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.commons.calendar; - -import java.io.File; - -import org.olat.core.logging.OLATRuntimeException; -import org.olat.core.util.WebappHelper; - -public class CalendarManagerFactory { - - private static CalendarManagerFactory INSTANCE; - private static CalendarManager DEFAULT_MANAGER; - - private CalendarManagerFactory(File fBaseDirectory) { - // singleton - File calendarBase = new File(fBaseDirectory, "/calendars"); - if (!calendarBase.exists()) { - if (!calendarBase.mkdirs()) - throw new OLATRuntimeException("Error creating calendar base directory at: " + calendarBase.getAbsolutePath(), null); - } - DEFAULT_MANAGER = new ICalFileCalendarManager(calendarBase); - } - - public static final CalendarManagerFactory getInstance() { - if (INSTANCE == null) { - File root = new File(WebappHelper.getUserDataRoot()); - INSTANCE = new CalendarManagerFactory(root); - } - return INSTANCE; - } - - public CalendarManager getCalendarManager() { - return DEFAULT_MANAGER; - } -} diff --git a/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationManager.java b/src/main/java/org/olat/commons/calendar/CalendarNotificationManager.java similarity index 96% rename from src/main/java/org/olat/commons/calendar/notification/CalendarNotificationManager.java rename to src/main/java/org/olat/commons/calendar/CalendarNotificationManager.java index a71a82db466..24611cea5a3 100644 --- a/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationManager.java +++ b/src/main/java/org/olat/commons/calendar/CalendarNotificationManager.java @@ -17,7 +17,7 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.commons.calendar.notification; +package org.olat.commons.calendar; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.commons.services.notifications.SubscriptionContext; diff --git a/src/main/java/org/olat/commons/calendar/CalendarUtils.java b/src/main/java/org/olat/commons/calendar/CalendarUtils.java index 29cd1b4ae8c..e2e448e13e3 100644 --- a/src/main/java/org/olat/commons/calendar/CalendarUtils.java +++ b/src/main/java/org/olat/commons/calendar/CalendarUtils.java @@ -31,12 +31,18 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; +import org.olat.commons.calendar.model.Kalendar; +import org.olat.commons.calendar.model.KalendarEvent; +import org.olat.commons.calendar.model.KalendarRecurEvent; +import org.olat.core.CoreSpringFactory; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; + import net.fortuna.ical4j.model.DateList; import net.fortuna.ical4j.model.DateTime; import net.fortuna.ical4j.model.Recur; @@ -46,13 +52,6 @@ import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.ExDate; import net.fortuna.ical4j.model.property.RRule; -import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarEvent; -import org.olat.commons.calendar.model.KalendarRecurEvent; -import org.olat.core.CoreSpringFactory; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; - public class CalendarUtils { private static final OLog log = Tracing.createLoggerFor(CalendarUtils.class); private static final SimpleDateFormat ical4jFormatter = new SimpleDateFormat("yyyyMMdd"); @@ -82,18 +81,6 @@ public class CalendarUtils { return cal; } - public static Calendar getStartOfWeekCalendar(int year, int weekOfYear, Locale locale) { - Calendar cal = createCalendarInstance(locale); - cal.clear(); - cal.set(Calendar.YEAR, year); - cal.set(Calendar.WEEK_OF_YEAR, weekOfYear); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); - return cal; - } - public static Calendar getStartOfDayCalendar(Locale locale) { Calendar cal = createCalendarInstance(locale); cal.set(Calendar.HOUR_OF_DAY, 0); @@ -102,65 +89,8 @@ public class CalendarUtils { cal.set(Calendar.MILLISECOND, 0); return cal; } - - /** - * Find all events in this calendar with the given subject. - * - * @param calendar - * @param subject - * @return - */ - public static List<KalendarEvent> findEvents(Kalendar calendar, String subject, String location, Date beginPeriod, Date endPeriod, boolean publicOnly) { - List<KalendarEvent> results = new ArrayList<KalendarEvent>(); - Collection<KalendarEvent> events = calendar.getEvents(); - String regExSubject = subject.replace("*", ".*"); - String regExLocation = location.replace("*", ".*"); - regExSubject = ".*" + regExSubject + ".*"; - regExLocation = ".*" + regExLocation + ".*"; - for (Iterator<KalendarEvent> iter = events.iterator(); iter.hasNext();) { - KalendarEvent event = iter.next(); - if (publicOnly && event.getClassification() != KalendarEvent.CLASS_PUBLIC) continue; - if (beginPeriod != null && event.getBegin().before(beginPeriod)) continue; - if (endPeriod != null && event.getEnd().after(endPeriod)) continue; - String eventSubject = event.getSubject().toLowerCase(); - eventSubject = eventSubject.replace("\n", " "); - eventSubject = eventSubject.replace("\r", " "); - if ( (subject != null) && !subject.trim().isEmpty() && !eventSubject.matches(regExSubject.toLowerCase())) { - log.debug("Does not add event because subject did not match eventSubject=" + eventSubject + " regExSubject=" + regExSubject); - continue; - } - if ( (location != null) && !location.trim().isEmpty() && (event.getLocation() != null) ) { - String eventLocation = event.getLocation().toLowerCase(); - eventLocation = eventLocation.replace("\n", " "); - eventLocation = eventLocation.replace("\r", " "); - if ( !eventLocation.matches(regExLocation.toLowerCase()) ) { - log.debug("Does not add event because subject did not match eventLocation=" + eventLocation + " regExLocation=" + regExLocation); - continue; - } - } - log.debug("add to results event.Location=" + event.getLocation() + " ,Subject=" + event.getSubject()); - results.add(event); - CalendarManager cm = CalendarManagerFactory.getInstance().getCalendarManager(); - Date periodStart = beginPeriod == null ? event.getBegin() : beginPeriod; - long year = 60 * 60 * 24 * 365 * 1000; - Date periodEnd = endPeriod == null ? new Date(periodStart.getTime() + year) : endPeriod; - List<KalendarRecurEvent> lstEvnt = cm.getRecurringDatesInPeriod(periodStart, periodEnd, event); - for ( KalendarRecurEvent recurEvent : lstEvnt ) { - if (publicOnly && event.getClassification() != KalendarEvent.CLASS_PUBLIC) continue; - if (beginPeriod != null && event.getBegin().before(beginPeriod)) continue; - if (endPeriod != null && event.getEnd().after(endPeriod)) continue; - if (subject != null && !event.getSubject().matches(regExSubject)) continue; - if (location != null && !event.getLocation().matches(regExLocation)) continue; - if ((subject != null) && !subject.trim().isEmpty() && !event.getSubject().toLowerCase().matches(regExSubject.toLowerCase())) continue; - if ((location != null) && !location.trim().isEmpty() && !event.getLocation().toLowerCase().matches(regExLocation.toLowerCase())) continue; - results.add(recurEvent); - } - } - Collections.sort(results); - return results; - } - protected static DateList getRecurringsInPeriod(Date periodStart, Date periodEnd, KalendarEvent kEvent) { + public static DateList getRecurringsInPeriod(Date periodStart, Date periodEnd, KalendarEvent kEvent) { DateList recurDates = null; String recurrenceRule = kEvent.getRecurrenceRule(); if(recurrenceRule != null && !recurrenceRule.equals("")) { @@ -257,10 +187,10 @@ public class CalendarUtils { public static List<KalendarEvent> listEventsForPeriod(Kalendar calendar, Date periodStart, Date periodEnd) { List<KalendarEvent> periodEvents = new ArrayList<KalendarEvent>(); + CalendarManager cm = CoreSpringFactory.getImpl(CalendarManager.class); Collection<KalendarEvent> events = calendar.getEvents(); for (Iterator<KalendarEvent> iter = events.iterator(); iter.hasNext();) { KalendarEvent event = iter.next(); - CalendarManager cm = CalendarManagerFactory.getInstance().getCalendarManager(); List<KalendarRecurEvent> lstEvnt = cm.getRecurringDatesInPeriod(periodStart, periodEnd, event); for ( KalendarRecurEvent recurEvent : lstEvnt ) { periodEvents.add(recurEvent); @@ -436,7 +366,5 @@ public class CalendarUtils { cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); - - } } \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/CalendarWebDAVProvider.java b/src/main/java/org/olat/commons/calendar/CalendarWebDAVProvider.java index 430b0c6e3e5..70e06196f97 100644 --- a/src/main/java/org/olat/commons/calendar/CalendarWebDAVProvider.java +++ b/src/main/java/org/olat/commons/calendar/CalendarWebDAVProvider.java @@ -27,6 +27,7 @@ package org.olat.commons.calendar; import java.io.File; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.webdav.WebDAVProvider; import org.olat.core.id.IdentityEnvironment; import org.olat.core.util.vfs.LocalFileImpl; @@ -48,7 +49,7 @@ public class CalendarWebDAVProvider implements WebDAVProvider { VirtualContainer calendars = new VirtualContainer("calendars"); calendars.setLocalSecurityCallback(new ReadOnlyCallback()); // get private calendar - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); File fPersonalCalendar = calendarManager.getCalendarICalFile(CalendarManager.TYPE_USER, identityEnv.getIdentity().getName()); calendars.addItem(new LocalFileImpl(fPersonalCalendar)); return calendars; diff --git a/src/main/java/org/olat/commons/calendar/GotoDateEvent.java b/src/main/java/org/olat/commons/calendar/GotoDateEvent.java deleted file mode 100644 index f046ccdfbd2..00000000000 --- a/src/main/java/org/olat/commons/calendar/GotoDateEvent.java +++ /dev/null @@ -1,53 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.commons.calendar; - -import java.util.Date; - -import org.olat.core.gui.control.Event; - -/** - * Event to jump to certain date. - * - * <P> - * Initial Date: 05.02.2008 - * @author cg - */ -public class GotoDateEvent extends Event { - - private static final long serialVersionUID = -6916106022637446581L; - private Date gotoDate; - - public GotoDateEvent(Date gotoDate) { - super("gotodateevent"); - this.gotoDate = gotoDate; - } - - public Date getGotoDate() { - return gotoDate; - } - -} diff --git a/src/main/java/org/olat/commons/calendar/ICalServlet.java b/src/main/java/org/olat/commons/calendar/ICalServlet.java index 1f0a4598ffb..e2371e39e01 100644 --- a/src/main/java/org/olat/commons/calendar/ICalServlet.java +++ b/src/main/java/org/olat/commons/calendar/ICalServlet.java @@ -29,24 +29,31 @@ package org.olat.commons.calendar; import java.io.IOException; import java.net.URISyntaxException; import java.util.Iterator; +import java.util.List; import java.util.StringTokenizer; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.olat.commons.calendar.model.CalendarFileInfos; +import org.olat.commons.calendar.model.CalendarUserConfiguration; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; -import org.olat.core.dispatcher.DispatcherModule; +import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.i18n.I18nManager; import net.fortuna.ical4j.data.CalendarOutputter; import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.ComponentList; import net.fortuna.ical4j.model.PropertyList; import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.component.VEvent; +import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.Url; import net.fortuna.ical4j.model.property.XProperty; @@ -63,6 +70,14 @@ public class ICalServlet extends HttpServlet { private static final long serialVersionUID = -155266285395912535L; private static final OLog log = Tracing.createLoggerFor(ICalServlet.class); + + /** collection of iCal feed prefixs **/ + public static final String[] SUPPORTED_PREFIX = { + CalendarManager.ICAL_PREFIX_AGGREGATED, + CalendarManager.ICAL_PREFIX_PERSONAL, + CalendarManager.ICAL_PREFIX_COURSE, + CalendarManager.ICAL_PREFIX_GROUP + }; /** * Default constructor. @@ -71,9 +86,6 @@ public class ICalServlet extends HttpServlet { // } - /** - * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) - */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Tracing.setUreq(req); @@ -87,146 +99,150 @@ public class ICalServlet extends HttpServlet { } } - /** - * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, - * javax.servlet.http.HttpServletResponse) - */ @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) { - Calendar icalDoc = null; - - final boolean debug = log.isDebug(); + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + String requestUrl = request.getPathInfo(); try { - String pathInfo = request.getPathInfo(); - if (debug) { - log.debug("doGet pathInfo=" + pathInfo); + if (log.isDebug()) { + log.debug("doGet pathInfo=" + requestUrl); } - if ((pathInfo == null) || (pathInfo.equals(""))) { + if ((requestUrl == null) || (requestUrl.equals(""))) { return; // error } - if (checkPath(pathInfo)) { - icalDoc = getIcalDocument(pathInfo); - if (icalDoc == null) { - DispatcherModule.sendNotFound(pathInfo, response); - return; - } - } else { - DispatcherModule.sendNotFound(pathInfo, response); - return; - } - - // OLAT-5243 related: sending back the reply can take arbitrary - // long, - // considering slow end-user connections for example - or a sudden - // death of the connection - // on the client-side which remains unnoticed (network partitioning) - DBFactory.getInstance().commitAndCloseSession(); - - // output the calendar to the stream - CalendarOutputter calOut = new CalendarOutputter(false); - calOut.output(icalDoc, response.getOutputStream()); + getIcalDocument(requestUrl, response); } catch (ValidationException e) { - // throw olat exception for nice logging log.warn("Validation Error when generate iCal stream for path::" + request.getPathInfo(), e); - DispatcherModule.sendNotFound("none", response); + response.sendError(HttpServletResponse.SC_CONFLICT, requestUrl); } catch (IOException e) { - // throw olat exception for nice logging log.warn("IOException Error when generate iCal stream for path::" + request.getPathInfo(), e); - DispatcherModule.sendNotFound("none", response); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, requestUrl); } catch (Exception e) { log.warn("Unknown Error in icalservlet", e); - DispatcherModule.sendNotFound("none", response); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, requestUrl); } } - + /** - * Checks the path information to match the prefixs in - * ICalTokenGenerator.ICAL_PREFIX_COLLECTION + * Reads in the appropriate ics file, depending upon the pathInfo:<br> + * <ul> + * <li>/aggregated/<config key>/AUTH_TOKEN.ics</li> + * <li>/user/<user_name>/AUTH_TOKEN.ics</li> + * <li>/group/<user_name>/AUTH_TOKEN/<group_id>.ics</li> + * <li>/course/<user_name>/AUTH_TOKEN/<course_unique_id>.ics</li> + * </ul> * - * @param icalFeedPath - * @return boolean + * @param pathInfo + * @return Calendar */ - private boolean checkPath(String icalFeedPath) { - // pathInfo is like: - // /user/<user_name>/AUTH_TOKEN.ics - // /group/<user_name>/AUTH_TOKEN/<group_id>.ics - // /course/<user_name>/AUTH_TOKEN/<course_unique_id>.ics + private void getIcalDocument(String requestUrl, HttpServletResponse response) + throws ValidationException, IOException { + // get the individual path tokens + String pathInfo = requestUrl.replaceAll(".ics", ""); + String[] pathInfoTokens = pathInfo.split("/"); + if(pathInfoTokens.length < 4) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, requestUrl); + return; + } + + String calendarType = pathInfoTokens[1]; + String userName = pathInfoTokens[2]; + String authToken = pathInfoTokens[3]; - // check the type of calendar - boolean calendarTypeMatched = false; - for (int prefixIndex = 0; prefixIndex < ICalTokenGenerator.ICAL_PREFIX_COLLECTION.length; prefixIndex++) { - if (icalFeedPath.indexOf(ICalTokenGenerator.ICAL_PREFIX_COLLECTION[prefixIndex]) == 0) { - calendarTypeMatched = true; + String calendarID; + if(CalendarManager.TYPE_COURSE.equals(calendarType) || CalendarManager.TYPE_GROUP.equals(calendarType)) { + if(pathInfoTokens.length < 5) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, requestUrl); + return; } + calendarID = pathInfoTokens[4]; + } else if(CalendarManager.TYPE_USER.equals(calendarType)) { + calendarID = userName; + } else if(CalendarManager.TYPE_USER_AGGREGATED.equals(calendarType)) { + calendarID = userName; + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, requestUrl); + log.warn("Type not supported: " + pathInfo); + return; } - if (calendarTypeMatched) { - // check the number of tokens in the icalFeedPath - int numberOfTokens = icalFeedPath.split("/").length - ICalTokenGenerator.ICAL_PATH_SHIFT; - if (isRequestForPersonalCalendarFeed(icalFeedPath)) { - return (numberOfTokens == ICalTokenGenerator.ICAL_PERSONAL_PATH_TOKEN_LENGTH); + + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); + if(CalendarManager.TYPE_USER_AGGREGATED.equals(calendarType)) { + // check the authentication token + CalendarUserConfiguration config = calendarManager.getCalendarUserConfiguration(Long.parseLong(userName)); + String savedToken = config.getToken(); + if (authToken == null || savedToken == null || !savedToken.equals(authToken)) { + log.warn("Authenticity Check failed for the ical feed path: " + pathInfo); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, requestUrl); } else { - return (numberOfTokens == ICalTokenGenerator.ICAL_PATH_TOKEN_LENGTH); + generateAggregatedCalendar(config.getIdentity(), response); + } + } else if (calendarManager.calendarExists(calendarType, calendarID)) { + // check the authentication token + String savedToken = calendarManager.getCalendarToken(calendarType, calendarID, userName); + if (authToken == null || savedToken == null || !savedToken.equals(authToken)) { + log.warn("Authenticity Check failed for the ical feed path: " + pathInfo); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, requestUrl); + } else { + // read and return the calendar file + Calendar calendar = calendarManager.readCalendar(calendarType, calendarID); + updateUrlProperties(calendar); + DBFactory.getInstance().commitAndCloseSession(); + new CalendarOutputter(false).output(calendar, response.getOutputStream()); } - } - return false; - } - - /** - * checks whether the iCal feed request is for a personal calendar - * - * @param icalFeedPath - * @return boolean - */ - private boolean isRequestForPersonalCalendarFeed(String icalFeedPath) { - if (icalFeedPath.indexOf(ICalTokenGenerator.ICAL_PREFIX_PERSONAL) != 0) { - return false; } else { - return true; + response.sendError(HttpServletResponse.SC_NOT_FOUND, requestUrl); } } - - /** - * Reads in the appropriate ics file, depending upon the pathInfo - * - * @param pathInfo - * @return Calendar - */ - private Calendar getIcalDocument(String pathInfo) { - // pathInfo is like: - // /user/<user_name>/AUTH_TOKEN.ics - // /group/<user_name>/AUTH_TOKEN/<group_id>.ics - // /course/<user_name>/AUTH_TOKEN/<course_unique_id>.ics + + private void generateAggregatedCalendar(Identity identity, HttpServletResponse response) throws IOException { + PersonalCalendarManager homeCalendarManager = CoreSpringFactory.getImpl(PersonalCalendarManager.class); + if(identity == null) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } else { + List<CalendarFileInfos> iCalFiles = homeCalendarManager.getListOfCalendarsFiles(identity); + DBFactory.getInstance().commitAndCloseSession(); + + ServletOutputStream out = response.getOutputStream(); + out.print(Calendar.BEGIN); + out.print(':'); + out.println(Calendar.VCALENDAR); - // get the individual path tokens - pathInfo = pathInfo.replaceAll(".ics", ""); - String[] pathInfoTokens = pathInfo.split("/"); - String calendarType = pathInfoTokens[0 + ICalTokenGenerator.ICAL_PATH_SHIFT]; - String userName = pathInfoTokens[1 + ICalTokenGenerator.ICAL_PATH_SHIFT]; - String authToken = pathInfoTokens[2 + ICalTokenGenerator.ICAL_PATH_SHIFT]; - String calendarID = userName; - if (!isRequestForPersonalCalendarFeed(pathInfo)) { - calendarID = pathInfoTokens[3 + ICalTokenGenerator.ICAL_PATH_SHIFT]; + int numOfFiles = iCalFiles.size(); + for(int i=0; i<numOfFiles; i++) { + outputCalendar(iCalFiles.get(i), out); + } + + out.print(Calendar.END); + out.print(':'); + out.print(Calendar.VCALENDAR); } - - // check the authentication token - if (!checkPathAuthenticity(calendarType, userName, authToken, calendarID)) { - log.warn("Authenticity Check failed for the ical feed path: " + pathInfo); - return null; + } + + private void outputCalendar(CalendarFileInfos fileInfos, ServletOutputStream out) throws IOException { + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); + Calendar calendar = calendarManager.readCalendar(fileInfos.getCalendarFile()); + updateUrlProperties(calendar); + + String prefix = fileInfos.getType() + "-" + fileInfos.getCalendarId() + "-"; + updateUUID(calendar, prefix); + + ComponentList events = calendar.getComponents(); + for (final Iterator<?> i = events.iterator(); i.hasNext();) { + out.print(i.next().toString()); } - - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); - - // check if the calendar exists (calendars are only persisted when an - // event is created) - if (calendarManager.calendarExists(calendarType, calendarID)) { - // read and return the calendar file - Calendar calendar = calendarManager.readCalendar(calendarType, calendarID); - updateUrlProperties(calendar); - return calendar; - } else { - // return an empty calendar file - return new Calendar(); + } + + private void updateUUID(Calendar calendar, String prefix) { + for (Iterator<?> eventIter = calendar.getComponents().iterator(); eventIter.hasNext();) { + Object comp = eventIter.next(); + if (comp instanceof VEvent) { + VEvent event = (VEvent)comp; + Uid uid = event.getUid(); + String newUid = prefix.concat(uid.getValue()); + uid.setValue(newUid); + } } } @@ -271,24 +287,4 @@ public class ICalServlet extends HttpServlet { } } } - - /** - * checks the AUTH_TOKEN in the iCal feed path - * - * @param type Type of calendar, i.e. User, Group or Course - * @param userName Name of the User - * @param authToken Authentication token for the calendar - * @param icsFileName Name of the ics file - * @return boolean - */ - private boolean checkPathAuthenticity(String calendarType, String userName, String authToken, String calendarID) { - // get the authentication token stored in the database - String authTokenFromDb = ICalTokenGenerator.getIcalAuthToken(calendarType, calendarID, userName, false); - // check if the token from db matches the token in the url - if (authTokenFromDb == null) { - return false; - } else { - return authTokenFromDb.equals(authToken); - } - } } \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ICalTokenGenerator.java b/src/main/java/org/olat/commons/calendar/ICalTokenGenerator.java deleted file mode 100644 index d8cfaf950cc..00000000000 --- a/src/main/java/org/olat/commons/calendar/ICalTokenGenerator.java +++ /dev/null @@ -1,403 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.lang.RandomStringUtils; -import org.olat.basesecurity.BaseSecurityManager; -import org.olat.commons.calendar.model.ICalToken; -import org.olat.core.CoreSpringFactory; -import org.olat.core.helpers.Settings; -import org.olat.core.id.Identity; -import org.olat.core.id.OLATResourceable; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; -import org.olat.course.CourseFactory; -import org.olat.group.BusinessGroupService; -import org.olat.properties.NarrowedPropertyManager; -import org.olat.properties.Property; -import org.olat.properties.PropertyManager; - - -/** - * Description:<BR> - * Constants and helper methods for the OLAT iCal feeds - * - * <P> - * Initial Date: June 2, 2008 - * - * @author Udit Sajjanhar - */ -public class ICalTokenGenerator { - - private static final OLog log = Tracing.createLoggerFor(ICalTokenGenerator.class); - - /** Authentication provider name for iCal authentication **/ - public static final String ICAL_AUTH_PROVIDER = "ICAL-OLAT"; - /** Key under which the users iCal token is beeing kept in the http session **/ - public static final String ICAL_AUTH_TOKEN_KEY = "icaltoken"; - - /** OLAT server URL **/ - public static final String URI_SERVER = Settings.getServerContextPathURI() + "/"; - /** path prefix for personal iCal feed **/ - public static final String ICAL_PREFIX_PERSONAL = "/user/"; - /** path prefix for course iCal feed **/ - public static final String ICAL_PREFIX_COURSE = "/course/"; - /** path prefix for group iCal feed **/ - public static final String ICAL_PREFIX_GROUP = "/group/"; - - public static final int ICAL_PATH_SHIFT = 1; - /** Expected number of tokens in the course/group calendar link **/ - public static final int ICAL_PATH_TOKEN_LENGTH = 4; - /** Expected number of tokens in the personal calendar link **/ - public static final int ICAL_PERSONAL_PATH_TOKEN_LENGTH = ICAL_PATH_TOKEN_LENGTH - 1; - - - /** collection of iCal feed prefixs **/ - public static final String[] ICAL_PREFIX_COLLECTION = {ICAL_PREFIX_PERSONAL, - ICAL_PREFIX_COURSE, - ICAL_PREFIX_GROUP}; - - /** category for the iCal AUTH_TOKEN property **/ - public static final String PROP_CAT_ICALTOKEN = "icalAuthToken"; - /** name for the iCal AUTH_TOKEN property **/ - public static final String PROP_NAME_ICALTOKEN = "authToken"; - - private static String createIcalAuthToken(OLATResourceable resourceable, Identity identity) { - // generate the random alpha numeric token - String token = RandomStringUtils.randomAlphanumeric(6); - - // save token as a property of resourceable - NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(resourceable); - Property tokenProperty = npm.createPropertyInstance(identity, null, - PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN, null, null, token, null ); - npm.saveProperty(tokenProperty); - - //return the token generated - return token; - } - - private static String createIcalAuthToken(Identity identity) { - // generate the random alpha numeric token - String token = RandomStringUtils.randomAlphabetic(6); - - // save token as a property of resourceable - PropertyManager pm = PropertyManager.getInstance(); - Property tokenProperty = pm.createPropertyInstance(identity, null, - null, PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN, null, null, token, null ); - pm.saveProperty(tokenProperty); - - //return the generated token - return token; - } - - public static List<ICalToken> getICalAuthTokens(Identity identity) { - PropertyManager pm = PropertyManager.getInstance(); - List<Property> tokenProperties = pm.findAllUserProperties(identity, PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - List<ICalToken> tokens = new ArrayList<>(); - for(Property tokenProperty:tokenProperties) { - Long resourceId = tokenProperty.getResourceTypeId(); - String resourceName = tokenProperty.getResourceTypeName(); - String value = tokenProperty.getStringValue(); - - String type; - if(resourceId == null) { - type = CalendarManager.TYPE_USER; - resourceId = identity.getKey(); - } else if("CourseModule".equals(resourceName)) { - type = CalendarManager.TYPE_COURSE; - } else if("BusinessGroup".equals(resourceName)) { - type = CalendarManager.TYPE_GROUP; - } else { - continue; - } - tokens.add(new ICalToken(type, value, resourceId)); - } - return tokens; - } - - private static String getIcalAuthToken(OLATResourceable resourceable, Identity identity, boolean create) { - // find the property for the resourceable - NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(resourceable); - Property tokenProperty = npm.findProperty(identity, null, - PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - - String token; - if (tokenProperty == null && create) { - token = createIcalAuthToken(resourceable, identity); - } else { - token = tokenProperty.getStringValue(); - } - - // return the string value for the property - return token; - } - - private static String getIcalAuthToken(Identity identity, boolean create) { - // find the property for the identity - PropertyManager pm = PropertyManager.getInstance(); - Property tokenProperty = pm.findProperty(identity, null, null, - PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - - String token; - if (tokenProperty == null && create) { - token = createIcalAuthToken(identity); - } else { - token = tokenProperty.getStringValue(); - } - - // return the string value for the property - return token; - } - - private static String regenerateIcalAuthToken(OLATResourceable resourceable, Identity identity) { - // find the property for the resourceable - NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(resourceable); - Property tokenProperty = npm.findProperty(identity, null, - PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - - //genearate the new token - String authToken = RandomStringUtils.randomAlphanumeric(6); - - //set new auth token as the string value of the property - tokenProperty.setStringValue(authToken); - - // update the property - npm.updateProperty(tokenProperty); - - //return the new token - return authToken; - } - - private static String regenerateIcalAuthToken(Identity identity) { - // find the property for the identity - PropertyManager pm = PropertyManager.getInstance(); - Property tokenProperty = pm.findProperty(identity, null, null, - PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - - //genearate the new token - String authToken = RandomStringUtils.randomAlphanumeric(6); - - //set new auth token as the string value of the property - tokenProperty.setStringValue(authToken); - - // update the property - pm.updateProperty(tokenProperty); - - //return the new token - return authToken; - } - - public static void destroyIcalAuthToken(String calendarType, String calendarID, Identity identity) { - if (!calendarType.equals(ICalFileCalendarManager.TYPE_USER)) { - // find the property for the resourceable - OLATResourceable resourceable = getResourceable(calendarType, calendarID); - NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(resourceable); - Property tokenProperty = npm.findProperty(identity, null, - PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - if (tokenProperty != null) { - npm.deleteProperty(tokenProperty); - } - } else { - PropertyManager pm = PropertyManager.getInstance(); - Property tokenProperty = pm.findProperty(identity, null, null, - PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - if (tokenProperty != null) { - pm.deleteProperty(tokenProperty); - } - } - } - - private static Identity getIdentity (String userName) { - Identity identity = BaseSecurityManager.getInstance().findIdentityByName(userName); - if (identity == null) { - // error - abort - log.error("Identity not found for the username: " + userName); - } - return identity; - } - - private static OLATResourceable getResourceable(String calendarType, String calendarID) { - OLATResourceable resourceable ; - - - if (calendarType.equals(ICalFileCalendarManager.TYPE_GROUP)) { - // get the group - resourceable = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(new Long(calendarID)); - if (resourceable == null) { - // error - log.error("Group not found for the Resourceableid: " + calendarID); - return null; - } - } else if ((calendarType.equals(ICalFileCalendarManager.TYPE_COURSE))) { - try { - // get the course - resourceable = CourseFactory.loadCourse(new Long(Long.parseLong(calendarID))); - } catch (Exception e) { - log.error("Course not found for the Resourceableid: " + calendarID); - return null; - } - } else { - // error - abort - log.error("Unmatching Calendar Type: " + calendarType); - return null; - } - - return resourceable; - } - - private static String constructIcalFeedPath(String calendarType, String userName, String authToken, String calendarID) { - if (calendarType.equals(ICalFileCalendarManager.TYPE_USER)) { - return URI_SERVER + "ical" + "/" + calendarType + "/" + userName + "/" + authToken + ".ics"; - } else { - return URI_SERVER + "ical" + "/" + calendarType + "/" + userName + "/" + authToken + "/" + calendarID + ".ics"; - } - } - - /** - * returns the authentication token for the calendar type and calendar id. - * authentication token is stored as a property. - * @param calendarType - * @param calendarID - * @param userName - * @param createToken create a new token if it doesn't exist - * @return authentication token - */ - public static String getIcalAuthToken(String calendarType, String calendarID, String userName, boolean createToken) { - - // get the identity of the user - Identity identity = getIdentity(userName); - if (identity == null) { - return null; - } - - return getIcalAuthToken(calendarType, calendarID, identity, createToken); - } - - /** - * returns the authentication token for the calendar type and calendar id. - * authentication token is stored as a property. - * @param calendarType - * @param calendarID - * @param identity - * @param createToken createToken create a new token if it doesn't exist - * @return authentication token - */ - public static String getIcalAuthToken(String calendarType, String calendarID, Identity identity, boolean createToken) { - - if (!calendarType.equals(ICalFileCalendarManager.TYPE_USER)) { - // get the resourceable - OLATResourceable resourceable = getResourceable(calendarType, calendarID); - if (resourceable == null) { - return null; - } - return getIcalAuthToken(resourceable, identity, createToken); - } else { - return getIcalAuthToken(identity, createToken); - } - } - - /** - * regenerates the authentication token for the calendar type and calendar id. - * returns the generated token - * @param calendarType - * @param calendarID - * @param identity - * @return authentication token - */ - public static FeedLink regenerateIcalAuthToken(String calendarType, String calendarID, Identity identity) { - String authToken; - if (!calendarType.equals(ICalFileCalendarManager.TYPE_USER)) { - // get the resourceable - OLATResourceable resourceable = getResourceable(calendarType, calendarID); - if (resourceable == null) { - return null; - } - authToken = regenerateIcalAuthToken(resourceable, identity); - } else { - authToken = regenerateIcalAuthToken(identity); - } - String path = constructIcalFeedPath(calendarType, identity.getName(), authToken, calendarID); - return new FeedLink(authToken, path); - } - - /** - * return the ical feed link for the calendar. - * authentication token is created if it doesn't exist already. - * @param calendarType - * @param calendarID - * @param identity - * @return - */ - public static FeedLink getIcalFeedLink(String calendarType, String calendarID, Identity identity) { - String authToken = getIcalAuthToken(calendarType, calendarID, identity, true); - String path = constructIcalFeedPath(calendarType, identity.getName(), authToken, calendarID); - return new FeedLink(authToken, path); - } - - /** - * Check if iCalFeedLink exist - * @param calendarType - * @param calendarID - * @param identity - * @return - */ - public static boolean existIcalFeedLink(String calendarType, String calendarID, Identity identity) { - Property tokenProperty = null; - if (!calendarType.equals(ICalFileCalendarManager.TYPE_USER)) { - // find the property for the resourceable - OLATResourceable resourceable = getResourceable(calendarType, calendarID); - NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(resourceable); - tokenProperty = npm.findProperty(identity, null, PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - } else { - PropertyManager pm = PropertyManager.getInstance(); - tokenProperty = pm.findProperty(identity, null, null, PROP_CAT_ICALTOKEN, PROP_NAME_ICALTOKEN); - } - return tokenProperty != null; - } - - public static class FeedLink { - - private final String token; - private final String link; - - public FeedLink(String token, String link) { - this.token = token; - this.link = link; - } - - public String getToken() { - return token; - } - - public String getLink() { - return link; - } - } -} diff --git a/src/main/java/org/olat/commons/calendar/ImportCalendarJob.java b/src/main/java/org/olat/commons/calendar/ImportCalendarJob.java index db1c19eddaa..72beccee307 100644 --- a/src/main/java/org/olat/commons/calendar/ImportCalendarJob.java +++ b/src/main/java/org/olat/commons/calendar/ImportCalendarJob.java @@ -25,6 +25,8 @@ */ package org.olat.commons.calendar; +import org.olat.commons.calendar.manager.ImportToCalendarManager; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.scheduler.JobWithDB; import org.quartz.JobExecutionContext; @@ -41,7 +43,7 @@ public class ImportCalendarJob extends JobWithDB { @Override public void executeWithDB(JobExecutionContext context) { try { - ImportCalendarManager.updateCalendarIn(); + CoreSpringFactory.getImpl(ImportToCalendarManager.class).updateCalendarIn(); } catch (Exception e) { log.error("", e); } diff --git a/src/main/java/org/olat/commons/calendar/ImportCalendarManager.java b/src/main/java/org/olat/commons/calendar/ImportCalendarManager.java deleted file mode 100644 index d56dce5fbd2..00000000000 --- a/src/main/java/org/olat/commons/calendar/ImportCalendarManager.java +++ /dev/null @@ -1,355 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.StringTokenizer; -import java.util.regex.Pattern; - -import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarComparator; -import org.olat.commons.calendar.model.KalendarConfig; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.persistence.DBFactory; -import org.olat.core.gui.UserRequest; -import org.olat.core.id.Identity; -import org.olat.core.id.OLATResourceable; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; -import org.olat.core.manager.BasicManager; -import org.olat.core.util.StringHelper; -import org.olat.core.util.resource.OresHelper; -import org.olat.properties.Property; -import org.olat.properties.PropertyManager; - - -/** - * Description:<BR> - * Constants and helper methods for the OLAT iCal feeds - * - * <P> - * Initial Date: July 22, 2008 - * - * @author Udit Sajjanhar - */ -public class ImportCalendarManager extends BasicManager { - private static final OLog log = Tracing.createLoggerFor(ImportCalendarManager.class); - - public static String PROP_CATEGORY = "Imported-Calendar"; - public static String PROP_CATEGORY_IMP = "Imported-Calendar-To"; - - - public static boolean importCalendarIn(Kalendar cal, String importUrl) { - try { - String calendarContent = getContentFromUrl(importUrl); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar importedCal = calManager.buildKalendarFrom(calendarContent, cal.getType(), cal.getCalendarID()); - boolean imported = calManager.updateCalendar(cal, importedCal); - if(imported) { - PropertyManager pm = PropertyManager.getInstance(); - List<Property> p = pm.findProperties(null, null, cal.getType(), 0l, PROP_CATEGORY_IMP, cal.getCalendarID()); - if(p.isEmpty()) { - long timestamp = System.currentTimeMillis(); - OLATResourceable ores = OresHelper.createOLATResourceableInstance(cal.getType(), 0l); - Property prop = pm.createPropertyInstance(null, null, ores, PROP_CATEGORY_IMP, cal.getCalendarID(), null, timestamp, null, importUrl); - pm.saveProperty(prop); - } else { - Property prop = p.get(0); - String textVal = prop.getTextValue(); - if(!textVal.contains(importUrl)) { - prop.setTextValue(textVal + "|" + importUrl); - } - } - } - return imported; - } catch (Exception e) { - log.error("", e); - return false; - } - } - - /** - * Method used by the cron job - * @return - */ - public static boolean updateCalendarIn() { - List<Property> properties = loadPropertiesByImportCategory(); - log.audit("Begin to update " + properties.size() + " calendars."); - - int count = 0; - for(Property property:properties) { - String type = property.getResourceTypeName(); - String id = property.getName(); - String importUrls = property.getTextValue(); - if(!StringHelper.containsNonWhitespace(importUrls) || !StringHelper.containsNonWhitespace(type) || !StringHelper.containsNonWhitespace(id)) { - continue; - } - - for(StringTokenizer tokenizer = new StringTokenizer(importUrls, "|"); tokenizer.hasMoreTokens(); ) { - String importUrl = tokenizer.nextToken(); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = calManager.getCalendar(type + "dru", id); - if(importCalendarIn(cal, importUrl)) { - log.audit("Updated successfully calendar: " + type + " / " + id); - } else { - log.audit("Failed to update calendar: " + type + " / " + id); - } - - if(count++ % 20 == 0) { - DBFactory.getInstance().intermediateCommit(); - } - } - } - return false; - } - - private static List<Property> loadPropertiesByImportCategory() { - StringBuilder sb = new StringBuilder(); - sb.append("select p from ").append(Property.class.getName()).append(" as p where ") - .append(" p.category = :category and p.resourceTypeId = :restypeid"); - - return DBFactory.getInstance().getCurrentEntityManager() - .createQuery(sb.toString(), Property.class) - .setParameter("category", PROP_CATEGORY_IMP) - .setParameter("restypeid", new Long(0)) - .getResultList(); - } - - public static boolean importCalendarIn(KalendarRenderWrapper calenderWrapper, InputStream in) { - try { - String calendarContent = getContentFromStream(in); - Kalendar cal = calenderWrapper.getKalendar(); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar importedCal = calManager.buildKalendarFrom(calendarContent, cal.getType(), cal.getCalendarID()); - return calManager.updateCalendar(cal, importedCal); - } catch (Exception e) { - log.error("", e); - return false; - } - } - - /** - * Save the imported calendar - * 1. make an entry in the database - * 2. save the calendar file - * @param calendarName - * @param ureq - * @param importUrl can be null when import from file - * @return - */ - public static void persistCalendar(String calendarName, UserRequest ureq, String importUrl) { - // move the temporary file to the permanent file - String tempCalendarID = getTempCalendarIDForUpload(ureq); - String importedCalendarID = getImportedCalendarID(ureq, calendarName); - String importedCalendarType = getImportedCalendarType(); - - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - File oldCalendarFile = calManager.getCalendarFile(importedCalendarType, tempCalendarID); - oldCalendarFile.renameTo(calManager.getCalendarFile(importedCalendarType, importedCalendarID)); - - // make the entry in the database - long timestamp = System.currentTimeMillis(); - PropertyManager pm = PropertyManager.getInstance(); - Property p = pm.createUserPropertyInstance(ureq.getIdentity(), PROP_CATEGORY, sanitize(calendarName), null, timestamp, importUrl, null); - pm.saveProperty(p); - } - - /** - * Delete an imported calendar - * 1. remove the entry from the database - * 2. delete the calendar file - * @param calendarID - * @param ureq - * @return - */ - public static void deleteCalendar(String calendarID, UserRequest ureq) { - String calendarName = getImportedCalendarNameFromID(ureq.getIdentity(), calendarID); - // remove the entry from the database - PropertyManager pm = PropertyManager.getInstance(); - Property p = pm.findUserProperty(ureq.getIdentity(), PROP_CATEGORY, calendarName); - pm.deleteProperty(p); - - // delete the calendar file - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - String importedCalendarID = getImportedCalendarID(ureq, calendarName); - String importedCalendarType = getImportedCalendarType(); - calManager.deleteCalendar(importedCalendarType, importedCalendarID); - } - - /** - * Get imported calendars for a user. - * @param ureq - * @return - */ - public static List<KalendarRenderWrapper> getImportedCalendarsForIdentity(UserRequest ureq) { - // initialize the calendars list - List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); - CalendarModule calendarModule = CoreSpringFactory.getImpl(CalendarModule.class); - if(calendarModule.isEnabled() && calendarModule.isEnablePersonalCalendar()) { - // read all the entries from the database - PropertyManager pm = PropertyManager.getInstance(); - List<Property> properties = pm.listProperties(ureq.getIdentity(), null, null, PROP_CATEGORY, null); - - // return the list of calendar objects - Iterator<Property> propertyIter = properties.iterator(); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - while (propertyIter.hasNext()) { - Property calendarProperty = propertyIter.next(); - String calendarName = calendarProperty.getName(); - KalendarRenderWrapper calendarWrapper = calManager.getImportedCalendar(ureq.getIdentity(), calendarName); - calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY); - calendarWrapper.setImported(true); - KalendarConfig importedKalendarConfig = calManager.findKalendarConfigForIdentity( - calendarWrapper.getKalendar(), ureq); - if (importedKalendarConfig != null) { - calendarWrapper.getKalendarConfig().setCss(importedKalendarConfig.getCss()); - calendarWrapper.getKalendarConfig().setVis(importedKalendarConfig.isVis()); - } - calendars.add(calendarWrapper); - } - Collections.sort(calendars, KalendarComparator.getInstance()); - } - return calendars; - } - - /** - * Reload imported calendars from URL and persist calendars. - * @param ureq - */ - public static void reloadUrlImportedCalendars(UserRequest ureq) { - // read all the entries from the database - List<Property> properties = PropertyManager.getInstance().listProperties(ureq.getIdentity(), null, null, PROP_CATEGORY, null); - // return the list of calendar objects - Iterator<Property> propertyIter = properties.iterator(); - while (propertyIter.hasNext()) { - Property calendarProperty = propertyIter.next(); - String calendarName = calendarProperty.getName(); - String calendarUrl = calendarProperty.getStringValue(); - long timestampLastupdate = calendarProperty.getLongValue(); - long timestamp = System.currentTimeMillis(); - // reload only if string-property with importUrl exist and last update is older than 1 hour - if ( (calendarProperty.getStringValue() != null) && (timestamp-timestampLastupdate > 3600000) ) { - reloadCalendarFromUrl(calendarUrl, getImportedCalendarType(), getImportedCalendarID(ureq, calendarName)); - log.info("Calendar reload started from url=" + calendarUrl); - calendarProperty.setLongValue(timestamp); - PropertyManager.getInstance().updateProperty(calendarProperty); - log.info("Calendar reloaded from url=" + calendarUrl); - } - } - } - - /** - * Reload calendar from url and store calendar file locally. - * @param importUrl - * @param calType - * @param calId - */ - private static void reloadCalendarFromUrl(String importUrl, String calType, String calId) { - try { - String calendarContent = getContentFromUrl(importUrl); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar kalendar = calManager.buildKalendarFrom(calendarContent, calType, calId); - calManager.persistCalendar(kalendar); - } catch (Exception e) { - log.error("Could not reload calendar from url=" + importUrl, e); - } - } - - /** - * Get a temporary calendarID for upload - * @param ureq - * @return - */ - public static String getTempCalendarIDForUpload(UserRequest ureq) { - return ureq.getIdentity().getName() + "_import_tmp"; - } - - /** - * Get ID of a imported calendar - * @param ureq - * @param calendarName - * @return - */ - public static String getImportedCalendarID(UserRequest ureq, String calendarName) { - return getImportedCalendarID(ureq.getIdentity(), calendarName); - } - - /** - * Get ID of a imported calendar - * @param identity - * @param calendarName - * @return - */ - public static String getImportedCalendarID(Identity identity, String calendarName) { - return identity.getName() + "_" + sanitize(calendarName); - } - - private static String getImportedCalendarNameFromID(Identity identity, String calendarID) { - int idLength = calendarID.length(); - return calendarID.substring(identity.getName().length() + 1, idLength ); - } - - private static String getImportedCalendarType() { - return CalendarManager.TYPE_USER; - } - - - private static String sanitize(String name) { - // delete the preceding and trailing whitespaces - name = name.trim(); - - // replace every other character other than alphabets and numbers by underscore - Pattern specialChars = Pattern.compile("([^a-zA-z0-9])"); - return specialChars.matcher(name).replaceAll("_").toLowerCase(); - } - - public static String getContentFromUrl(String url) throws IOException { - InputStream in = new URL(url).openStream(); - return getContentFromStream(in); - } - - public static String getContentFromStream(InputStream in) throws IOException { - BufferedReader dis = new BufferedReader(new InputStreamReader(in)); - StringBuffer fBuf = new StringBuffer() ; - String line; - while ( (line = dis.readLine()) != null) { - fBuf.append (line + "\n"); - } - in.close (); - return fBuf.toString(); - } -} diff --git a/src/main/java/org/olat/commons/calendar/PersonalCalendarManager.java b/src/main/java/org/olat/commons/calendar/PersonalCalendarManager.java new file mode 100644 index 00000000000..75b018fbb68 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/PersonalCalendarManager.java @@ -0,0 +1,42 @@ +/** + * <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.commons.calendar; + +import java.util.List; + +import org.olat.commons.calendar.model.CalendarFileInfos; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.Identity; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface PersonalCalendarManager { + + public List<CalendarFileInfos> getListOfCalendarsFiles(Identity identity); + + public List<KalendarRenderWrapper> getListOfCalendarWrappers(UserRequest ureq, WindowControl wControl); + +} diff --git a/src/main/java/org/olat/commons/calendar/_content/calConfig.html b/src/main/java/org/olat/commons/calendar/_content/calConfig.html deleted file mode 100644 index 806042c3359..00000000000 --- a/src/main/java/org/olat/commons/calendar/_content/calConfig.html +++ /dev/null @@ -1,56 +0,0 @@ -<fieldset class="o_block_large"> - <legend> - $r.contextHelpWithWrapper("org.olat.commons.calendar","personal-calendar-callist.html","help.hover.callist") - $r.translate("cal.list")</legend> - -#foreach ($calendarWrapper in $calendars) - <div class="o_block_bottom clearfix"> - - #if (!$insideManager) - #if ($calendarWrapper.getKalendarConfig().isVis()) - <a class="o_cal_config_enabled" - href="$r.commandURIbg("tglvis", "id", "$calendarWrapper.getKalendar().getCalendarID()")" - $r.bgTarget() - title="$r.translateInAttribute("cal.disable")"><i class="o_icon o_icon-lg o_icon_calendar_enabled"> </i></a> - #else - <a class="o_cal_config_disabled" - href="$r.commandURIbg("tglvis", "id", "$calendarWrapper.getKalendar().getCalendarID()")" - $r.bgTarget() - title="$r.translateInAttribute("cal.enable")"><i class="o_icon o_icon-lg o_icon_calendar_disabled"> </i></a> - #end - #end - - <div class="o_cal_config_calendar $calendarWrapper.getKalendarConfig().getCss()"> - $r.escapeHtml($calendarWrapper.getKalendarConfig().getDisplayName()) - </div> - - <a id="colorchooser_$calendarWrapper.getKalendar().getCalendarID()" class="o_cal_config_chosecolor" - href="$r.commandURIbg("cc", "id", "$calendarWrapper.getKalendar().getCalendarID()")" $r.bgTarget() - title="$r.translateInAttribute("cal.color.choose")"><i class="o_icon o_icon-lg o_icon_color_picker"> </i></a> - - #if ($calendarWrapper.getAccess() == 0) - <a class="o_cal_config_addevent" - href="$r.commandURIbg("add", "id", "$calendarWrapper.getKalendar().getCalendarID()")" $r.bgTarget() - title="$r.translateInAttribute("cal.add.event")"><i class="o_icon o_icon-lg o_icon_add"> </i></a> - <a class="o_cal_config_importevent" - href="$r.commandURIbg("import", "id", "$calendarWrapper.getKalendar().getCalendarID()")" $r.bgTarget() - title="$r.translateInAttribute("cal.import.event")"><i class="o_icon o_icon-lg o_icon_import"> </i></a> - #else - <span class="o_cal_config_readonly" title="$r.translateInAttribute("cal.add.readonly")"><i class="o_icon o_icon-lg o_icon_readonly"> </i></span> - #end - - <a class="o_cal_config_subscribeical" - href="$r.commandURIbg("if", "id", "$calendarWrapper.getKalendar().getCalendarID()")" $r.bgTarget() - title="$r.translateInAttribute("cal.icalfeed.subscribe")"><i class="o_icon o_icon-lg o_icon_rss"> </i></a> - - #if ($icalTokens.hasIcalFeed($calendarWrapper)) - <a class="o_cal_config_remove_subscribeical" - href="$r.commandURIbg("rmif", "id", "$calendarWrapper.getKalendar().getCalendarID()")" $r.bgTarget() - title="$r.translateInAttribute("cal.icalfeed.subscribe.remove")"><i class="o_icon o_icon-lg o_icon_remove"> </i></a> - <a class="o_cal_config_regenerate_link" - href="$r.commandURIbg("rf", "id", "$calendarWrapper.getKalendar().getCalendarID()")" $r.bgTarget() - title="$r.translateInAttribute("cal.icalfeed.regenerate.title")"><i class="o_icon o_icon-lg o_icon_refresh"> </i></a> - #end - </div> -#end -</fieldset> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_content/calIcalFeed.html b/src/main/java/org/olat/commons/calendar/_content/calIcalFeed.html deleted file mode 100644 index c118e9e2051..00000000000 --- a/src/main/java/org/olat/commons/calendar/_content/calIcalFeed.html +++ /dev/null @@ -1,8 +0,0 @@ -<h4>$r.translate("cal.icalfeed.title")</h4> - -$r.translate("cal.icalfeed.info") -<br/><br/> -<a href="$icalFeedLink">$icalFeedLink</a> - - -<p style="clear: both"> diff --git a/src/main/java/org/olat/commons/calendar/_content/calImportByUrl.html b/src/main/java/org/olat/commons/calendar/_content/calImportByUrl.html deleted file mode 100644 index 2f4c5da759f..00000000000 --- a/src/main/java/org/olat/commons/calendar/_content/calImportByUrl.html +++ /dev/null @@ -1,2 +0,0 @@ - $r.render("urlinput") - diff --git a/src/main/java/org/olat/commons/calendar/_content/calSearchMain.html b/src/main/java/org/olat/commons/calendar/_content/calSearchMain.html deleted file mode 100644 index 883d5df98bf..00000000000 --- a/src/main/java/org/olat/commons/calendar/_content/calSearchMain.html +++ /dev/null @@ -1,6 +0,0 @@ -#if ($displayBackLink) - $r.render("backLink") -#end -<br /> -<br /> -$r.render("panel") \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_content/importEvents.html b/src/main/java/org/olat/commons/calendar/_content/importEvents.html deleted file mode 100644 index 56d5e6cf150..00000000000 --- a/src/main/java/org/olat/commons/calendar/_content/importEvents.html +++ /dev/null @@ -1,4 +0,0 @@ -$r.render("chooseContainer") -$r.render("urlContainer") -$r.render("fileContainer") -$r.render("buttonGroupLayout") \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_content/importedCalConfig.html b/src/main/java/org/olat/commons/calendar/_content/importedCalConfig.html deleted file mode 100644 index 5c39b17809c..00000000000 --- a/src/main/java/org/olat/commons/calendar/_content/importedCalConfig.html +++ /dev/null @@ -1,66 +0,0 @@ -#if ($insideManager) - <div id="o_cal_manage_config"> -#else - <div id="o_cal_config"> -#end - <fieldset> - <legend>$r.translate("cal.import.list")</legend> - -#if (!$insideManager) - <div class="o_button_group o_button_group_right"> - $r.render("cal.managecalendars") - </div> -#end - -#if ($calendars && $calendars.size() > 5) - <div class="o_cal_config_scrollwrapper"> -#end - -#foreach ($calendarWrapper in $calendars) - <div class="o_block clearfix"> - #if (!$insideManager) - #if ($calendarWrapper.getKalendarConfig().isVis()) - <a class="o_cal_config_enabled" - href="$r.commandURIbg("tglvis", "id", "$calendarWrapper.getKalendar().getCalendarID()")" - $r.bgTarget() - title="$r.translateInAttribute("cal.disable")"><i class="o_icon o_icon-lg o_icon_calendar_enabled"> </i></a> - #else - <a class="o_cal_config_disabled" - href="$r.commandURIbg("tglvis", "id", "$calendarWrapper.getKalendar().getCalendarID()")" - $r.bgTarget() - title="$r.translateInAttribute("cal.enable")"><i class="o_icon o_icon-lg o_icon_calendar_disabled"> </i></a> - #end - #end - <div class="o_cal_config_calendar $calendarWrapper.getKalendarConfig().getCss()"> - $r.escapeHtml($calendarWrapper.getKalendarConfig().getDisplayName()) - </div> - - #if (!$insideManager) - <a class="o_cal_config_chosecolor" - href="$r.commandURIbg("cc", "id", "$calendarWrapper.getKalendar().getCalendarID()")" - $r.bgTarget() - title="$r.translateInAttribute("cal.color.choose")"><i class="o_icon o_icon-lg o_icon_color_picker"> </i></a> - - #if ($calendarWrapper.getAccess() == 0) - <a class="o_cal_config_addevent" - href="$r.commandURIbg("add", "id", "$calendarWrapper.getKalendar().getCalendarID()")" - $r.bgTarget() - title="$r.translateInAttribute("cal.add.event")"><i class="o_icon o_icon-lg o_icon_add"> </i></a> - #else - <span class="o_cal_config_readonly" - title="$r.translateInAttribute("cal.add.readonly")"><i class="o_icon o_icon-lg o_icon_readonly"> </i></span> - #end - #else - <a class="o_cal_config_remove_cal" - href="$r.commandURIbg("rm", "id", "$calendarWrapper.getKalendar().getCalendarID()")" - $r.bgTarget() - title="$r.translateInAttribute("cal.import.remove")"><i class="o_icon o_icon-lg o_icon_remove"> </i></a> - </div> - #end -#end - -#if ($calendars && $calendars.size() > 5) - </div> -#end -</fieldset> -</div> diff --git a/src/main/java/org/olat/commons/calendar/_content/manageCalendars.html b/src/main/java/org/olat/commons/calendar/_content/manageCalendars.html deleted file mode 100644 index f077526f5bd..00000000000 --- a/src/main/java/org/olat/commons/calendar/_content/manageCalendars.html +++ /dev/null @@ -1,29 +0,0 @@ -#if ($importedCalendarWrappers && $importedCalendarWrappers.size() > 0) - <div class="clearfix"> - $r.render("importedCalendarConfig") - </div> - <div class="clearfix"> - #if ($choose == 0) - $r.render("fileupload") - #else - Import Calendar - <div class="o_button_group"> - $r.render("cal.import.type.file") - $r.render("cal.import.type.url") - </div> - #end - </div> - </div> -#else - #if ($choose == 0) - $r.render("fileupload") - #else - <div> - $r.translate("cal.import.title") - <div class="o_button_group"> - $r.render("cal.import.type.file") - $r.render("cal.import.type.url") - </div> - </div> - #end -#end \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ar.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ar.properties index f620bf25ca1..c1e89518e42 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ar.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ar.properties @@ -88,7 +88,7 @@ cal.import.remove.title=\u062A\u0623\u0643\u064A\u062F \u0627\u0644\u062D\u0630\ cal.import.success=\u062A\u0645 \u0627\u0644\u0627\u0633\u062A\u064A\u0631\u0627\u062F \u0628\u0646\u062C\u0627\u062D cal.import.title=\u0627\u0633\u062A\u064A\u0631\u0627\u062F \u062A\u0642\u0648\u064A\u0645 cal.import.type.file=\u0627\u0633\u062A\u064A\u0631\u0627\u062F \u0645\u0644\u0641 -cal.import.type.url=\u0627\u0633\u062A\u064A\u0631\u0627\u062F \u0645\u0646 \u0627\u0644\u0648\u064A\u0628 +cal.synchronize.type.url=\u0627\u0633\u062A\u064A\u0631\u0627\u062F \u0645\u0646 \u0627\u0644\u0648\u064A\u0628 cal.import.url.content.invalid=\u0635\u064A\u063A\u0629 \u0645\u0644\u0641 \u0627\u0644\u062A\u0642\u0648\u064A\u0645 \u063A\u064A\u0631 \u0635\u062D\u064A\u062D\u0629\u060C \u064A\u0631\u062C\u0649 \u0641\u062D\u0635 \u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0627\u0644\u0643\u062A\u0631\u0648\u0646\u0649 cal.import.url.empty.error=\u0644\u0627 \u064A\u0645\u0643\u0646 \u062A\u0631\u0643 \u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0627\u0644\u0643\u062A\u0631\u0648\u0646\u0649 \u0641\u0627\u0631\u063A\u0627\u064B cal.import.url.file.write.error=\u0644\u0627 \u064A\u0645\u0643\u0646 \u0643\u062A\u0627\u0628\u0629 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0641\u0649 \u0645\u0644\u0641 \u0627\u0644\u062A\u0642\u0648\u064A\u0645\u060C \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0641\u0649 \u0648\u0642\u062A \u0644\u0627\u062D\u0642. diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_bg.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_bg.properties index 3289d00b013..9d7a99016f7 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_bg.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_bg.properties @@ -85,7 +85,7 @@ cal.import.remove.title=\u041F\u043E\u0442\u0432\u044A\u0440\u0434\u0435\u0442\u cal.import.success=\u0412\u043D\u0430\u0441\u044F\u043D\u0435\u0442\u043E \u0435 \u0443\u0441\u043F\u0435\u0448\u043D\u043E cal.import.title=\u0412\u043D\u0435\u0441\u0435\u0442\u0435 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440 cal.import.type.file=\u0412\u043D\u0435\u0441\u0435\u0442\u0435 \u043E\u0442 \u0444\u0430\u0439\u043B -cal.import.type.url=\u0412\u043D\u0435\u0441\u0435\u0442\u0435 \u043E\u0442 \u0443\u0435\u0431 \u0430\u0434\u0440\u0435\u0441 +cal.synchronize.type.url=\u0412\u043D\u0435\u0441\u0435\u0442\u0435 \u043E\u0442 \u0443\u0435\u0431 \u0430\u0434\u0440\u0435\u0441 cal.import.url.content.invalid=\u041A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u043D\u0438\u044F\u0442 \u0444\u0430\u0439\u043B \u0435 \u0433\u0440\u0435\u0448\u0435\u043D. \u041C\u043E\u043B\u044F, \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u0442\u0435 \u0443\u0435\u0431 \u0430\u0434\u0440\u0435\u0441\u0430 cal.import.url.empty.error=\u0423\u0435\u0431 \u0430\u0434\u0440\u0435\u0441\u044A\u0442 \u043D\u0430 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u0430 \u043D\u0435 \u043C\u043E\u0436\u0435 \u0434\u0430 \u0431\u044A\u0434\u0435 \u043F\u0440\u0430\u0437\u0435\u043D cal.import.url.file.write.error=\u041D\u0435\u043E\u0447\u0430\u043A\u0432\u0430\u043D \u043E\u0442\u0433\u043E\u0432\u043E\u0440. \u041C\u043E\u043B\u044F, \u043E\u043F\u0438\u0442\u0430\u0439\u0442\u0435 \u043E\u0442\u043D\u043E\u0432\u043E \u043F\u043E-\u043A\u044A\u0441\u043D\u043E diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties index 047bccfc8f8..6f966653324 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties @@ -31,6 +31,7 @@ cal.form.class.freebusy=Nur Zeit sichtbar cal.form.class.private=Privat cal.form.class.public=Öffentlich cal.form.date=Datum +cal.form.description=Beschreibung cal.form.end=Ende cal.form.error.date=Datum/Zeit ist ung\u00FCltig. cal.form.error.endbeforebegin=Enddatum/Zeit liegt vor Beginndatum/Zeit. @@ -136,6 +137,21 @@ chelp.personal-calendar.title=Pers chelp.personal-calendar-callist.title=Kalenderliste chelp.admin-calendar.title=Kalender: Konfiguration +cal.configuration.tooltip=Einstellungen +table.header.aggregated.feed=Aggregiert +table.header.color=Farbe +cal.delete.imported.calendar=Importierte Kalendar löschen +table.header.name=Name +table.header.type=Typ +table.header.url=<i class="o_icon o_icon-lg o_icon_rss"> </i> +table.header.visible=Sichtbar +config.visible.on=<i class="o_icon o_icon_calendar_enabled"> </i> +config.visible.off=<i class="o_icon o_icon_calendar_disabled"> </i> +table.header.tools=<i class="o_icon o_icon-lg o_icon_actions"> </i> +cal.icalfeed.aggregated.title=Aggregiertes iCal Feed-Link +cal.icalfeed.aggregated.tooltip=$\:cal.icalfeed.aggregated.title +cal.icalfeed.aggregated.info = Bitte benutzen Sie die folgende URL, um diesen Kalender von anderen Anwendungen aus aufzurufen. Sie können die URL kopieren und in jede andere Kalenderanwendung einfügen, welche das iCal-Format unterstützt. + day.short.di=$org.olat.core.gui.components.form.flexible.impl.elements\:day.short.di day.short.do=$org.olat.core.gui.components.form.flexible.impl.elements\:day.short.do day.short.fr=$org.olat.core.gui.components.form.flexible.impl.elements\:day.short.fr @@ -236,9 +252,6 @@ cal.import.remove.confirmation_message = Wollen Sie den importierten Kalender en cal.import.remove.info = Kalender wurde entfernt cal.import.type.file = Datei importieren cal.import.type.file.desc = Kalender von einer Datei importieren -cal.import.type.url = Importieren via URL -cal.import.type.url.desc = Kalender via URL importieren -cal.import.type.url.error = Kalender via URL importieren cal.import.url.prompt = Öffentliche Kalender-Adresse (URL im iCal-Format) cal.import.url.empty.error = Die URL des Kalenders darf nicht leer sein. cal.import.url.invalid = Der Download ist fehlgeschlagen. Bitte überprüfen Sie die URL. @@ -251,5 +264,10 @@ cal.notifications.header.group=Kalender in Gruppe "{0}" cal.notifications.entry=Termin "{0}" vom {1} {2} geändert von {3} cal.notifications.location=in {0} +cal.synchronize.type.url = Kalendar via URL synchronizieren +cal.synchronize.type.url.desc = Kalender via URL synchronizieren +cal.synchronize.personal.type.url.desc = $\:cal.synchronize.type.url.desc +cal.synchronize.type.url.error = Kalender konnte nicht synchroniziert werden + ul.select=Datei auswählen cal.week.label=Woche diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_el.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_el.properties index 8fa618872ef..5a613643bf2 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_el.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_el.properties @@ -88,7 +88,7 @@ cal.import.remove.title=\u0395\u03C0\u03B9\u03B2\u03B5\u03B2\u03B1\u03AF\u03C9\u cal.import.success=\u0395\u03C0\u03B9\u03C4\u03C5\u03C7\u03AE\u03C2 \u03B5\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE cal.import.title=\u0395\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE \u0397\u03BC\u03B5\u03C1\u03BF\u03BB\u03BF\u03B3\u03AF\u03BF\u03C5 cal.import.type.file=\u0395\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE \u03B1\u03C0\u03CC \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF -cal.import.type.url=\u0395\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE \u03B1\u03C0\u03CC URL +cal.synchronize.type.url=\u0395\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE \u03B1\u03C0\u03CC URL cal.import.url.content.invalid=\u039B\u03AC\u03B8\u03BF\u03C2 \u03C4\u03CD\u03C0\u03BF\u03C2 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 \u03B7\u03BC\u03B5\u03C1\u03BF\u03BB\u03BF\u03B3\u03AF\u03BF\u03C5. \u03A0\u03B1\u03C1\u03B1\u03BA\u03B1\u03BB\u03CE \u03B5\u03BB\u03AD\u03B3\u03BE\u03C4\u03B5 \u03C4\u03BF URL cal.import.url.empty.error=\u03A4\u03BF URL \u03C4\u03BF\u03C5 \u03B7\u03BC\u03B5\u03C1\u03BF\u03BB\u03BF\u03B3\u03AF\u03BF\u03C5 \u03B4\u03B5 \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 \u03BA\u03B5\u03BD\u03CC. cal.import.url.file.write.error=\u0394\u03B5\u03BD \u03AD\u03B3\u03B9\u03BD\u03B5 \u03B5\u03B3\u03B3\u03C1\u03B1\u03C6\u03AE \u03C4\u03C9\u03BD \u03B4\u03B5\u03B4\u03BF\u03BC\u03AD\u03BD\u03C9\u03BD \u03C3\u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03B7\u03BC\u03B5\u03C1\u03BF\u03BB\u03BF\u03B3\u03AF\u03BF\u03C5.\u03A0\u03B1\u03C1\u03B1\u03BA\u03B1\u03BB\u03CE \u03C0\u03C1\u03BF\u03C3\u03C0\u03B1\u03B8\u03AE\u03C3\u03C4\u03B5 \u03B1\u03C1\u03B3\u03CC\u03C4\u03B5\u03C1\u03B1 diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties index feb34ed082d..b8baf2b332d 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties @@ -36,6 +36,7 @@ cal.form.class.public=Public cal.form.created.by=by cal.form.created.label=Created cal.form.date=Date +cal.form.description=Description cal.form.end=End cal.form.error.date=This date/time format is invalid. cal.form.error.endbeforebegin=Date/time ends before it begins. @@ -92,9 +93,9 @@ cal.import.success=Import sucessful cal.import.title=Import calendar cal.import.type.file=Import file cal.import.type.file.desc=Import a calendar form a file -cal.import.type.url=Import from URL -cal.import.type.url.desc=Import a calendar from an URL -cal.import.type.url.error=Import calendar from an URL +cal.synchronize.type.url=Import from URL +cal.synchronize.type.url.desc=Import a calendar from an URL +cal.synchronize.personal.type.url.desc = $\:cal.synchronize.type.url.desc cal.import.url.content.invalid=Calendar file format incorrect. Please check the URL cal.import.url.empty.error=URL of calendar cannot be left blank. cal.import.url.file.write.error=Data could not be written to that calendar file. Please try again later. diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_fr.properties index 2d4ccd030e1..5723630606f 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_fr.properties @@ -92,9 +92,8 @@ cal.import.success=Import\u00E9 avec succ\u00E8s cal.import.title=Importer calendrier cal.import.type.file=Importer fichier cal.import.type.file.desc=Importer le calendrier depuis un fichier -cal.import.type.url=Importer par URL -cal.import.type.url.desc=Import le calendrier par une URL -cal.import.type.url.error=Importer le calendrier par une URL +cal.synchronize.type.url=Synchroniser un calendarier par URL +cal.synchronize.type.url.desc=Synchroniser un calendrier par une URL cal.import.url.content.invalid=Le format du fichier calendrier n'est pas correcte. Veuillez v\u00E9rifier l'URL. cal.import.url.empty.error=LURL du calendrier ne peut pas \u00EAtre vide. cal.import.url.file.write.error=Le fichier calendrier n'a pas pu \u00EAtre \u00E9crite. Veuillez r\u00E9essayer ult\u00E9rieurement, s.v.p. . diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_it.properties index 9477043e698..c39feda732e 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_it.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_it.properties @@ -88,7 +88,7 @@ cal.import.remove.title=Conferma rimozione cal.import.success=Importazione riuscita cal.import.title=Importare calendari cal.import.type.file=Importare file -cal.import.type.url=Importazione via URL +cal.synchronize.type.url=Importazione via URL cal.import.url.content.invalid=Il formato del file calendario non \u00E8 corretto. Verifichi l'URL, p.f. cal.import.url.empty.error=L'URL del calendario deve essere indicato. cal.import.url.file.write.error=Non \u00E8 possibile scrivere il file calendario. Riprovi pi\u00F9 tardi, p.f. diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_jp.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_jp.properties index 8cdb0f6533a..e6d491fa5a5 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_jp.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_jp.properties @@ -84,7 +84,7 @@ cal.import.remove.title=\u524A\u9664\u78BA\u8A8D cal.import.success=\u6B63\u5E38\u306B\u30A4\u30F3\u30DD\u30FC\u30C8\u3055\u308C\u307E\u3057\u305F\u3002 cal.import.title=\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u30A4\u30F3\u30DD\u30FC\u30C8\u3059\u308B cal.import.type.file=\u30D5\u30A1\u30A4\u30EB\u3092\u30A4\u30F3\u30DD\u30FC\u30C8\u3059\u308B -cal.import.type.url=URI\u304B\u3089\u30A4\u30F3\u30DD\u30FC\u30C8\u3059\u308B +cal.synchronize.type.url=URI\u304B\u3089\u30A4\u30F3\u30DD\u30FC\u30C8\u3059\u308B cal.import.url.content.invalid=\u30AB\u30EC\u30F3\u30C0\u30FC\u30D5\u30A1\u30A4\u30EB\u306E\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002URI\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002 cal.import.url.empty.error=\u30AB\u30EC\u30F3\u30C0\u30FC\u306EURI\u306B\u306F\u3001\u7A7A\u767D\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002 cal.import.url.file.write.error=\u30AB\u30EC\u30F3\u30C0\u30FC\u30D5\u30A1\u30A4\u30EB\u306E\u30C7\u30FC\u30BF\u3092\u66F8\u304D\u8FBC\u3080\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u5F8C\u3067\u3001\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002 diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_nl_NL.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_nl_NL.properties index 3a6e19c0814..123cb9fd21b 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_nl_NL.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_nl_NL.properties @@ -88,7 +88,7 @@ cal.import.remove.title=Verplaatsing bevestigen cal.import.success=Import succesvol cal.import.title=Kalender importeren cal.import.type.file=Bestand importeren -cal.import.type.url=Import van URL +cal.synchronize.type.url=Import van URL cal.import.url.content.invalid=Kalender bestandsgrootte incorrect. Gelieve de URL na te kijken cal.import.url.empty.error=URL van kalender kan niet leeg gelaten worden. cal.import.url.file.write.error=Data kon niet geschreven worden naar dat kalenderbestand. Gelieve het later opnieuw te proberen. diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pl.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pl.properties index c29d38c3418..726deb0f106 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pl.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pl.properties @@ -92,7 +92,7 @@ cal.import.success=Import zako\u0144czono pomy\u015Blnie cal.import.title=Importuj kalendarz cal.import.type.file=Importuj plik cal.import.type.file.desc=Importuj kalendarz z pliku -cal.import.type.url=Importuj z adresu URL +cal.synchronize.type.url=Importuj z adresu URL cal.import.url.content.invalid=B\u0142\u0119dny format kalendarza. Sprawd\u017A adres URL cal.import.url.empty.error=Adres URL kalendarza nie mo\u017Ce by\u0107 pusty cal.import.url.file.write.error=Nie mo\u017Cna zapisa\u0107 danych do tego pliku kalendarza. Spr\u00F3buj ponownie p\u00F3\u017Aniej diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pt_BR.properties index d8037eb5982..3d29a7d2a95 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pt_BR.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_pt_BR.properties @@ -92,9 +92,9 @@ cal.import.success=Importa\u00E7\u00E3o com sucesso cal.import.title=Impotar calend\u00E1rio cal.import.type.file=Importar arquivo cal.import.type.file.desc=Importar um arquivo de calend\u00E1rio -cal.import.type.url=Importar de URL -cal.import.type.url.desc=Importar um calend\u00E1rio a partir de uma URL -cal.import.type.url.error=Calend\u00E1rio de importa\u00E7\u00E3o a partir de uma URL +cal.synchronize.type.url=Importar de URL +cal.synchronize.type.url.desc=Importar um calend\u00E1rio a partir de uma URL +cal.synchronize.personal.type.url.desc = $\:cal.synchronize.type.url.desc cal.import.url.content.invalid=Formato do arquivo de calend\u00E1rio incorreto. Favor verificar a URL. cal.import.url.empty.error=URL do calend\u00E1rio n\u00E3o pode ficar vazio. cal.import.url.file.write.error=Data n\u00E3o pode ser escrita no arquivo de calend\u00E1rio. Favor tente novamente mais tarde. diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ru.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ru.properties index 3b50c258e88..c3c077961d3 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ru.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_ru.properties @@ -85,7 +85,7 @@ cal.import.remove.title=\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u cal.import.success=\u0418\u043C\u043F\u043E\u0440\u0442 \u0443\u0441\u043F\u0435\u0448\u0435\u043D cal.import.title=\u0418\u043C\u043F\u043E\u0440\u0442 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F cal.import.type.file=\u0418\u043C\u043F\u043E\u0440\u0442 \u0444\u0430\u0439\u043B\u0430 -cal.import.type.url=\u0418\u043C\u043F\u043E\u0440\u0442 \u0438\u0437 URL +cal.synchronize.type.url=\u0418\u043C\u043F\u043E\u0440\u0442 \u0438\u0437 URL cal.import.url.content.invalid=\u0424\u0430\u0439\u043B \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F \u0438\u043C\u0435\u0435\u0442 \u043D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442. \u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 URL cal.import.url.empty.error=URL \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C cal.import.url.file.write.error=\u0414\u0430\u043D\u043D\u044B\u0435 \u043D\u0435 \u043C\u043E\u0433\u0443\u0442 \u0431\u044B\u0442\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u044B \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u044F. \u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0443 \u043F\u043E\u0437\u0436\u0435 diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_CN.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_CN.properties index bdb5e54aaef..0a289d66a43 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_CN.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_CN.properties @@ -85,7 +85,7 @@ cal.import.remove.title=\u786E\u8BA4\u79FB\u9664 cal.import.success=\u5BFC\u5165\u6210\u529F cal.import.title=\u5BFC\u5165\u65E5\u5386 cal.import.type.file=\u5BFC\u5165\u6587\u4EF6 -cal.import.type.url=\u4ECEURL\u5730\u5740\u5BFC\u5165 +cal.synchronize.type.url=\u4ECEURL\u5730\u5740\u5BFC\u5165 cal.import.url.content.invalid=\u65E5\u5386\u6587\u4EF6\u683C\u5F0F\u4E0D\u6B63\u786E\u3002\u8BF7\u68C0\u67E5URL\u5730\u5740\u3002 cal.import.url.empty.error=\u65E5\u5386\u7684URL\u5730\u5740\u4E0D\u80FD\u4E3A\u7A7A cal.import.url.file.write.error=\u6570\u636E\u4E0D\u80FD\u88AB\u5199\u5165\u65E5\u5386\u6587\u4EF6\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002 diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_TW.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_TW.properties index 893b4c96025..35a8e6f58a9 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_TW.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_zh_TW.properties @@ -85,7 +85,7 @@ cal.import.remove.title=\u78BA\u8A8D\u79FB\u9664 cal.import.success=\u532F\u5165\u6210\u529F cal.import.title=\u532F\u5165\u884C\u4E8B\u66C6 cal.import.type.file=\u532F\u5165\u6A94\u6848 -cal.import.type.url=\u5F9E\u7DB2\u5740\u532F\u5165 +cal.synchronize.type.url=\u5F9E\u7DB2\u5740\u532F\u5165 cal.import.url.content.invalid=\u884C\u4E8B\u66C6\u6A94\u6848\u683C\u5F0F\u932F\u8AA4\u3002\u8ACB\u6AA2\u67E5\u4E00\u4E0B\u7DB2\u5740\u3002 cal.import.url.empty.error=\u884C\u4E8B\u66C6\u7DB2\u5740\u4E0D\u80FD\u7A7A\u767D cal.import.url.file.write.error=\u8CC7\u6599\u7121\u6CD5\u5BEB\u5165\u5230\u8A72\u884C\u4E8B\u66C6\u6A94\u6848\u3002\u8ACB\u7A0D\u5F8C\u518D\u8A66\u3002 diff --git a/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml b/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml index a948660b265..5a5369adfe5 100644 --- a/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml +++ b/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml @@ -34,9 +34,6 @@ <property name="jobClass" value="org.olat.commons.calendar.ImportCalendarJob" /> </bean> - <bean id="calendarNotificationManager" class="org.olat.commons.calendar.notification.CalendarNotificationManagerImpl"/> - <bean id="calendarUserDataDeleteManager" class="org.olat.commons.calendar.ICalFileCalendarUserDeleteManager"/> - <bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> <property name="order" value="7207" /> <property name="navigationKey" value="calendaradmin" /> diff --git a/src/main/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAO.java b/src/main/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAO.java new file mode 100644 index 00000000000..2cc93f003bb --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAO.java @@ -0,0 +1,144 @@ +/** + * <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.commons.calendar.manager; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.persistence.TypedQuery; + +import org.olat.basesecurity.IdentityRef; +import org.olat.commons.calendar.model.CalendarUserConfiguration; +import org.olat.commons.calendar.model.Kalendar; +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 25.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class CalendarUserConfigurationDAO { + + @Autowired + private DB dbInstance; + + /** + * Create and persist an user configuration with default settings: + * + * @param calendar + * @param identity + * @return + */ + public CalendarUserConfiguration createCalendarUserConfiguration(Kalendar calendar, Identity identity) { + return createCalendarUserConfiguration(calendar, identity, null, true, true); + } + + /** + * Create and persist an user configuration + * @param calendar + * @param identity + * @param token + * @param aggregated + * @param visible + * @return + */ + public CalendarUserConfiguration createCalendarUserConfiguration(Kalendar calendar, Identity identity, String token, boolean aggregated, boolean visible) { + CalendarUserConfiguration config = new CalendarUserConfiguration(); + config.setCreationDate(new Date()); + config.setLastModified(config.getCreationDate()); + config.setCalendarId(calendar.getCalendarID()); + config.setType(calendar.getType()); + config.setToken(token); + config.setIdentity(identity); + config.setInAggregatedFeed(aggregated); + config.setVisible(visible); + dbInstance.getCurrentEntityManager().persist(config); + return config; + } + + public List<CalendarUserConfiguration> getCalendarUserConfigurations(IdentityRef identity, String... types) { + StringBuilder sb = new StringBuilder(512); + sb.append("select conf from caluserconfig conf where conf.identity.key=:identityKey"); + if(types != null && types.length > 0 && types[0] != null) { + sb.append(" and conf.type in (:types)"); + } + + TypedQuery<CalendarUserConfiguration> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), CalendarUserConfiguration.class) + .setParameter("identityKey", identity.getKey()); + if(types != null && types.length > 0 && types[0] != null) { + List<String> typeList = new ArrayList<>(types.length); + for(String type:types) { + typeList.add(type); + } + query.setParameter("types", typeList); + } + return query.getResultList(); + } + + public CalendarUserConfiguration getCalendarUserConfiguration(IdentityRef identity, String calendarId, String type) { + List<CalendarUserConfiguration> configs = dbInstance.getCurrentEntityManager() + .createNamedQuery("loadCalUserConfigByIdentityAndCalendar", CalendarUserConfiguration.class) + .setParameter("identityKey", identity.getKey()) + .setParameter("calendarId", calendarId) + .setParameter("type", type) + .getResultList(); + return configs == null || configs.isEmpty() ? null : configs.get(0); + } + + public CalendarUserConfiguration update(CalendarUserConfiguration config) { + config.setLastModified(new Date()); + return dbInstance.getCurrentEntityManager().merge(config); + } + + public String getCalendarToken(String calendarType, String calendarID, String username) { + StringBuilder sb = new StringBuilder(512); + sb.append("select conf.token from caluserconfig conf") + .append(" inner join conf.identity ident") + .append(" where ident.name=:username and conf.calendarId=:calendarId and conf.type=:type"); + + List<String> tokens = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), String.class) + .setParameter("username", username) + .setParameter("calendarId", calendarID) + .setParameter("type", calendarType) + .getResultList(); + return tokens == null || tokens.isEmpty() ? null : tokens.get(0); + } + + public CalendarUserConfiguration getCalendarUserConfiguration(Long key) { + StringBuilder sb = new StringBuilder(512); + sb.append("select conf from caluserconfig conf") + .append(" inner join fetch conf.identity ident") + .append(" where conf.key=:key"); + + List<CalendarUserConfiguration> configs = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), CalendarUserConfiguration.class) + .setParameter("key", key) + .getResultList(); + return configs == null || configs.isEmpty() ? null : configs.get(0); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ICalFileCalendarManager.java b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java similarity index 75% rename from src/main/java/org/olat/commons/calendar/ICalFileCalendarManager.java rename to src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java index 89ee961fcfc..9078b3afeec 100644 --- a/src/main/java/org/olat/commons/calendar/ICalFileCalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarManager.java @@ -23,7 +23,7 @@ * under the Apache 2.0 license as the original file. */ -package org.olat.commons.calendar; +package org.olat.commons.calendar.manager; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -32,10 +32,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.StringReader; import java.net.URISyntaxException; import java.text.ParseException; import java.util.ArrayList; @@ -45,16 +44,22 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; - +import java.util.UUID; + +import org.apache.commons.lang.RandomStringUtils; +import org.olat.basesecurity.IdentityRef; +import org.olat.commons.calendar.CalendarManagedFlag; +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.CalendarModule; +import org.olat.commons.calendar.CalendarUtils; +import org.olat.commons.calendar.model.CalendarKey; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarConfig; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.model.KalendarEventLink; import org.olat.commons.calendar.model.KalendarRecurEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; -import org.olat.core.CoreSpringFactory; -import org.olat.core.gui.UserRequest; +import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLATRuntimeException; @@ -63,13 +68,17 @@ import org.olat.core.logging.Tracing; import org.olat.core.util.CodeHelper; import org.olat.core.util.FileUtils; import org.olat.core.util.StringHelper; +import org.olat.core.util.WebappHelper; import org.olat.core.util.cache.CacheWrapper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.SyncerCallback; -import org.olat.core.util.prefs.Preferences; import org.olat.core.util.resource.OresHelper; import org.olat.course.ICourse; import org.olat.group.BusinessGroup; +import org.olat.user.UserManager; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.CalendarOutputter; @@ -88,6 +97,7 @@ import net.fortuna.ical4j.model.property.CalScale; import net.fortuna.ical4j.model.property.Clazz; import net.fortuna.ical4j.model.property.Contact; import net.fortuna.ical4j.model.property.Created; +import net.fortuna.ical4j.model.property.Description; import net.fortuna.ical4j.model.property.Duration; import net.fortuna.ical4j.model.property.ExDate; import net.fortuna.ical4j.model.property.LastModified; @@ -100,7 +110,8 @@ import net.fortuna.ical4j.model.property.Url; import net.fortuna.ical4j.model.property.Version; import net.fortuna.ical4j.model.property.XProperty; -public class ICalFileCalendarManager implements CalendarManager { +@Service +public class ICalFileCalendarManager implements CalendarManager, InitializingBean { private static final OLog log = Tracing.createLoggerFor(ICalFileCalendarManager.class); @@ -118,19 +129,35 @@ public class ICalFileCalendarManager implements CalendarManager { private static final String ICAL_EXDATE = "EXDATE"; private TimeZone tz; + + @Autowired + private UserManager userManager; + @Autowired + private CalendarModule calendarModule; + @Autowired + private CalendarUserConfigurationDAO calendarUserConfigDao; - protected ICalFileCalendarManager(File fStorageBase) { - this.fStorageBase = fStorageBase; + @Override + public void afterPropertiesSet() { + fStorageBase = new File(WebappHelper.getUserDataRoot(), "calendars"); if (!fStorageBase.exists()) { if (!fStorageBase.mkdirs()) throw new OLATRuntimeException("Error creating calendar base directory at: " + fStorageBase.getAbsolutePath(), null); } - createCalendarFileDirectories(); + + //create the directories + String[] dirs = new String[]{ TYPE_USER, TYPE_GROUP, TYPE_COURSE }; + for(String dir:dirs) { + File fDirectory = new File(fStorageBase, dir); + if(!fDirectory.exists()) { + fDirectory.mkdirs(); + } + } // set parser to relax (needed for allday events // see http://sourceforge.net/forum/forum.php?thread_id=1253735&forum_id=368291 - //made in module System.setProperty("ical4j.unfolding.relaxed", "true"); - // initialize tiemzone - tz = CoreSpringFactory.getImpl(CalendarModule.class).getDefaultTimeZone(); + // made in module System.setProperty("ical4j.unfolding.relaxed", "true"); + // initialize timezone + tz = calendarModule.getDefaultTimeZone(); calendarCache = CoordinatorManager.getInstance().getCoordinator().getCacher().getCache(CalendarManager.class.getSimpleName(), "calendar"); } @@ -199,7 +226,7 @@ public class ICalFileCalendarManager implements CalendarManager { return kalendar; } - private Kalendar createKalendar(String type, String calendarID, Calendar calendar) { + protected Kalendar createKalendar(String type, String calendarID, Calendar calendar) { Kalendar cal = new Kalendar(calendarID, type); for (Iterator<?> iter = calendar.getComponents().iterator(); iter.hasNext();) { Object comp = iter.next(); @@ -227,42 +254,88 @@ public class ICalFileCalendarManager implements CalendarManager { } File calendarFile = getCalendarFile(type, calendarID); - - Calendar calendar; + return readCalendar(calendarFile); + } + + @Override + public Calendar readCalendar(File calendarFile) { try(InputStream fIn = new FileInputStream(calendarFile); InputStream in = new BufferedInputStream(fIn)) { CalendarBuilder builder = new CalendarBuilder(); - calendar = builder.build(in); + return builder.build(in); } catch (FileNotFoundException fne) { throw new OLATRuntimeException("Not found: " + calendarFile, fne); } catch (Exception e) { throw new OLATRuntimeException("Error parsing calendar file.", e); } - return calendar; } @Override - public Kalendar buildKalendarFrom(String calendarContent, String calType, String calId) { - Kalendar kalendar = null; - BufferedReader reader = new BufferedReader(new StringReader(calendarContent)); - CalendarBuilder builder = new CalendarBuilder(); - try { + public Kalendar buildKalendarFrom(InputStream in, String calType, String calId) { + Kalendar kalendar = null; + + try(BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { + CalendarBuilder builder = new CalendarBuilder(); Calendar calendar = builder.build(reader); kalendar = createKalendar(calType, calId, calendar); } catch (Exception e) { throw new OLATRuntimeException("Error parsing calendar file.", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - throw new OLATRuntimeException("Could not close reader after build calendar file.", e); - } - } } - return kalendar; - } + return kalendar; + } + + @Override + public boolean synchronizeCalendarFrom(InputStream in, String source, Kalendar targetCalendar) { + try(BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { + Calendar calendar = new CalendarBuilder().build(reader); + Kalendar tmpKalendar = createKalendar("TEMP", UUID.randomUUID().toString(), calendar); + + OLATResourceable calOres = getOresHelperFor(targetCalendar); + Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() { + @Override + public Boolean execute() { + //remove event in target calendar which doesn't exist in stream + KalendarEvent[] currentEvents = targetCalendar.getEvents().toArray(new KalendarEvent[0]); + for(KalendarEvent currentEvent:currentEvents) { + if(currentEvent.getExternalSource() != null + && source.equals(currentEvent.getExternalSource())) { + + String eventId = currentEvent.getID(); + if(tmpKalendar.getEvent(eventId) == null) { + targetCalendar.removeEvent(currentEvent); + } + } + } + + // + for(KalendarEvent event:tmpKalendar.getEvents()) { + event.setManagedFlags(new CalendarManagedFlag[]{ CalendarManagedFlag.all } ); + event.setExternalSource(source); + + KalendarEvent currentEvent = targetCalendar.getEvent(event.getID()); + if(currentEvent == null) { + targetCalendar.addEvent(event); + } else { + //need perhaps more refined synchronization per event + targetCalendar.addEvent(event); + } + } + + boolean successfullyPersist = persistCalendar(targetCalendar); + // inform all controller about calendar change for reload + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .fireEventToListenersOf(new CalendarGUIModifiedEvent(targetCalendar), OresHelper.lookupType(CalendarManager.class)); + return new Boolean(successfullyPersist); + } + }); + + return updatedSuccessful.booleanValue(); + } catch (Exception e) { + log.error("", e); + return false; + } + } /** * Save a calendar. @@ -271,6 +344,7 @@ public class ICalFileCalendarManager implements CalendarManager { * @param calendar */ // o_clusterOK by:cg only called by Junit-test + @Override public boolean persistCalendar(Kalendar kalendar) { Calendar calendar = buildCalendar(kalendar); boolean success = writeCalendarFile(calendar,kalendar.getType(), kalendar.getCalendarID()); @@ -296,38 +370,82 @@ public class ICalFileCalendarManager implements CalendarManager { /** * Delete calendar by type and id. */ + @Override public boolean deleteCalendar(String type, String calendarID) { calendarCache.remove( getKeyFor(type,calendarID) ); File fKalendarFile = getCalendarFile(type, calendarID); return fKalendarFile.delete(); } + @Override public File getCalendarICalFile(String type, String calendarID) { File fCalendarICalFile = getCalendarFile(type, calendarID); if (fCalendarICalFile.exists()) return fCalendarICalFile; else return null; } - - /** - * @see org.olat.calendar.CalendarManager#findKalendarConfigForIdentity(org.olat.calendar.model.Kalendar, org.olat.core.gui.UserRequest) - */ - public KalendarConfig findKalendarConfigForIdentity(Kalendar kalendar, UserRequest ureq) { - Preferences guiPreferences = ureq.getUserSession().getGuiPreferences(); - if(guiPreferences == null) { - return null; + + @Override + public CalendarUserConfiguration findCalendarConfigForIdentity(Kalendar calendar, IdentityRef identity) { + return calendarUserConfigDao.getCalendarUserConfiguration(identity, calendar.getCalendarID(), calendar.getType()); + } + + @Override + public void saveCalendarConfigForIdentity(KalendarRenderWrapper wrapper, Identity identity) { + Kalendar calendar = wrapper.getKalendar(); + CalendarUserConfiguration configuration = calendarUserConfigDao + .getCalendarUserConfiguration(identity, calendar.getCalendarID(), calendar.getType()); + + if(configuration == null) { + configuration = calendarUserConfigDao.createCalendarUserConfiguration(wrapper.getKalendar(), identity, + wrapper.getToken(), wrapper.isInAggregatedFeed(), wrapper.isVisible()); + } else { + configuration.setVisible(wrapper.isVisible()); + configuration.setCssClass(wrapper.getCssClass()); + configuration.setToken(wrapper.getToken()); + configuration.setInAggregatedFeed(wrapper.isInAggregatedFeed()); + configuration = calendarUserConfigDao.update(configuration); } - return (KalendarConfig)guiPreferences.get(KalendarConfig.class, kalendar.getCalendarID()); + } + + @Override + public CalendarUserConfiguration createAggregatedCalendarConfig(Identity identity) { + String token = RandomStringUtils.randomAlphanumeric(6); + Kalendar calendar = new Kalendar(identity.getKey().toString(), CalendarManager.TYPE_USER_AGGREGATED); + return calendarUserConfigDao.createCalendarUserConfiguration(calendar, identity, + token, false, false); } - /** - * @see org.olat.calendar.CalendarManager#saveKalendarConfigForIdentity(org.olat.calendar.model.KalendarConfig, org.olat.calendar.model.Kalendar, org.olat.core.gui.UserRequest) - */ - public void saveKalendarConfigForIdentity(KalendarConfig config, Kalendar kalendar, UserRequest ureq) { - Preferences guiPreferences = ureq.getUserSession().getGuiPreferences(); - guiPreferences.putAndSave(KalendarConfig.class, kalendar.getCalendarID(), config); + @Override + public CalendarUserConfiguration saveCalendarConfig(CalendarUserConfiguration configuration) { + return calendarUserConfigDao.update(configuration); + } + + @Override + public CalendarUserConfiguration getCalendarUserConfiguration(Long key) { + return calendarUserConfigDao.getCalendarUserConfiguration(key); + } + + @Override + public List<CalendarUserConfiguration> getCalendarUserConfigurationsList(IdentityRef identity, String... types) { + return calendarUserConfigDao.getCalendarUserConfigurations(identity, types); } - private Calendar buildCalendar(Kalendar kalendar) { + @Override + public Map<CalendarKey,CalendarUserConfiguration> getCalendarUserConfigurationsMap(IdentityRef identity, String... types) { + List<CalendarUserConfiguration> list = calendarUserConfigDao.getCalendarUserConfigurations(identity, types); + Map<CalendarKey,CalendarUserConfiguration> map = new HashMap<>(); + for(CalendarUserConfiguration config:list) { + map.put(new CalendarKey(config.getCalendarId(), config.getType()), config); + } + return map; + } + + @Override + public String getCalendarToken(String calendarType, String calendarID, String userName) { + return calendarUserConfigDao.getCalendarToken(calendarType, calendarID, userName); + } + + protected Calendar buildCalendar(Kalendar kalendar) { Calendar calendar = new Calendar(); // add standard propeties calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN")); @@ -400,6 +518,10 @@ public class ICalFileCalendarManager implements CalendarManager { vEventProperties.add(new Location(kEvent.getLocation())); } + if(kEvent.getDescription() != null) { + vEventProperties.add(new Description(kEvent.getDescription())); + } + // event links Url urlOnce = null; List<KalendarEventLink> kalendarEventLinks = kEvent.getKalendarEventLinks(); @@ -452,14 +574,19 @@ public class ICalFileCalendarManager implements CalendarManager { vEventProperties.add(new XProperty(ICAL_X_OLAT_SOURCENODEID, kEvent.getSourceNodeId())); } - if(kEvent.isManaged()) { - vEventProperties.add(new XProperty(ICAL_X_OLAT_MANAGED, "true")); + if(kEvent.getManagedFlags() != null) { + String val = CalendarManagedFlag.toString(kEvent.getManagedFlags()); + vEventProperties.add(new XProperty(ICAL_X_OLAT_MANAGED, val)); } if(StringHelper.containsNonWhitespace(kEvent.getExternalId())) { vEventProperties.add(new XProperty(ICAL_X_OLAT_EXTERNAL_ID, kEvent.getExternalId())); } + if(StringHelper.containsNonWhitespace(kEvent.getExternalSource())) { + vEventProperties.add(new XProperty(ICAL_X_OLAT_EXTERNAL_SOURCE, kEvent.getExternalSource())); + } + // recurrence String recurrence = kEvent.getRecurrenceRule(); if(recurrence != null && !recurrence.equals("")) { @@ -562,6 +689,11 @@ public class ICalFileCalendarManager implements CalendarManager { calEvent.setLastModified(lastModified.getDate().getTime()); } + Description description = event.getDescription(); + if(description != null) { + calEvent.setDescription(description.getValue()); + } + // location Location location = event.getLocation(); if (location != null) { @@ -616,15 +748,24 @@ public class ICalFileCalendarManager implements CalendarManager { calEvent.setSourceNodeId(sourceNodId.getValue()); } + //managed properties Property managed = event.getProperty(ICAL_X_OLAT_MANAGED); if(managed != null) { - calEvent.setManaged("true".equals(managed.getValue())); + String value = managed.getValue(); + if("true".equals(value)) { + value = "all"; + } + CalendarManagedFlag[] values = CalendarManagedFlag.toEnum(managed.getValue()); + calEvent.setManagedFlags(values); } - Property externalId = event.getProperty(ICAL_X_OLAT_EXTERNAL_ID); if(externalId != null) { calEvent.setExternalId(externalId.getValue()); } + Property externalSource = event.getProperty(ICAL_X_OLAT_EXTERNAL_SOURCE); + if(externalSource != null) { + calEvent.setExternalSource(externalSource.getValue()); + } // recurrence if (event.getProperty(ICAL_RRULE) != null) { @@ -639,9 +780,7 @@ public class ICalFileCalendarManager implements CalendarManager { return calEvent; } - /** - * {@inheritDoc} - */ + @Override public KalendarEvent getRecurringInPeriod(Date periodStart, Date periodEnd, KalendarEvent kEvent) { boolean isRecurring= isRecurringInPeriod(periodStart, periodEnd, kEvent); KalendarEvent recurEvent = null; @@ -669,9 +808,7 @@ public class ICalFileCalendarManager implements CalendarManager { return recurEvent; } - /** - * {@inheritDoc} - */ + @Override public boolean isRecurringInPeriod(Date periodStart, Date periodEnd, KalendarEvent kEvent) { DateList recurDates = CalendarUtils.getRecurringsInPeriod(periodStart, periodEnd, kEvent); return (recurDates != null && !recurDates.isEmpty()); @@ -682,51 +819,46 @@ public class ICalFileCalendarManager implements CalendarManager { return CalendarUtils.getRecurringDatesInPeriod(periodStart, periodEnd, kEvent, tz); } + @Override public File getCalendarFile(String type, String calendarID) { return new File(fStorageBase, "/" + type + "/" + calendarID + ".ics"); } - private void createCalendarFileDirectories() { - File fDirectory = new File(fStorageBase, "/" + TYPE_USER); - fDirectory.mkdirs(); - fDirectory = new File(fStorageBase, "/" + TYPE_GROUP); - fDirectory.mkdirs(); - fDirectory = new File(fStorageBase, "/" + TYPE_COURSE); - fDirectory.mkdirs(); - } - @Override public KalendarRenderWrapper getPersonalCalendar(Identity identity) { Kalendar cal = getCalendar(CalendarManager.TYPE_USER, identity.getName()); - KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal); - KalendarConfig config = new KalendarConfig(identity.getName(), KalendarRenderWrapper.CALENDAR_COLOR_BLUE, true); - calendarWrapper.setKalendarConfig(config); + String fullName = userManager.getUserDisplayName(identity); + KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, fullName); + calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_BLUE); + calendarWrapper.setVisible(true); return calendarWrapper; } @Override - public KalendarRenderWrapper getImportedCalendar(Identity identity, String calendarName) { - Kalendar cal = getCalendar(CalendarManager.TYPE_USER, ImportCalendarManager.getImportedCalendarID(identity, calendarName)); - KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal); - KalendarConfig config = new KalendarConfig(calendarName, KalendarRenderWrapper.CALENDAR_COLOR_BLUE, true); - calendarWrapper.setKalendarConfig(config); + public KalendarRenderWrapper getImportedCalendar(Identity identity, String calendarId) { + Kalendar cal = getCalendar(CalendarManager.TYPE_USER, calendarId); + KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, calendarId); + calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_BLUE); + calendarWrapper.setVisible(true); + calendarWrapper.setImported(true); return calendarWrapper; } @Override public KalendarRenderWrapper getGroupCalendar(BusinessGroup businessGroup) { Kalendar cal = getCalendar(CalendarManager.TYPE_GROUP, businessGroup.getResourceableId().toString()); - KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal); - KalendarConfig config = new KalendarConfig(businessGroup.getName(), KalendarRenderWrapper.CALENDAR_COLOR_ORANGE, true); - calendarWrapper.setKalendarConfig(config); + KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, businessGroup.getName()); + calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_ORANGE); + calendarWrapper.setVisible(true); return calendarWrapper; } - + + @Override public KalendarRenderWrapper getCourseCalendar(ICourse course) { Kalendar cal = getCalendar(CalendarManager.TYPE_COURSE, course.getResourceableId().toString()); - KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal); - KalendarConfig config = new KalendarConfig(course.getCourseTitle(), KalendarRenderWrapper.CALENDAR_COLOR_GREEN, true); - calendarWrapper.setKalendarConfig(config); + KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, course.getCourseTitle()); + calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_GREEN); + calendarWrapper.setVisible(true); return calendarWrapper; } @@ -739,34 +871,37 @@ public class ICalFileCalendarManager implements CalendarManager { type = CalendarManager.TYPE_GROUP; } Kalendar cal = getCalendar(type, resource.getResourceableId().toString()); - KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal); - KalendarConfig config = new KalendarConfig("To delete", KalendarRenderWrapper.CALENDAR_COLOR_GREEN, true); - calendarWrapper.setKalendarConfig(config); + KalendarRenderWrapper calendarWrapper = new KalendarRenderWrapper(cal, "To delete"); + calendarWrapper.setCssClass(KalendarRenderWrapper.CALENDAR_COLOR_GREEN); + calendarWrapper.setVisible(true); return calendarWrapper; } + @Override public void deletePersonalCalendar(Identity identity) { deleteCalendar(CalendarManager.TYPE_USER, identity.getName()); } - + + @Override public void deleteGroupCalendar(BusinessGroup businessGroup) { deleteCalendar(CalendarManager.TYPE_GROUP, businessGroup.getResourceableId().toString()); } - + + @Override public void deleteCourseCalendar(ICourse course) { deleteCalendar(CalendarManager.TYPE_COURSE, course.getResourceableId().toString()); } - + + @Override public void deleteCourseCalendar(OLATResourceable course) { deleteCalendar(CalendarManager.TYPE_COURSE, course.getResourceableId().toString()); } - /** - * @see org.olat.commons.calendar.CalendarManager#addEventTo(org.olat.commons.calendar.model.Kalendar, org.olat.commons.calendar.model.KalendarEvent) - */ + @Override public boolean addEventTo(final Kalendar cal, final KalendarEvent kalendarEvent) { OLATResourceable calOres = getOresHelperFor(cal); Boolean persistSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() { + @Override public Boolean execute() { Kalendar loadedCal = getCalendarFromCache(cal.getType(),cal.getCalendarID()); loadedCal.addEvent(kalendarEvent); @@ -775,16 +910,18 @@ public class ICalFileCalendarManager implements CalendarManager { } }); // inform all controller about calendar change for reload - CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new KalendarModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); return persistSuccessful.booleanValue(); } /** * @see org.olat.commons.calendar.CalendarManager#removeEventFrom(org.olat.commons.calendar.model.Kalendar, org.olat.commons.calendar.model.KalendarEvent) */ + @Override public boolean removeEventFrom(final Kalendar cal, final KalendarEvent kalendarEvent) { OLATResourceable calOres = getOresHelperFor(cal); Boolean removeSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() { + @Override public Boolean execute() { Kalendar loadedCal = getCalendarFromCache(cal.getType(),cal.getCalendarID()); loadedCal.removeEvent(kalendarEvent); @@ -793,16 +930,18 @@ public class ICalFileCalendarManager implements CalendarManager { } }); // inform all controller about calendar change for reload - CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new KalendarModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); return removeSuccessful.booleanValue(); } /** * @see org.olat.commons.calendar.CalendarManager#updateEventFrom(org.olat.commons.calendar.model.Kalendar, org.olat.commons.calendar.model.KalendarEvent) */ + @Override public boolean updateEventFrom(final Kalendar cal, final KalendarEvent kalendarEvent) { OLATResourceable calOres = getOresHelperFor(cal); Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() { + @Override public Boolean execute() { return updateEventAlreadyInSync(cal, kalendarEvent); } @@ -813,6 +952,7 @@ public class ICalFileCalendarManager implements CalendarManager { /** * @see org.olat.commons.calendar.CalendarManager#updateEventFrom(org.olat.commons.calendar.model.Kalendar, org.olat.commons.calendar.model.KalendarEvent) */ + @Override public boolean updateEventAlreadyInSync(final Kalendar cal, final KalendarEvent kalendarEvent) { OLATResourceable calOres = getOresHelperFor(cal); CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(calOres); @@ -821,13 +961,15 @@ public class ICalFileCalendarManager implements CalendarManager { loadedCal.addEvent(kalendarEvent); // add changed event boolean successfullyPersist = persistCalendar(loadedCal); // inform all controller about calendar change for reload - CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new KalendarModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); return successfullyPersist; } + @Override public boolean updateCalendar(final Kalendar cal, final Kalendar importedCal) { OLATResourceable calOres = getOresHelperFor(cal); Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() { + @Override public Boolean execute() { Map<String,KalendarEvent> uidToEvent = new HashMap<String,KalendarEvent>(); for(KalendarEvent event:cal.getEvents()) { @@ -849,7 +991,7 @@ public class ICalFileCalendarManager implements CalendarManager { boolean successfullyPersist = persistCalendar(cal); // inform all controller about calendar change for reload - CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new KalendarModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new CalendarGUIModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); return new Boolean(successfullyPersist); } }); @@ -870,5 +1012,4 @@ public class ICalFileCalendarManager implements CalendarManager { return loadCalendarFromFile(callType, callCalendarID); } } - } diff --git a/src/main/java/org/olat/commons/calendar/ICalFileCalendarUserDeleteManager.java b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarUserDeleteManager.java similarity index 80% rename from src/main/java/org/olat/commons/calendar/ICalFileCalendarUserDeleteManager.java rename to src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarUserDeleteManager.java index 6437c1191e9..a2b775fcf6b 100644 --- a/src/main/java/org/olat/commons/calendar/ICalFileCalendarUserDeleteManager.java +++ b/src/main/java/org/olat/commons/calendar/manager/ICalFileCalendarUserDeleteManager.java @@ -17,12 +17,15 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.commons.calendar; +package org.olat.commons.calendar.manager; +import org.olat.commons.calendar.CalendarManager; import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.user.UserDataDeletable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; /** * @@ -30,16 +33,17 @@ import org.olat.user.UserDataDeletable; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ +@Service("calendarUserDataDeleteManager") public class ICalFileCalendarUserDeleteManager implements UserDataDeletable { private static final OLog log = Tracing.createLoggerFor(ICalFileCalendarUserDeleteManager.class); + @Autowired + private CalendarManager calendarManager; + @Override public void deleteUserData(Identity identity, String newDeletedUserName) { - CalendarManagerFactory.getInstance().getCalendarManager().deletePersonalCalendar(identity); + calendarManager.deletePersonalCalendar(identity); log.debug("Personal calendar deleted for identity=" + identity); } - - - } diff --git a/src/main/java/org/olat/commons/calendar/manager/ImportCalendarManager.java b/src/main/java/org/olat/commons/calendar/manager/ImportCalendarManager.java new file mode 100644 index 00000000000..df7d7b2cca7 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/manager/ImportCalendarManager.java @@ -0,0 +1,207 @@ +/** +* OLAT - Online Learning and Training<br> +* http://www.olat.org +* <p> +* Licensed under the Apache License, Version 2.0 (the "License"); <br> +* you may not use this file except in compliance with the License.<br> +* You may obtain a copy of the License at +* <p> +* http://www.apache.org/licenses/LICENSE-2.0 +* <p> +* Unless required by applicable law or agreed to in writing,<br> +* software distributed under the License is distributed on an "AS IS" BASIS, <br> +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> +* See the License for the specific language governing permissions and <br> +* limitations under the License. +* <p> +* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <hr> +* <a href="http://www.openolat.org"> +* OpenOLAT - Online Learning and Training</a><br> +* This file has been modified by the OpenOLAT community. Changes are licensed +* under the Apache 2.0 license as the original file. +* <p> +*/ + +package org.olat.commons.calendar.manager; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.regex.Pattern; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.CalendarModule; +import org.olat.commons.calendar.model.CalendarUserConfiguration; +import org.olat.commons.calendar.model.ImportedCalendar; +import org.olat.commons.calendar.model.Kalendar; +import org.olat.commons.calendar.model.KalendarComparator; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.WebappHelper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import net.fortuna.ical4j.model.Calendar; + + +/** + * Description:<BR> + * Constants and helper methods for the OLAT iCal feeds + * + * <P> + * Initial Date: July 22, 2008 + * + * @author Udit Sajjanhar + */ +@Service +public class ImportCalendarManager { + private static final OLog log = Tracing.createLoggerFor(ImportCalendarManager.class); + + @Autowired + private CalendarModule calendarModule; + @Autowired + private CalendarManager calendarManager; + @Autowired + private ImportedCalendarDAO importedCalendarDao; + + public KalendarRenderWrapper importCalendar(Identity identity, String calendarName, String type, String url) throws IOException { + File tmpFile = new File(WebappHelper.getTmpDir(), UUID.randomUUID() + ".ics"); + try(InputStream in = new URL(url).openStream()) { + Files.copy(in, tmpFile.toPath()); + } catch(IOException e) { + throw e; + } + + KalendarRenderWrapper calendarWrapper = null; + Calendar calendar = calendarManager.readCalendar(tmpFile); + if(calendar != null) { + String calendarID = getImportedCalendarID(identity, calendarName); + File calendarFile = calendarManager.getCalendarFile(type, calendarID); + if(!tmpFile.renameTo(calendarFile)) { + Files.copy(tmpFile.toPath(), calendarFile.toPath()); + } + + importedCalendarDao.createImportedCalendar(identity, calendarName, calendarID, type, url, new Date()); + calendarWrapper = calendarManager.getImportedCalendar(identity, calendarName); + } + return calendarWrapper; + } + + public KalendarRenderWrapper importCalendar(Identity identity, String calendarName, String type, File file) + throws IOException { + KalendarRenderWrapper calendarWrapper = null; + Calendar calendar = calendarManager.readCalendar(file); + if(calendar != null) { + String calendarID = getImportedCalendarID(identity, calendarName); + File calendarFile = calendarManager.getCalendarFile(type, calendarID); + if(!file.renameTo(calendarFile)) { + Files.copy(file.toPath(), calendarFile.toPath()); + } + + importedCalendarDao.createImportedCalendar(identity, calendarName, calendarID, type, null, new Date()); + calendarWrapper = calendarManager.getImportedCalendar(identity, calendarName); + } + return calendarWrapper; + } + + /** + * Delete an imported calendar + * 1. remove the entry from the database + * 2. delete the calendar file + * @param calendarID + * @param ureq + * @return + */ + public void deleteCalendar(Identity identity, Kalendar calendar) { + importedCalendarDao.deleteImportedCalendar(identity, calendar.getCalendarID(), calendar.getType()); + calendarManager.deleteCalendar(calendar.getType(), calendar.getCalendarID()); + } + + /** + * Get imported calendars for a user. + * @param ureq + * @return + */ + public List<KalendarRenderWrapper> getImportedCalendarsForIdentity(Identity identity, boolean reload) { + // initialize the calendars list + + List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); + if(calendarModule.isEnabled() && calendarModule.isEnablePersonalCalendar()) { + long timestamp = System.currentTimeMillis(); + + List<ImportedCalendar> importedCalendars = importedCalendarDao.getImportedCalendars(identity); + for (ImportedCalendar importedCalendar: importedCalendars) { + String calendarId = importedCalendar.getCalendarId(); + String url = importedCalendar.getUrl(); + + if(reload) { + Date lastUpdate = importedCalendar.getLastUpdate(); + if (url != null && (timestamp - lastUpdate.getTime() > 3600000)) { + log.info("Calendar reload started from url=" + url); + reloadCalendarFromUrl(url, CalendarManager.TYPE_USER, calendarId); + importedCalendar.setLastUpdate(new Date()); + importedCalendar = importedCalendarDao.update(importedCalendar); + log.info("Calendar reloaded from url=" + url); + } + } + + KalendarRenderWrapper calendarWrapper = calendarManager.getImportedCalendar(identity, calendarId); + calendarWrapper.setDisplayName(importedCalendar.getDisplayName()); + calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY); + calendarWrapper.setImported(true); + CalendarUserConfiguration config = calendarManager.findCalendarConfigForIdentity(calendarWrapper.getKalendar(), identity); + if (config != null) { + calendarWrapper.setConfiguration(config); + } + calendars.add(calendarWrapper); + } + Collections.sort(calendars, KalendarComparator.getInstance()); + } + return calendars; + } + + /** + * Reload calendar from url and store calendar file locally. + * @param importUrl + * @param calType + * @param calId + */ + private void reloadCalendarFromUrl(String importUrl, String calType, String calId) { + try (InputStream in = new URL(importUrl).openStream()){ + Kalendar kalendar = calendarManager.buildKalendarFrom(in, calType, calId); + calendarManager.persistCalendar(kalendar); + } catch (Exception e) { + log.error("Could not reload calendar from url=" + importUrl, e); + } + } + + /** + * Get ID of a imported calendar + * @param identity + * @param calendarName + * @return + */ + public static String getImportedCalendarID(Identity identity, String calendarName) { + return identity.getName() + "_" + sanitize(calendarName); + } + + private static String sanitize(String name) { + // delete the preceding and trailing whitespaces + name = name.trim(); + + // replace every other character other than alphabets and numbers by underscore + Pattern specialChars = Pattern.compile("([^a-zA-z0-9])"); + return specialChars.matcher(name).replaceAll("_").toLowerCase(); + } +} diff --git a/src/main/java/org/olat/commons/calendar/manager/ImportToCalendarManager.java b/src/main/java/org/olat/commons/calendar/manager/ImportToCalendarManager.java new file mode 100644 index 00000000000..1720bd39bb2 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/manager/ImportToCalendarManager.java @@ -0,0 +1,142 @@ +/** +* OLAT - Online Learning and Training<br> +* http://www.olat.org +* <p> +* Licensed under the Apache License, Version 2.0 (the "License"); <br> +* you may not use this file except in compliance with the License.<br> +* You may obtain a copy of the License at +* <p> +* http://www.apache.org/licenses/LICENSE-2.0 +* <p> +* Unless required by applicable law or agreed to in writing,<br> +* software distributed under the License is distributed on an "AS IS" BASIS, <br> +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> +* See the License for the specific language governing permissions and <br> +* limitations under the License. +* <p> +* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <hr> +* <a href="http://www.openolat.org"> +* OpenOLAT - Online Learning and Training</a><br> +* This file has been modified by the OpenOLAT community. Changes are licensed +* under the Apache 2.0 license as the original file. +* <p> +*/ + +package org.olat.commons.calendar.manager; + +import java.io.InputStream; +import java.net.URL; +import java.util.Date; +import java.util.List; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.model.ImportedToCalendar; +import org.olat.commons.calendar.model.Kalendar; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + +/** + * Description:<BR> + * Constants and helper methods for the OLAT iCal feeds + * + * <P> + * Initial Date: July 22, 2008 + * + * @author Udit Sajjanhar + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +@Service +public class ImportToCalendarManager { + + private static final OLog log = Tracing.createLoggerFor(ImportToCalendarManager.class); + + @Autowired + private CalendarManager calendarManager; + @Autowired + private ImportedToCalendarDAO importedToCalendarDao; + + /** + * Method used by the cron job + * @return + */ + public boolean updateCalendarIn() { + List<ImportedToCalendar> importedToCalendars = importedToCalendarDao.getImportedToCalendars(); + log.audit("Begin to update " + importedToCalendars.size() + " calendars."); + + int count = 0; + for(ImportedToCalendar importedToCalendar:importedToCalendars) { + String type = importedToCalendar.getToType(); + String id = importedToCalendar.getToCalendarId(); + String importUrl = importedToCalendar.getUrl(); + + Kalendar cal = calendarManager.getCalendar(type, id); + try(InputStream in = new URL(importUrl).openStream()) { + if(calendarManager.synchronizeCalendarFrom(in, importUrl, cal)) { + log.audit("Updated successfully calendar: " + type + " / " + id); + } else { + log.audit("Failed to update calendar: " + type + " / " + id); + } + } catch(Exception ex) { + log.error("Cannot synchronize calendar from url: " + importUrl, ex); + } + + if(count++ % 20 == 0) { + DBFactory.getInstance().commit(); + } + } + return false; + } + + /** + * Append the stream of events to the specified calendar. + * + * @param calenderWrapper The target calendar. + * @param in An iCal stream. + * @return true if successfully imported + */ + public boolean importCalendarIn(KalendarRenderWrapper calenderWrapper, InputStream in) { + try { + Kalendar cal = calenderWrapper.getKalendar(); + Kalendar importedCal = calendarManager.buildKalendarFrom(in, cal.getType(), cal.getCalendarID()); + return calendarManager.updateCalendar(cal, importedCal); + } catch (Exception e) { + log.error("", e); + return false; + } + } + + /** + * Import an external calendar. + * + * @param cal + * @param importUrl + * @return + */ + public boolean importCalendarIn(Kalendar cal, String importUrl) { + try (InputStream in = new URL(importUrl).openStream()){ + Kalendar importedCal = calendarManager.buildKalendarFrom(in, cal.getType(), cal.getCalendarID()); + boolean imported = calendarManager.updateCalendar(cal, importedCal); + if(imported) { + List<ImportedToCalendar> importedToCalendars = importedToCalendarDao.getImportedToCalendars(cal.getCalendarID(), cal.getType(), importUrl); + if(importedToCalendars.isEmpty()) { + importedToCalendarDao.createImportedToCalendar(cal.getCalendarID(), cal.getType(), importUrl, new Date()); + } else { + ImportedToCalendar importedToCalendar = importedToCalendars.get(0); + importedToCalendar.setLastUpdate(new Date()); + importedToCalendar = importedToCalendarDao.update(importedToCalendar); + } + } + return imported; + } catch (Exception e) { + log.error("", e); + return false; + } + } +} diff --git a/src/main/java/org/olat/commons/calendar/manager/ImportedCalendarDAO.java b/src/main/java/org/olat/commons/calendar/manager/ImportedCalendarDAO.java new file mode 100644 index 00000000000..3c3bcec470a --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/manager/ImportedCalendarDAO.java @@ -0,0 +1,89 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.commons.calendar.manager; + +import java.util.Date; +import java.util.List; + +import org.olat.basesecurity.IdentityRef; +import org.olat.commons.calendar.model.ImportedCalendar; +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class ImportedCalendarDAO { + + @Autowired + private DB dbInstance; + + public ImportedCalendar createImportedCalendar(Identity owner, String displayName, + String calendarId, String type, String url, Date lastUpdate) { + + ImportedCalendar calendar = new ImportedCalendar(); + calendar.setCreationDate(new Date()); + calendar.setLastModified(calendar.getCreationDate()); + calendar.setLastUpdate(lastUpdate); + calendar.setDisplayName(displayName); + calendar.setCalendarId(calendarId); + calendar.setType(type); + calendar.setUrl(url); + calendar.setIdentity(owner); + dbInstance.getCurrentEntityManager().persist(calendar); + return calendar; + } + + public ImportedCalendar update(ImportedCalendar importedCalendar) { + importedCalendar.setLastModified(new Date()); + return dbInstance.getCurrentEntityManager().merge(importedCalendar); + } + + public List<ImportedCalendar> getImportedCalendar(IdentityRef identity, String calendarId, String type) { + return dbInstance.getCurrentEntityManager() + .createNamedQuery("importedCalendarByIdentityCalendarIdAndType", ImportedCalendar.class) + .setParameter("identityKey", identity.getKey()) + .setParameter("calendarId", calendarId) + .setParameter("type", type) + .getResultList(); + } + + public List<ImportedCalendar> getImportedCalendars(IdentityRef identity) { + StringBuilder sb = new StringBuilder(); + sb.append("select cal from importedcal cal where cal.identity.key=:identityKey"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), ImportedCalendar.class) + .setParameter("identityKey", identity.getKey()) + .getResultList(); + } + + public void deleteImportedCalendar(IdentityRef identity, String calendarId, String type) { + List<ImportedCalendar> calendars = getImportedCalendar(identity, calendarId, type); + for(ImportedCalendar calendar:calendars) { + dbInstance.getCurrentEntityManager().remove(calendar); + } + } +} diff --git a/src/main/java/org/olat/commons/calendar/manager/ImportedToCalendarDAO.java b/src/main/java/org/olat/commons/calendar/manager/ImportedToCalendarDAO.java new file mode 100644 index 00000000000..21893f946b9 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/manager/ImportedToCalendarDAO.java @@ -0,0 +1,73 @@ +/** + * <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.commons.calendar.manager; + +import java.util.Date; +import java.util.List; + +import org.olat.commons.calendar.model.ImportedToCalendar; +import org.olat.core.commons.persistence.DB; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class ImportedToCalendarDAO { + + @Autowired + private DB dbInstance; + + public ImportedToCalendar createImportedToCalendar(String toCalendarId, String toType, String url, Date lastUpdate) { + ImportedToCalendar calendar = new ImportedToCalendar(); + calendar.setCreationDate(new Date()); + calendar.setLastModified(calendar.getCreationDate()); + calendar.setLastUpdate(lastUpdate); + calendar.setToCalendarId(toCalendarId); + calendar.setToType(toType); + calendar.setUrl(url); + dbInstance.getCurrentEntityManager().persist(calendar); + return calendar; + } + + public ImportedToCalendar update(ImportedToCalendar importedToCalendar) { + importedToCalendar.setLastModified(new Date()); + return dbInstance.getCurrentEntityManager().merge(importedToCalendar); + } + + public List<ImportedToCalendar> getImportedToCalendars(String toCalendarId, String toType, String url) { + return dbInstance.getCurrentEntityManager() + .createNamedQuery("importedToCalendarByIdTypeAndUrl", ImportedToCalendar.class) + .setParameter("toCalendarId", toCalendarId) + .setParameter("toType", toType) + .setParameter("url", url) + .getResultList(); + } + + public List<ImportedToCalendar> getImportedToCalendars() { + return dbInstance.getCurrentEntityManager() + .createNamedQuery("importedToCalendars", ImportedToCalendar.class) + .getResultList(); + } +} diff --git a/src/main/java/org/olat/commons/calendar/model/ICalToken.java b/src/main/java/org/olat/commons/calendar/model/CalendarFileInfos.java similarity index 68% rename from src/main/java/org/olat/commons/calendar/model/ICalToken.java rename to src/main/java/org/olat/commons/calendar/model/CalendarFileInfos.java index 1d234a99ce9..deb9444677e 100644 --- a/src/main/java/org/olat/commons/calendar/model/ICalToken.java +++ b/src/main/java/org/olat/commons/calendar/model/CalendarFileInfos.java @@ -19,39 +19,35 @@ */ package org.olat.commons.calendar.model; +import java.io.File; + /** * - * Initial date: 18.08.2015<br> + * Initial date: 27.08.2015<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class ICalToken { +public class CalendarFileInfos { + private final String calendarId; private final String type; - private final String token; - private final Long resourceId; + private final File calendarFile; - public ICalToken(String type, String token, Long resourceId) { + public CalendarFileInfos(String calendarId, String type, File calendarFile) { + this.calendarFile = calendarFile; + this.calendarId = calendarId; this.type = type; - this.token = token; - this.resourceId = resourceId; } - - public ICalToken(ICalToken iCalToken, String token) { - this.type = iCalToken.getType(); - this.token = token; - this.resourceId = iCalToken.getResourceId(); + + public String getCalendarId() { + return calendarId; } public String getType() { return type; } - public String getToken() { - return token; - } - - public Long getResourceId() { - return resourceId; + public File getCalendarFile() { + return calendarFile; } } diff --git a/src/main/java/org/olat/commons/calendar/model/CalendarKey.java b/src/main/java/org/olat/commons/calendar/model/CalendarKey.java new file mode 100644 index 00000000000..16876d6ae4c --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/model/CalendarKey.java @@ -0,0 +1,70 @@ +/** + * <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.commons.calendar.model; + +import java.io.Serializable; + +/** + * Useable in HashMap and co. + * + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarKey implements Serializable { + + private static final long serialVersionUID = 4402127033060370317L; + private final String type; + private final String calendarId; + + public CalendarKey(String calendarId, String type) { + this.type = type; + this.calendarId = calendarId; + } + + public String getType() { + return type; + } + + public String getCalendarId() { + return calendarId; + } + + @Override + public int hashCode() { + return type.hashCode() + + calendarId.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof CalendarKey) { + CalendarKey key = (CalendarKey)obj; + return key.calendarId.equals(calendarId) + && key.type.equals(type); + } + return false; + } + +} diff --git a/src/main/java/org/olat/commons/calendar/model/CalendarUserConfiguration.java b/src/main/java/org/olat/commons/calendar/model/CalendarUserConfiguration.java new file mode 100644 index 00000000000..b6e30c20a2f --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/model/CalendarUserConfiguration.java @@ -0,0 +1,193 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.commons.calendar.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.GenericGenerator; +import org.olat.basesecurity.IdentityImpl; +import org.olat.core.id.Identity; +import org.olat.core.id.ModifiedInfo; +import org.olat.core.id.Persistable; + +/** + * + * Initial date: 25.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="caluserconfig") +@Table(name="o_cal_use_config") +@NamedQueries({ + @NamedQuery(name="loadCalUserConfigByIdentityAndCalendar", query="select conf from caluserconfig conf where conf.identity.key=:identityKey and conf.calendarId=:calendarId and conf.type=:type") +}) +public class CalendarUserConfiguration implements ModifiedInfo, Persistable { + + private static final long serialVersionUID = -3206707703546422555L; + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "hilo") + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) + private Date lastModified; + + @Column(name="c_calendar_id", nullable=false, insertable=true, updatable=false) + private String calendarId; + @Column(name="c_calendar_type", nullable=false, insertable=true, updatable=false) + private String type; + + @Column(name="c_token", nullable=true, insertable=true, updatable=true) + private String token; + @Column(name="c_cssclass", nullable=true, insertable=true, updatable=true) + private String cssClass; + @Column(name="c_visible", nullable=true, insertable=true, updatable=true) + private boolean visible; + @Column(name="c_aggregated_feed", nullable=true, insertable=true, updatable=true) + private boolean inAggregatedFeed; + + @ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false) + private Identity identity; + + @Override + public Long getKey() { + return key; + } + + public void setKey(Long key) { + this.key = key; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public Date getLastModified() { + return lastModified; + } + + @Override + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + public String getCalendarId() { + return calendarId; + } + + public void setCalendarId(String calendarId) { + this.calendarId = calendarId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getCssClass() { + return cssClass; + } + + public void setCssClass(String cssClass) { + this.cssClass = cssClass; + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public boolean isInAggregatedFeed() { + return inAggregatedFeed; + } + + public void setInAggregatedFeed(boolean inAggregatedFeed) { + this.inAggregatedFeed = inAggregatedFeed; + } + + public Identity getIdentity() { + return identity; + } + + public void setIdentity(Identity identity) { + this.identity = identity; + } + + @Override + public int hashCode() { + return key == null ? 37487 :key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof CalendarUserConfiguration) { + CalendarUserConfiguration config = (CalendarUserConfiguration)obj; + return key != null && key.equals(config.key); + } + return false; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} diff --git a/src/main/java/org/olat/commons/calendar/model/ImportedCalendar.java b/src/main/java/org/olat/commons/calendar/model/ImportedCalendar.java new file mode 100644 index 00000000000..b12baa4a058 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/model/ImportedCalendar.java @@ -0,0 +1,186 @@ +/** + * <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.commons.calendar.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.GenericGenerator; +import org.olat.basesecurity.IdentityImpl; +import org.olat.core.id.Identity; +import org.olat.core.id.ModifiedInfo; +import org.olat.core.id.Persistable; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="importedcal") +@Table(name="o_cal_import") +@NamedQueries({ + @NamedQuery(name="importedCalendarByIdentityCalendarIdAndType", + query="select cal from importedcal cal where cal.identity.key=:identityKey and cal.calendarId=:calendarId and cal.type=:type"), + +}) +public class ImportedCalendar implements ModifiedInfo, Persistable { + + private static final long serialVersionUID = 8800143218824364514L; + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "hilo") + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) + private Date lastModified; + + @Column(name="c_calendar_id", nullable=false, insertable=true, updatable=false) + private String calendarId; + @Column(name="c_calendar_type", nullable=false, insertable=true, updatable=false) + private String type; + + @Column(name="c_displayname", nullable=false, insertable=true, updatable=true) + private String displayName; + @Column(name="c_lastupdate", nullable=false, insertable=true, updatable=true) + private Date lastUpdate; + @Column(name="c_url", nullable=true, insertable=true, updatable=false) + private String url; + + @ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false) + private Identity identity; + + + @Override + public Long getKey() { + return key; + } + + public void setKey(Long key) { + this.key = key; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public Date getLastModified() { + return lastModified; + } + + @Override + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + public String getCalendarId() { + return calendarId; + } + + public void setCalendarId(String calendarId) { + this.calendarId = calendarId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public Date getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(Date lastUpdate) { + this.lastUpdate = lastUpdate; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Identity getIdentity() { + return identity; + } + + public void setIdentity(Identity identity) { + this.identity = identity; + } + + @Override + public int hashCode() { + return key == null ? 37487 :key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof ImportedCalendar) { + ImportedCalendar cal = (ImportedCalendar)obj; + return key != null && key.equals(cal.key); + } + return false; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} diff --git a/src/main/java/org/olat/commons/calendar/model/ImportedToCalendar.java b/src/main/java/org/olat/commons/calendar/model/ImportedToCalendar.java new file mode 100644 index 00000000000..a8c9937759d --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/model/ImportedToCalendar.java @@ -0,0 +1,157 @@ +/** + * <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.commons.calendar.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.GenericGenerator; +import org.olat.core.id.ModifiedInfo; +import org.olat.core.id.Persistable; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="importedtocal") +@Table(name="o_cal_import_to") +@NamedQueries({ + @NamedQuery(name="importedToCalendarByIdTypeAndUrl", query="select cal from importedtocal cal where cal.toCalendarId=:toCalendarId and cal.toType=:toType and cal.url=:url"), + @NamedQuery(name="importedToCalendars", query="select cal from importedtocal cal") +}) +public class ImportedToCalendar implements ModifiedInfo, Persistable { + + private static final long serialVersionUID = -1026023143604898592L; + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "hilo") + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) + private Date lastModified; + + @Column(name="c_to_calendar_id", nullable=false, insertable=true, updatable=false) + private String toCalendarId; + @Column(name="c_to_calendar_type", nullable=false, insertable=true, updatable=false) + private String toType; + + @Column(name="c_lastupdate", nullable=false, insertable=true, updatable=true) + private Date lastUpdate; + @Column(name="c_url", nullable=false, insertable=true, updatable=true) + private String url; + + @Override + public Long getKey() { + return key; + } + + public void setKey(Long key) { + this.key = key; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public Date getLastModified() { + return lastModified; + } + + @Override + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + public String getToCalendarId() { + return toCalendarId; + } + + public void setToCalendarId(String toCalendarId) { + this.toCalendarId = toCalendarId; + } + + public String getToType() { + return toType; + } + + public void setToType(String toType) { + this.toType = toType; + } + + public Date getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(Date lastUpdate) { + this.lastUpdate = lastUpdate; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public int hashCode() { + return key == null ? 37487 :key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof ImportedToCalendar) { + ImportedToCalendar cal = (ImportedToCalendar)obj; + return key != null && key.equals(cal.key); + } + return false; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} diff --git a/src/main/java/org/olat/commons/calendar/model/KalendarComparator.java b/src/main/java/org/olat/commons/calendar/model/KalendarComparator.java index 9e0675b5641..82807c5d6ff 100644 --- a/src/main/java/org/olat/commons/calendar/model/KalendarComparator.java +++ b/src/main/java/org/olat/commons/calendar/model/KalendarComparator.java @@ -39,8 +39,8 @@ public class KalendarComparator implements Comparator<KalendarRenderWrapper> { public int compare(KalendarRenderWrapper calendar0, KalendarRenderWrapper calendar1) { // if of the same type, order by display name if (calendar0.getKalendar().getType() == calendar1.getKalendar().getType()) - return calendar0.getKalendarConfig().getDisplayName().compareTo( - calendar1.getKalendarConfig().getDisplayName()); + return calendar0.getDisplayName().compareTo( + calendar1.getDisplayName()); // if of different type, order by type if (calendar0.getKalendar().getType() == CalendarManager.TYPE_USER) return -1; // TYPE_USER is displayed first if (calendar0.getKalendar().getType() == CalendarManager.TYPE_GROUP) return +1; // TYPE GROUP is displayed last diff --git a/src/main/java/org/olat/commons/calendar/model/KalendarConfig.java b/src/main/java/org/olat/commons/calendar/model/KalendarConfig.java deleted file mode 100644 index f6d5be8e6a9..00000000000 --- a/src/main/java/org/olat/commons/calendar/model/KalendarConfig.java +++ /dev/null @@ -1,79 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.commons.calendar.model; - -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; - -public class KalendarConfig { - - private transient String displayName; - private String css; - private boolean vis; - private Long resId; - - public KalendarConfig() { - this("Calendar", KalendarRenderWrapper.CALENDAR_COLOR_BLUE, true); - } - - public KalendarConfig(String displayName, String calendarCSS, boolean visible) { - this.displayName = displayName; - this.css = calendarCSS; - this.vis = visible; - this.resId = null; - } - - public Long getResId() { - return this.resId; - } - - public void setResId(Long resId) { - this.resId = resId; - } - - public String getCss() { - return css; - } - - public void setCss(String css) { - this.css = css; - } - - public boolean isVis() { - return vis; - } - - public void setVis(boolean vis) { - this.vis = vis; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } -} diff --git a/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java b/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java index d3a24d52250..2f71a115f75 100644 --- a/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java +++ b/src/main/java/org/olat/commons/calendar/model/KalendarEvent.java @@ -30,10 +30,11 @@ import java.util.Calendar; import java.util.Date; import java.util.List; -import net.fortuna.ical4j.model.Recur; - +import org.olat.commons.calendar.CalendarManagedFlag; import org.olat.commons.calendar.CalendarUtils; +import net.fortuna.ical4j.model.Recur; + public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> { public static final int CLASS_PRIVATE = 0; @@ -70,8 +71,9 @@ public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> { private String recurrenceRule; private String recurrenceExc; - private boolean managed; private String externalId; + private String externalSource; + private CalendarManagedFlag[] managedFlags; public KalendarEvent() { // save no-args constructor for XStream @@ -143,37 +145,45 @@ public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> { public String getID() { return id; } + public Date getBegin() { return begin; } + public void setBegin(Date begin) { this.begin = begin; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Date getEnd() { return end; } + public void setEnd(Date end) { this.end = end; } + public String getSubject() { return subject; } + public void setSubject(String subject) { this.subject = subject; } - public boolean isManaged() { - return managed; + public CalendarManagedFlag[] getManagedFlags() { + return managedFlags; } - public void setManaged(boolean managed) { - this.managed = managed; + public void setManagedFlags(CalendarManagedFlag[] managedFlags) { + this.managedFlags = managedFlags; } public String getExternalId() { @@ -184,6 +194,14 @@ public class KalendarEvent implements Cloneable, Comparable<KalendarEvent> { this.externalId = externalId; } + public String getExternalSource() { + return externalSource; + } + + public void setExternalSource(String externalSource) { + this.externalSource = externalSource; + } + public int getClassification() { return classification; } diff --git a/src/main/java/org/olat/commons/calendar/CalendarNotificationHandler.java b/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationHandler.java similarity index 76% rename from src/main/java/org/olat/commons/calendar/CalendarNotificationHandler.java rename to src/main/java/org/olat/commons/calendar/notification/CalendarNotificationHandler.java index 0c70893886f..1157df4f789 100644 --- a/src/main/java/org/olat/commons/calendar/CalendarNotificationHandler.java +++ b/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationHandler.java @@ -17,16 +17,16 @@ * BPS Bildungsportal Sachsen GmbH, http://www.bps-system.de * <p> */ -package org.olat.commons.calendar; +package org.olat.commons.calendar.notification; import java.util.Collection; import java.util.Date; import java.util.Locale; +import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.ui.CalendarController; -import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.notifications.NotificationsHandler; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.Publisher; @@ -38,16 +38,19 @@ import org.olat.core.commons.services.notifications.model.TitleItem; import org.olat.core.gui.translator.Translator; import org.olat.core.id.OLATResourceable; import org.olat.core.id.context.BusinessControlFactory; -import org.olat.core.logging.LogDelegator; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.resource.OresHelper; import org.olat.course.CourseModule; import org.olat.group.BusinessGroup; -import org.olat.group.BusinessGroupService; +import org.olat.group.manager.BusinessGroupDAO; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; /** * @@ -61,10 +64,22 @@ import org.olat.repository.RepositoryManager; * * @author bja */ -public class CalendarNotificationHandler extends LogDelegator implements NotificationsHandler { +@Service +public class CalendarNotificationHandler implements NotificationsHandler { + private static final OLog log = Tracing.createLoggerFor(CalendarNotificationHandler.class); private static final String CSS_CLASS_CALENDAR_ICON = "o_calendar_icon"; + + @Autowired + private RepositoryManager repositoryManager; + @Autowired + private BusinessGroupDAO businessGroupDao; + @Autowired + private CalendarManager calendarManager; + @Autowired + private NotificationsManager notificationsManager; + @Override public SubscriptionInfo createSubscriptionInfo(Subscriber subscriber, Locale locale, Date compareDate) { SubscriptionInfo si = null; Publisher p = subscriber.getPublisher(); @@ -73,7 +88,7 @@ public class CalendarNotificationHandler extends LogDelegator implements Notific // do not try to create a subscription info if state is deleted - results in // exceptions, course // can't be loaded when already deleted - if (NotificationsManager.getInstance().isPublisherValid(p) && compareDate.before(latestNews)) { + if (notificationsManager.isPublisherValid(p) && compareDate.before(latestNews)) { Long id = p.getResId(); String type = p.getSubidentifier(); @@ -83,14 +98,14 @@ public class CalendarNotificationHandler extends LogDelegator implements Notific String calType = null; String title = null; if (type.equals(CalendarController.ACTION_CALENDAR_COURSE)) { - String displayName = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(id); + String displayName = repositoryManager.lookupDisplayNameByOLATResourceableId(id); calType = CalendarManager.TYPE_COURSE; title = translator.translate("cal.notifications.header.course", new String[]{displayName}); } else if (type.equals(CalendarController.ACTION_CALENDAR_GROUP)) { - BusinessGroup group = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(id); + BusinessGroup group = businessGroupDao.load(id); calType = CalendarManager.TYPE_GROUP; if(group == null) { - return NotificationsManager.getInstance().getNoSubscriptionInfo(); + return notificationsManager.getNoSubscriptionInfo(); } title = translator.translate("cal.notifications.header.group", new String[]{ group.getName() }); } @@ -105,22 +120,22 @@ public class CalendarNotificationHandler extends LogDelegator implements Notific } else if("CalendarManager.course".equals(p.getResName())) { try { OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.getCourseTypeName(), p.getResId()); - RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(ores, true); + RepositoryEntry re = repositoryManager.lookupRepositoryEntry(ores, true); bPath = "[RepositoryEntry:" + re.getKey() + "]";//Fallback } catch (Exception e) { - logError("Error processing calendar notifications of publisher:" + p.getKey(), e); - return NotificationsManager.getInstance().getNoSubscriptionInfo(); + log.error("Error processing calendar notifications of publisher:" + p.getKey(), e); + return notificationsManager.getNoSubscriptionInfo(); } } else { //cannot make link without business path - return NotificationsManager.getInstance().getNoSubscriptionInfo(); + return notificationsManager.getNoSubscriptionInfo(); } - Kalendar cal = CalendarManagerFactory.getInstance().getCalendarManager().getCalendar(calType, id.toString()); + Kalendar cal = calendarManager.getCalendar(calType, id.toString()); Collection<KalendarEvent> calEvents = cal.getEvents(); for (KalendarEvent kalendarEvent : calEvents) { if (showEvent(compareDate, kalendarEvent)) { - logDebug("found a KalendarEvent: " + kalendarEvent.getSubject() + " with time: " + kalendarEvent.getBegin() + log.debug("found a KalendarEvent: " + kalendarEvent.getSubject() + " with time: " + kalendarEvent.getBegin() + " modified before: " + compareDate.toString(), null); // found a modified event in this calendar Date modDate = null; @@ -156,12 +171,12 @@ public class CalendarNotificationHandler extends LogDelegator implements Notific } } } catch (Exception e) { - logError("Unexpected exception", e); + log.error("Unexpected exception", e); checkPublisher(p); - si = NotificationsManager.getInstance().getNoSubscriptionInfo(); + si = notificationsManager.getNoSubscriptionInfo(); } } else { - si = NotificationsManager.getInstance().getNoSubscriptionInfo(); + si = notificationsManager.getNoSubscriptionInfo(); } return si; } @@ -169,19 +184,19 @@ public class CalendarNotificationHandler extends LogDelegator implements Notific private void checkPublisher(Publisher p) { try { if(CalendarController.ACTION_CALENDAR_GROUP.equals(p.getSubidentifier())) { - BusinessGroup bg = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(p.getResId()); + BusinessGroup bg = businessGroupDao.load(p.getResId()); if(bg == null) { - logInfo("deactivating publisher with key; " + p.getKey(), null); - NotificationsManager.getInstance().deactivate(p); + log.info("deactivating publisher with key; " + p.getKey(), null); + notificationsManager.deactivate(p); } } else if (CalendarController.ACTION_CALENDAR_COURSE.equals(p.getSubidentifier())) { if(!NotificationsUpgradeHelper.checkCourse(p)) { - logInfo("deactivating publisher with key; " + p.getKey(), null); - NotificationsManager.getInstance().deactivate(p); + log.info("deactivating publisher with key; " + p.getKey(), null); + notificationsManager.deactivate(p); } } } catch (Exception e) { - logError("", e); + log.error("", e); } } @@ -198,20 +213,20 @@ public class CalendarNotificationHandler extends LogDelegator implements Notific @Override public String createTitleInfo(Subscriber subscriber, Locale locale) { try { - Translator translator = Util.createPackageTranslator(this.getClass(), locale); + Translator translator = Util.createPackageTranslator(CalendarManager.class, locale); String title = null; Long id = subscriber.getPublisher().getResId(); String type = subscriber.getPublisher().getSubidentifier(); if (type.equals(CalendarController.ACTION_CALENDAR_COURSE)) { - String displayName = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(id); + String displayName = repositoryManager.lookupDisplayNameByOLATResourceableId(id); title = translator.translate("cal.notifications.header.course", new String[]{displayName}); } else if (type.equals(CalendarController.ACTION_CALENDAR_GROUP)) { - BusinessGroup group = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(id); + BusinessGroup group = businessGroupDao.load(id); title = translator.translate("cal.notifications.header.group", new String[]{group.getName()}); } return title; } catch (Exception e) { - logError("Error while creating calendar notifications for subscriber: " + subscriber.getKey(), e); + log.error("Error while creating calendar notifications for subscriber: " + subscriber.getKey(), e); checkPublisher(subscriber.getPublisher()); return "-"; } diff --git a/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationManagerImpl.java b/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationManagerImpl.java index 464355efdcc..be7893851a6 100644 --- a/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationManagerImpl.java +++ b/src/main/java/org/olat/commons/calendar/notification/CalendarNotificationManagerImpl.java @@ -20,21 +20,27 @@ package org.olat.commons.calendar.notification; import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.CalendarNotificationManager; import org.olat.commons.calendar.ui.CalendarController; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.id.OLATResourceable; import org.olat.core.util.resource.OresHelper; import org.olat.group.BusinessGroup; -import org.olat.group.BusinessGroupService; +import org.olat.group.manager.BusinessGroupDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; /** * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ +@Service("calendarNotificationManager") public class CalendarNotificationManagerImpl implements CalendarNotificationManager { + @Autowired + private BusinessGroupDAO businessGroupDao; + @Override public SubscriptionContext getSubscriptionContext(KalendarRenderWrapper kalendarRenderWrapper) { String caller = kalendarRenderWrapper.getKalendar().getType(); @@ -72,10 +78,9 @@ public class CalendarNotificationManagerImpl implements CalendarNotificationMana public BusinessGroup getBusinessGroup(KalendarRenderWrapper kalendarRenderWrapper) { String caller = kalendarRenderWrapper.getKalendar().getType(); if (caller.equals(CalendarController.CALLER_COLLAB) || caller.equals(CalendarManager.TYPE_GROUP)) { - Long resId = kalendarRenderWrapper.getKalendarConfig().getResId(); - if (resId == null) resId = Long.parseLong(kalendarRenderWrapper.getKalendar().getCalendarID()); + Long resId = Long.parseLong(kalendarRenderWrapper.getKalendar().getCalendarID()); if (resId != null) { - BusinessGroup businessGroup = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(resId); + BusinessGroup businessGroup = businessGroupDao.load(resId); if (businessGroup != null) { return businessGroup; } diff --git a/src/main/java/org/olat/commons/calendar/restapi/CalWebService.java b/src/main/java/org/olat/commons/calendar/restapi/CalWebService.java index 4debd3c1ed2..1cf0bddf98f 100644 --- a/src/main/java/org/olat/commons/calendar/restapi/CalWebService.java +++ b/src/main/java/org/olat/commons/calendar/restapi/CalWebService.java @@ -19,8 +19,11 @@ */ package org.olat.commons.calendar.restapi; +import static org.olat.commons.calendar.restapi.CalendarWSHelper.hasReadAccess; +import static org.olat.commons.calendar.restapi.CalendarWSHelper.hasWriteAccess; +import static org.olat.commons.calendar.restapi.CalendarWSHelper.processEvents; +import static org.olat.commons.calendar.restapi.CalendarWSHelper.transfer; import static org.olat.restapi.security.RestSecurityHelper.getUserRequest; -import static org.olat.commons.calendar.restapi.CalendarWSHelper.*; import java.util.ArrayList; import java.util.Collection; @@ -45,9 +48,9 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.util.StringHelper; @@ -106,7 +109,7 @@ public class CalWebService { return Response.serverError().status(Status.UNAUTHORIZED).build(); } - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); if(eventId == null) { return Response.ok().status(Status.NOT_FOUND).build(); } else { @@ -150,7 +153,7 @@ public class CalWebService { } KalendarEvent kalEvent; - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); if(!StringHelper.containsNonWhitespace(event.getId())) { String id = UUID.randomUUID().toString(); kalEvent = new KalendarEvent(id, event.getSubject(), event.getBegin(), event.getEnd()); diff --git a/src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java b/src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java index 43295c2a555..e28a2bb30fc 100644 --- a/src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java +++ b/src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java @@ -46,8 +46,8 @@ public class CalendarVO { id = wrapper.getKalendar().getType() + "_" + wrapper.getKalendar().getCalendarID(); subscribed = wrapper.isSubscribed(); type = wrapper.getKalendar().getType(); - css = wrapper.getKalendarConfig().getCss(); - displayName = wrapper.getKalendarConfig().getDisplayName(); + css = wrapper.getCssClass(); + displayName = wrapper.getDisplayName(); this.canWrite = canWrite; } diff --git a/src/main/java/org/olat/commons/calendar/restapi/EventVO.java b/src/main/java/org/olat/commons/calendar/restapi/EventVO.java index ec9b5433ae9..ea823727f9d 100644 --- a/src/main/java/org/olat/commons/calendar/restapi/EventVO.java +++ b/src/main/java/org/olat/commons/calendar/restapi/EventVO.java @@ -25,6 +25,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; +import org.olat.commons.calendar.CalendarManagedFlag; import org.olat.commons.calendar.model.KalendarEvent; @XmlAccessorType(XmlAccessType.FIELD) @@ -41,8 +42,9 @@ public class EventVO { private Date end; private boolean allDayEvent; - private boolean managed; + private String managedFlags; private String externalId; + private String externalSource; public EventVO() { // @@ -57,8 +59,9 @@ public class EventVO { end = event.getEnd(); allDayEvent = event.isAllDayEvent(); calendarId = event.getCalendar().getType() + "_" + event.getCalendar().getCalendarID(); - managed = event.isManaged(); + managedFlags = CalendarManagedFlag.toString(event.getManagedFlags()); externalId = event.getExternalId(); + externalSource = event.getExternalSource(); } public String getId() { @@ -125,12 +128,12 @@ public class EventVO { this.calendarId = calendarId; } - public boolean isManaged() { - return managed; + public String getManagedFlags() { + return managedFlags; } - public void setManaged(boolean managed) { - this.managed = managed; + public void setManagedFlags(String managedFlags) { + this.managedFlags = managedFlags; } public String getExternalId() { @@ -140,4 +143,12 @@ public class EventVO { public void setExternalId(String externalId) { this.externalId = externalId; } + + public String getExternalSource() { + return externalSource; + } + + public void setExternalSource(String externalSource) { + this.externalSource = externalSource; + } } diff --git a/src/main/java/org/olat/commons/calendar/restapi/UserCalendarWebService.java b/src/main/java/org/olat/commons/calendar/restapi/UserCalendarWebService.java index 2cd25d77de8..d84e83d7bcf 100644 --- a/src/main/java/org/olat/commons/calendar/restapi/UserCalendarWebService.java +++ b/src/main/java/org/olat/commons/calendar/restapi/UserCalendarWebService.java @@ -48,9 +48,8 @@ import org.olat.basesecurity.IdentityShort; import org.olat.collaboration.CollaborationManager; import org.olat.collaboration.CollaborationTools; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.CalendarModule; -import org.olat.commons.calendar.model.KalendarConfig; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.CoreSpringFactory; @@ -68,6 +67,8 @@ import org.olat.course.config.CourseConfig; import org.olat.course.nodes.CalCourseNode; import org.olat.course.nodes.cal.CourseCalendars; import org.olat.course.run.userview.CourseTreeVisitor; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.course.run.userview.UserCourseEnvironmentImpl; import org.olat.course.run.userview.VisibleTreeFilter; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; @@ -187,28 +188,30 @@ public class UserCalendarWebService { } } else if("course".equals(type) && (calendarModule.isEnableCourseElementCalendar() || calendarModule.isEnableCourseToolCalendar())) { Long courseId = Long.parseLong(id); + IdentityEnvironment ienv = new IdentityEnvironment(); + ienv.setIdentity(ureq.getIdentity()); + ienv.setRoles(ureq.getUserSession().getRoles()); ICourse course = CourseFactory.loadCourse(courseId); - wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, course, null); + UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment()); + wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, userCourseEnv, null); } else if("user".equals(type) && calendarModule.isEnablePersonalCalendar()) { List<String> identityName = Collections.singletonList(id); List<IdentityShort> shorts = BaseSecurityManager.getInstance().findShortIdentitiesByName(identityName); if(shorts.size() == 1 && shorts.get(0).getKey().equals(ureq.getIdentity().getKey())) { - wrapper = getPersonalCalendar(ureq); + wrapper = getPersonalCalendar(ureq.getIdentity()); } } return wrapper; } - private KalendarRenderWrapper getPersonalCalendar(UserRequest ureq) { + private KalendarRenderWrapper getPersonalCalendar(Identity identity) { // get the personal calendar - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); - KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(ureq.getIdentity()); + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); + KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(identity); calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); - KalendarConfig personalKalendarConfig = calendarManager.findKalendarConfigForIdentity( - calendarWrapper.getKalendar(), ureq); - if (personalKalendarConfig != null) { - calendarWrapper.getKalendarConfig().setCss(personalKalendarConfig.getCss()); - calendarWrapper.getKalendarConfig().setVis(personalKalendarConfig.isVis()); + CalendarUserConfiguration config = calendarManager.findCalendarConfigForIdentity(calendarWrapper.getKalendar(), identity); + if (config != null) { + calendarWrapper.setConfiguration(config); } return calendarWrapper; } @@ -221,7 +224,7 @@ public class UserCalendarWebService { if(calendarModule.isEnabled()) { if(calendarModule.isEnablePersonalCalendar()) { - KalendarRenderWrapper personalWrapper = getPersonalCalendar(ureq); + KalendarRenderWrapper personalWrapper = getPersonalCalendar(ureq.getIdentity()); calVisitor.visit(personalWrapper); } @@ -231,6 +234,11 @@ public class UserCalendarWebService { SearchRepositoryEntryParameters repoParams = new SearchRepositoryEntryParameters(retrievedUser, roles, "CourseModule"); repoParams.setOnlyExplicitMember(true); repoParams.setIdentity(retrievedUser); + + IdentityEnvironment ienv = new IdentityEnvironment(); + ienv.setIdentity(retrievedUser); + ienv.setRoles(roles); + List<RepositoryEntry> entries = rm.genericANDQueryWithRolesRestriction(repoParams, 0, -1, true); for(RepositoryEntry entry:entries) { AccessResult result = acManager.isAccessible(entry, retrievedUser, false); @@ -238,15 +246,16 @@ public class UserCalendarWebService { try { final ICourse course = CourseFactory.loadCourse(entry.getOlatResource()); CourseConfig config = course.getCourseEnvironment().getCourseConfig(); + UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment()); + if(config.isCalendarEnabled()) { - KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, entry.getOlatResource(), null); + KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, userCourseEnv, null); calVisitor.visit(wrapper); } else { - IdentityEnvironment ienv = new IdentityEnvironment(retrievedUser, roles); CalCourseNodeVisitor visitor = new CalCourseNodeVisitor(); new CourseTreeVisitor(course, ienv).visit(visitor, new VisibleTreeFilter()); if(visitor.isFound()) { - KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, entry.getOlatResource(), null); + KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, userCourseEnv, null); calVisitor.visit(wrapper); } } diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarAggregatedURLController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarAggregatedURLController.java new file mode 100644 index 00000000000..8ce24a9d892 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarAggregatedURLController.java @@ -0,0 +1,57 @@ +/** + * <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.commons.calendar.ui; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.util.Util; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarAggregatedURLController extends BasicController { + + public CalendarAggregatedURLController(UserRequest ureq, WindowControl wControl, String icalFeedLink) { + super(ureq, wControl); + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); + + VelocityContainer colorVC = createVelocityContainer("calAggregatedFeed"); + colorVC.contextPut("icalFeedLink", icalFeedLink); + putInitialPanel(colorVC); + } + + @Override + public void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void doDispose() { + // nothing to dispose + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarColorChooserController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarColorChooserController.java index 2c188491fff..2baccad0628 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarColorChooserController.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarColorChooserController.java @@ -25,7 +25,6 @@ package org.olat.commons.calendar.ui; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -38,9 +37,8 @@ import org.olat.core.gui.control.controller.BasicController; public class CalendarColorChooserController extends BasicController { - private VelocityContainer colorVC; private String choosenColor; - private final KalendarRenderWrapper calendarWrapper; + private final CalendarPersonalConfigurationRow row; private static final String[] colors = new String[]{ "o_cal_green", "o_cal_blue", "o_cal_orange", @@ -48,20 +46,18 @@ public class CalendarColorChooserController extends BasicController { }; public CalendarColorChooserController(UserRequest ureq, WindowControl wControl, - KalendarRenderWrapper calendarWrapper, String currentCssSelection) { + CalendarPersonalConfigurationRow row) { super(ureq, wControl); - this.calendarWrapper = calendarWrapper; - - colorVC = createVelocityContainer("calEdit", "calColor"); - + this.row = row; + VelocityContainer colorVC = createVelocityContainer("calEdit", "calColor"); for(String color:colors) { - addColor(color, currentCssSelection); + addColor(color, row.getCssClass(), colorVC); } putInitialPanel(colorVC); } - private void addColor(String css, String currentCssSelection) { + private void addColor(String css, String currentCssSelection, VelocityContainer colorVC) { Link colorLink = LinkFactory.createCustomLink(css, "selc", "", Link.NONTRANSLATED, colorVC, this); if (currentCssSelection.equals(css)){ colorLink.setIconLeftCSS("o_icon o_cal_colorchooser_selected"); @@ -84,8 +80,8 @@ public class CalendarColorChooserController extends BasicController { return choosenColor; } - public KalendarRenderWrapper getCalendarWrapper() { - return calendarWrapper; + public CalendarPersonalConfigurationRow getRow() { + return row; } @Override diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarConfigurationController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarConfigurationController.java deleted file mode 100644 index 3c997f638f3..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarConfigurationController.java +++ /dev/null @@ -1,353 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.commons.calendar.ui; - -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.ICalTokenGenerator; -import org.olat.commons.calendar.ICalTokenGenerator.FeedLink; -import org.olat.commons.calendar.model.ICalToken; -import org.olat.commons.calendar.model.KalendarConfig; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarGUIAddEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIImportEvent; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.velocity.VelocityContainer; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.controller.BasicController; -import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; -import org.olat.core.gui.control.generic.modal.DialogBoxController; -import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; -import org.olat.core.util.StringHelper; -import org.olat.core.util.Util; - -public class CalendarConfigurationController extends BasicController { - - private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); - - private static final Object CMD_ADD = "add"; - private static final Object CMD_IMPORT = "import"; - private static final Object CMD_TOGGLE_DISPLAY = "tglvis"; - private static final Object CMD_CHOOSE_COLOR = "cc"; - private static final Object CMD_ICAL_FEED = "if"; - private static final Object CMD_ICAL_REGENERATE = "rf"; - private static final Object CMD_ICAL_REMOVE_FEED = "rmif"; - private static final String PARAM_ID = "id"; - - private ICalTokens iCalTokens; - private VelocityContainer configVC; - private List<KalendarRenderWrapper> calendars; - - private CloseableModalController cmc; - private CalendarExportController exportController; - private CalendarColorChooserController colorChooser; - - private DialogBoxController confirmRemoveDialog; - private DialogBoxController confirmRegenerateDialog; - - public CalendarConfigurationController(List<KalendarRenderWrapper> calendars, UserRequest ureq, WindowControl wControl, boolean insideManager) { - super(ureq, wControl); - setTranslator(Util.createPackageTranslator(CalendarManager.class, ureq.getLocale())); - - configVC = new VelocityContainer("calEdit", VELOCITY_ROOT + "/calConfig.html", getTranslator(), this); - configVC.contextPut("insideManager", insideManager); - configVC.contextPut("identity", ureq.getIdentity()); - - setCalendars(calendars); - putInitialPanel(configVC); - } - - @Override - protected void doDispose() { - // - } - - public void setCalendars(List<KalendarRenderWrapper> calendars) { - this.calendars = calendars; - configVC.contextPut("calendars", calendars); - - ICalTokens newICalTokens = new ICalTokens(getIdentity().getKey()); - List<ICalToken> tokens = ICalTokenGenerator.getICalAuthTokens(getIdentity()); - for(ICalToken token:tokens) { - newICalTokens.put(new ICalTokenKey(token.getType(), token.getResourceId()), token); - } - configVC.contextPut("icalTokens", newICalTokens); - iCalTokens = newICalTokens; - } - - @Override - public void event(UserRequest ureq, Component source, Event event) { - if (source == configVC) { - String command = event.getCommand(); - if (command.equals(CMD_ADD)) { - // add new event to calendar - String calendarID = ureq.getParameter(PARAM_ID); - fireEvent(ureq, new KalendarGUIAddEvent(calendarID, new Date())); - } else if (command.equals(CMD_IMPORT)) { - // add new event to calendar - String calendarID = ureq.getParameter(PARAM_ID); - fireEvent(ureq, new KalendarGUIImportEvent(calendarID)); - } else if (command.equals(CMD_TOGGLE_DISPLAY)) { - doToogleDisplay(ureq, ureq.getParameter(PARAM_ID)); - } else if (command.equals(CMD_CHOOSE_COLOR)) { - doOpenColorChooser(ureq, ureq.getParameter(PARAM_ID)); - } else if (command.equals(CMD_ICAL_FEED)) { - doShowICalink(ureq.getParameter(PARAM_ID)); - } else if (command.equals(CMD_ICAL_REGENERATE)) { - confirmRegenerateDialog = activateOkCancelDialog(ureq, translate("cal.icalfeed.regenerate.title"), translate("cal.icalfeed.regenerate.warning"), confirmRegenerateDialog); - confirmRegenerateDialog.setUserObject(ureq.getParameter(PARAM_ID)); - } else if (command.equals(CMD_ICAL_REMOVE_FEED)) { - confirmRemoveDialog = activateOkCancelDialog(ureq, translate("cal.icalfeed.remove.title"), translate("cal.icalfeed.remove.confirmation_message"), confirmRemoveDialog); - confirmRemoveDialog.setUserObject(ureq.getParameter(PARAM_ID)); - } - } - } - - @Override - public void event(UserRequest ureq, Controller source, Event event) { - if (source == colorChooser) { - cmc.deactivate(); - if (event == Event.DONE_EVENT) { - doChooseColor(ureq); - } - cleanUp(); - } else if (source == confirmRemoveDialog ) { - if (DialogBoxUIFactory.isOkEvent(event)) { - doRemoveToken((String)confirmRemoveDialog.getUserObject()); - showInfo("cal.icalfeed.remove.info"); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } else if (source == confirmRegenerateDialog) { - if (DialogBoxUIFactory.isOkEvent(event)) { - doRegenerateToken((String)confirmRegenerateDialog.getUserObject()); - } - } else if (source == cmc) { - cleanUp(); - } - configVC.setDirty(true); - } - - private void cleanUp() { - removeAsListenerAndDispose(cmc); - removeAsListenerAndDispose(colorChooser); - removeAsListenerAndDispose(exportController); - - cmc = null; - colorChooser = null; - exportController = null; - } - - private void doShowICalink(String calendarID) { - removeAsListenerAndDispose(cmc); - removeAsListenerAndDispose(exportController); - - KalendarRenderWrapper calendarWrapper = findKalendarRenderWrapper(calendarID); - FeedLink calFeedLink = ICalTokenGenerator.getIcalFeedLink(calendarWrapper.getKalendar().getType(), calendarID, getIdentity()); - exportController = new CalendarExportController(getLocale(), getWindowControl(), calFeedLink.getLink()); - listenTo(exportController); - - cmc = new CloseableModalController(getWindowControl(), translate("close"), exportController.getInitialComponent()); - listenTo(cmc); - cmc.activate(); - - String type = calendarWrapper.getKalendar().getType(); - if(CalendarManager.TYPE_USER.equals(type)) { - iCalTokens.update(new ICalTokenKey(type, getIdentity().getKey()), calFeedLink.getToken()); - } else { - iCalTokens.update(new ICalTokenKey(type, Long.valueOf(calendarID)), calFeedLink.getToken()); - } - configVC.setDirty(true); - } - - private void doOpenColorChooser(UserRequest ureq, String calendarID) { - removeAsListenerAndDispose(cmc); - removeAsListenerAndDispose(colorChooser); - - KalendarRenderWrapper calendarWrapper = findKalendarRenderWrapper(calendarID); - colorChooser = new CalendarColorChooserController(ureq, getWindowControl(), calendarWrapper, calendarWrapper.getKalendarConfig().getCss()); - - listenTo(colorChooser); - - cmc = new CloseableModalController(getWindowControl(), translate("close"), colorChooser.getInitialComponent(), false, translate("cal.color.title")); - listenTo(cmc); - cmc.activate(); - } - - private void doChooseColor(UserRequest ureq) { - String choosenColor = colorChooser.getChoosenColor(); - KalendarRenderWrapper calendarWrapper = colorChooser.getCalendarWrapper(); - KalendarConfig config = calendarWrapper.getKalendarConfig(); - config.setCss(choosenColor); - CalendarManagerFactory.getInstance().getCalendarManager().saveKalendarConfigForIdentity(config, calendarWrapper.getKalendar(), ureq); - fireEvent(ureq, Event.CHANGED_EVENT); - } - - private void doToogleDisplay(UserRequest ureq, String calendarID) { - KalendarRenderWrapper calendarWrapper = findKalendarRenderWrapper(calendarID); - KalendarConfig config = calendarWrapper.getKalendarConfig(); - config.setVis(!config.isVis()); - CalendarManagerFactory.getInstance().getCalendarManager().saveKalendarConfigForIdentity(config, calendarWrapper.getKalendar(), ureq); - fireEvent(ureq, Event.CHANGED_EVENT); - } - - private void doRegenerateToken(String calendarId) { - removeAsListenerAndDispose(cmc); - removeAsListenerAndDispose(exportController); - - KalendarRenderWrapper calendarWrapper = findKalendarRenderWrapper(calendarId); - FeedLink calFeedLink = ICalTokenGenerator.regenerateIcalAuthToken(calendarWrapper.getKalendar().getType(), calendarId, getIdentity()); - - exportController = new CalendarExportController(getLocale(), getWindowControl(), calFeedLink.getLink()); - listenTo(exportController); - - cmc = new CloseableModalController(getWindowControl(), translate("close"), exportController.getInitialComponent()); - cmc.activate(); - listenTo(cmc); - - String type = calendarWrapper.getKalendar().getType(); - if(CalendarManager.TYPE_USER.equals(type)) { - iCalTokens.update(new ICalTokenKey(type, getIdentity().getKey()), calFeedLink.getToken()); - } else { - iCalTokens.update(new ICalTokenKey(type, Long.valueOf(calendarId)), calFeedLink.getToken()); - } - configVC.setDirty(true); - } - - private void doRemoveToken(String calendarId) { - KalendarRenderWrapper calendarWrapper = findKalendarRenderWrapper(calendarId); - ICalTokenGenerator.destroyIcalAuthToken(calendarWrapper.getKalendar().getType(), calendarId, getIdentity()); - - String type = calendarWrapper.getKalendar().getType(); - if(CalendarManager.TYPE_USER.equals(type)) { - iCalTokens.remove(new ICalTokenKey(type, getIdentity().getKey())); - } else { - iCalTokens.remove(new ICalTokenKey(type, Long.valueOf(calendarId))); - } - configVC.setDirty(true); - } - - private KalendarRenderWrapper findKalendarRenderWrapper(String calendarID) { - for (KalendarRenderWrapper calendarWrapper : calendars) { - if (calendarWrapper.getKalendar().getCalendarID().equals(calendarID)) - return calendarWrapper; - } - return null; - } - - public static class ICalTokens { - - private final Long identityKey; - private final Map<ICalTokenKey, ICalToken> tokenMap = new HashMap<>(); - - public ICalTokens(Long identityKey) { - this.identityKey = identityKey; - } - - public void put(ICalTokenKey key, ICalToken token) { - synchronized(tokenMap) { - tokenMap.put(key, token); - } - } - - public void remove(ICalTokenKey key) { - synchronized(tokenMap) { - tokenMap.remove(key); - } - } - - public void update(ICalTokenKey key, String newToken) { - ICalToken token = tokenMap.get(key); - synchronized(tokenMap) { - if(token != null) { - tokenMap.put(key, new ICalToken(token, newToken)); - } else { - tokenMap.put(key, new ICalToken(key.getType(), newToken, key.getResourceId())); - } - } - } - - public boolean hasIcalFeed(KalendarRenderWrapper wrapper) { - String type = wrapper.getKalendar().getType(); - Long calendarId; - if(CalendarManager.TYPE_USER.equals(type)) { - calendarId = identityKey; - } else { - calendarId = Long.valueOf(wrapper.getKalendar().getCalendarID()); - } - ICalTokenKey tokenKey = new ICalTokenKey(type, calendarId); - synchronized(tokenMap) { - ICalToken token = tokenMap.get(tokenKey); - return token != null && StringHelper.containsNonWhitespace(token.getToken()); - } - } - } - - public static class ICalTokenKey { - - private final String type; - private final Long resourceId; - - public ICalTokenKey(String type, Long resourceId) { - this.type = type; - this.resourceId = resourceId; - } - - public String getType() { - return type; - } - - public Long getResourceId() { - return resourceId; - } - - @Override - public int hashCode() { - return type.hashCode() - + resourceId.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if(this == obj) { - return true; - } - if(obj instanceof ICalTokenKey) { - ICalTokenKey key = (ICalTokenKey)obj; - return key.resourceId.equals(resourceId) - && key.type.equals(type); - } - return false; - } - } -} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarController.java index 85d8322a967..c1c8ea0098c 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarController.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarController.java @@ -62,13 +62,6 @@ public interface CalendarController extends Controller { */ public void setCalendars(List<KalendarRenderWrapper> calendars); - /** - * Sets the list of KalendarRenderWrappers for this calendar controller. - * - * @param calendars - */ - public void setCalendars(List<KalendarRenderWrapper> calendars, List<KalendarRenderWrapper> importedCalendars); - /** * Sets the calendar dirty. The calendar controller should * issue an KalendarModfied event upon next user click. diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java index 221ae2433e7..42cb300a64d 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java @@ -27,13 +27,14 @@ import java.util.Locale; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.time.DateUtils; +import org.olat.commons.calendar.CalendarManagedFlag; import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.CalendarModule; import org.olat.commons.calendar.CalendarUtils; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.model.KalendarEventLink; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarGUIEditEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIEditEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -57,7 +58,7 @@ import org.springframework.beans.factory.annotation.Autowired; public class CalendarDetailsController extends BasicController { private final KalendarEvent calEvent; - private final KalendarRenderWrapper calWrapper; + private final KalendarRenderWrapper calendar; private Link editButton; @@ -67,24 +68,33 @@ public class CalendarDetailsController extends BasicController { private CalendarModule calendarModule; public CalendarDetailsController(UserRequest ureq, WindowControl wControl, - KalendarEvent event, KalendarRenderWrapper calWrapper) { + KalendarEvent event, KalendarRenderWrapper calendar) { super(ureq, wControl, Util.createPackageTranslator(CalendarManager.class, ureq.getLocale())); this.calEvent = event; - this.calWrapper = calWrapper; + this.calendar = calendar; isGuestOnly = ureq.getUserSession().getRoles().isGuestOnly(); VelocityContainer mainVC = createVelocityContainer("event_details"); - - if(!isGuestOnly && - !(calendarModule.isManagedCalendars() && event.isManaged())) { + + if(!isGuestOnly + && !(calendarModule.isManagedCalendars() && CalendarManagedFlag.isManaged(event, CalendarManagedFlag.all)) + && calendar.getAccess() == KalendarRenderWrapper.ACCESS_READ_WRITE) { editButton = LinkFactory.createButton("edit", mainVC, this); mainVC.put("edit", editButton); } mainVC.contextPut("date", formatDate()); - mainVC.contextPut("subject", event.getSubject()); - if(StringHelper.containsNonWhitespace(event.getLocation())) { - mainVC.contextPut("location", event.getLocation()); + + if(!calendar.isPrivateEventsVisible() && event.getClassification() == KalendarEvent.CLASS_X_FREEBUSY) { + mainVC.contextPut("subject", ""); + mainVC.contextPut("description", ""); + mainVC.contextPut("links", new ArrayList<LinkWrapper>(1)); + } else { + mainVC.contextPut("subject", event.getSubject()); + mainVC.contextPut("description", event.getDescription()); + if(StringHelper.containsNonWhitespace(event.getLocation())) { + mainVC.contextPut("location", event.getLocation()); + } + mainVC.contextPut("links", renderEventLinks()); } - mainVC.contextPut("links", renderEventLinks()); putInitialPanel(mainVC); } @@ -110,7 +120,7 @@ public class CalendarDetailsController extends BasicController { private List<LinkWrapper> renderEventLinks() { - List<LinkWrapper> linkWrappers = new ArrayList<LinkWrapper>(); + List<LinkWrapper> linkWrappers = new ArrayList<>(); List<KalendarEventLink> kalendarEventLinks = calEvent.getKalendarEventLinks(); if (kalendarEventLinks != null && !kalendarEventLinks.isEmpty()) { String rootUri = Settings.getServerContextPathURI(); @@ -155,7 +165,7 @@ public class CalendarDetailsController extends BasicController { protected void event(UserRequest ureq, Component source, Event event) { if(source == editButton) { if(!isGuestOnly) { - fireEvent(ureq, new KalendarGUIEditEvent(calEvent, calWrapper)); + fireEvent(ureq, new CalendarGUIEditEvent(calEvent, calendar)); } } } diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryDetailsController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryDetailsController.java index 9ce653ab56a..06194db7aca 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryDetailsController.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryDetailsController.java @@ -29,8 +29,9 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import org.olat.commons.calendar.CalendarManagedFlag; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; +import org.olat.commons.calendar.CalendarModule; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.model.KalendarEventLink; @@ -56,6 +57,8 @@ import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.gui.util.CSSHelper; import org.olat.core.helpers.Settings; import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.springframework.beans.factory.annotation.Autowired; public class CalendarEntryDetailsController extends BasicController { @@ -75,11 +78,12 @@ public class CalendarEntryDetailsController extends BasicController { private MediaLinksController mediaLinksController; private Link deleteButton; + @Autowired + private CalendarManager calendarManager; + public CalendarEntryDetailsController(UserRequest ureq, KalendarEvent kalendarEvent, KalendarRenderWrapper calendarWrapper, List<KalendarRenderWrapper> availableCalendars, boolean isNew, String caller, WindowControl wControl) { - super(ureq, wControl); - - setBasePackage(CalendarManager.class); + super(ureq, wControl, Util.createPackageTranslator(CalendarModule.class, ureq.getLocale())); this.availableCalendars = availableCalendars; this.kalendarEvent = kalendarEvent; @@ -102,10 +106,9 @@ public class CalendarEntryDetailsController extends BasicController { eventVC.contextPut("isReadOnly", new Boolean(isReadOnly)); pane.addTab(translate("tab.event"), eventVC); - //linkVC = new VelocityContainer("calEditLinks", VELOCITY_ROOT + "/calEditLinks.html", getTranslator(), this); linkVC = createVelocityContainer ("calEditLinks"); linkVC.contextPut("caller", caller); - if (!isReadOnly) { + if (!isReadOnly && !CalendarManagedFlag.isManaged(kalendarEvent, CalendarManagedFlag.links)) { //course node links pane.addTab(translate("tab.links"), linkVC); @@ -131,6 +134,7 @@ public class CalendarEntryDetailsController extends BasicController { mainPanel = putInitialPanel(mainVC); } + @Override public void event(UserRequest ureq, Component source, Event event) { if (source == pane) { if (event instanceof TabbedPaneChangedEvent) { @@ -172,7 +176,7 @@ public class CalendarEntryDetailsController extends BasicController { if (source == deleteYesNoController) { if (DialogBoxUIFactory.isYesEvent(event)) { Kalendar cal = kalendarEvent.getCalendar(); - CalendarManagerFactory.getInstance().getCalendarManager().removeEventFrom(cal,kalendarEvent); + calendarManager.removeEventFrom(cal,kalendarEvent); fireEvent(ureq, Event.DONE_EVENT); } } else if (source == copyEventToCalendarController) { @@ -198,7 +202,7 @@ public class CalendarEntryDetailsController extends BasicController { KalendarRenderWrapper calendarWrapper = iter.next(); if (!calendarWrapper.getKalendar().getCalendarID().equals(calendarID)) continue; Kalendar cal = calendarWrapper.getKalendar(); - boolean result = CalendarManagerFactory.getInstance().getCalendarManager().addEventTo(cal, kalendarEvent); + boolean result = calendarManager.addEventTo(cal, kalendarEvent); if (result==false) { // if one failed => done not successfully doneSuccessfully = false; @@ -207,7 +211,7 @@ public class CalendarEntryDetailsController extends BasicController { } else { // this is an existing event, so we get the previousely assigned calendar from the event Kalendar cal = kalendarEvent.getCalendar(); - doneSuccessfully =CalendarManagerFactory.getInstance().getCalendarManager().updateEventFrom(cal, kalendarEvent); + doneSuccessfully = calendarManager.updateEventFrom(cal, kalendarEvent); } // check if event is still available if (!doneSuccessfully) { @@ -252,7 +256,7 @@ public class CalendarEntryDetailsController extends BasicController { links.add(new KalendarEventLink(provider, id, displayName, uri, iconCssClass)); Kalendar cal = kalendarEvent.getCalendar(); - doneSuccessfully = CalendarManagerFactory.getInstance().getCalendarManager().updateEventFrom(cal, kalendarEvent); + doneSuccessfully = calendarManager.updateEventFrom(cal, kalendarEvent); } if (doneSuccessfully) { @@ -265,7 +269,7 @@ public class CalendarEntryDetailsController extends BasicController { //save externals links Kalendar cal = kalendarEvent.getCalendar(); if (kalendarEvent.getCalendar() != null) { - boolean doneSuccessfully = CalendarManagerFactory.getInstance().getCalendarManager().updateEventFrom(cal, kalendarEvent); + boolean doneSuccessfully = calendarManager.updateEventFrom(cal, kalendarEvent); if (doneSuccessfully) { fireEvent(ureq, Event.DONE_EVENT); } else { diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java index c83203bad63..878ca320847 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarEntryForm.java @@ -31,6 +31,7 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import org.olat.commons.calendar.CalendarManagedFlag; import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.CalendarUtils; import org.olat.commons.calendar.model.KalendarEvent; @@ -53,6 +54,7 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; public class CalendarEntryForm extends FormBasicController { @@ -66,16 +68,16 @@ public class CalendarEntryForm extends FormBasicController { private KalendarRenderWrapper choosenWrapper; private StaticTextElement calendarName; private SingleSelection chooseCalendar; - private TextElement subject, location; + private TextElement subjectEl, descriptionEl, locationEl; private SelectionElement allDayEvent; private DateChooser begin, end; private SingleSelection classification; - private boolean readOnly, isNew; private SingleSelection chooseRecurrence; private DateChooser recurrenceEnd; private List<KalendarRenderWrapper> writeableCalendars; private FormLink multi; private boolean isMulti; + private final boolean readOnly, isNew; private String[] calendarKeys, calendarValues; private String[] keysRecurrence, valuesRecurrence; @@ -94,8 +96,7 @@ public class CalendarEntryForm extends FormBasicController { public CalendarEntryForm(UserRequest ureq, WindowControl wControl, KalendarEvent event, KalendarRenderWrapper choosenWrapper, Collection<KalendarRenderWrapper> availableCalendars, boolean isNew) { super(ureq, wControl); - - setBasePackage(CalendarManager.class); + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); this.event = event; this.choosenWrapper = choosenWrapper; @@ -117,7 +118,7 @@ public class CalendarEntryForm extends FormBasicController { for (int i = 0; i < writeableCalendars.size(); i++) { KalendarRenderWrapper cw = writeableCalendars.get(i); calendarKeys[i] = cw.getKalendar().getCalendarID(); - calendarValues[i] = cw.getKalendarConfig().getDisplayName(); + calendarValues[i] = cw.getDisplayName(); } // String currentRecur = CalendarUtils.getRecurrence(event.getRecurrenceRule()); @@ -155,15 +156,15 @@ public class CalendarEntryForm extends FormBasicController { protected void setEntry(KalendarEvent kalendarEvent) { // subject if (readOnly && kalendarEvent.getClassification() == KalendarEvent.CLASS_X_FREEBUSY) { - subject.setValue(getTranslator().translate("cal.form.subject.hidden")); + subjectEl.setValue(getTranslator().translate("cal.form.subject.hidden")); } else { - subject.setValue(kalendarEvent.getSubject()); + subjectEl.setValue(kalendarEvent.getSubject()); } // location if (readOnly && kalendarEvent.getClassification() == KalendarEvent.CLASS_X_FREEBUSY) { - location.setValue(getTranslator().translate("cal.form.location.hidden")); + locationEl.setValue(getTranslator().translate("cal.form.location.hidden")); } else { - location.setValue(kalendarEvent.getLocation()); + locationEl.setValue(kalendarEvent.getLocation()); } begin.setDate(kalendarEvent.getBegin()); end.setDate(kalendarEvent.getEnd()); @@ -205,35 +206,38 @@ public class CalendarEntryForm extends FormBasicController { @Override protected boolean validateFormLogic (UserRequest ureq) { + boolean allOk = true; + begin.clearError(); if (begin.getDate() == null) { begin.setErrorKey("cal.form.error.date", null); - return false; + allOk &= false; } + end.clearError(); if (end.getDate() == null) { end.setErrorKey("cal.form.error.date", null); - return false; + allOk &= false; } if (end.getDate().before(begin.getDate())) { end.setErrorKey("cal.form.error.endbeforebegin", null); - return false; + allOk &= false; } boolean hasEnd = !chooseRecurrence.getSelectedKey().equals(RECURRENCE_NONE); - + recurrenceEnd.clearError(); if (hasEnd && recurrenceEnd.getDate() == null) { recurrenceEnd.setErrorKey("cal.form.error.date", null); - return false; + allOk &= false; } if (hasEnd && recurrenceEnd.getDate().before(begin.getDate())) { recurrenceEnd.setErrorKey("cal.form.error.endbeforebegin", null); - return false; + allOk &= false; } - return true; + return allOk & super.validateFormLogic(ureq); } /** @@ -242,10 +246,11 @@ public class CalendarEntryForm extends FormBasicController { */ public KalendarEvent getUpdatedKalendarEvent() { // subject - event.setSubject(subject.getValue()); - + event.setSubject(subjectEl.getValue()); + // description + event.setDescription(descriptionEl.getValue()); // location - event.setLocation(location.getValue()); + event.setLocation(locationEl.getValue()); // date / time event.setBegin(begin.getDate()); @@ -301,50 +306,79 @@ public class CalendarEntryForm extends FormBasicController { chooseCalendar.select(choosenWrapper.getKalendar().getCalendarID(), true); } chooseCalendar.setVisible(isNew); + if(event.getManagedFlags() != null && event.getManagedFlags().length > 0) { + chooseCalendar.setEnabled(false); + } - String calName = choosenWrapper == null ? "" : StringHelper.escapeHtml(choosenWrapper.getKalendarConfig().getDisplayName()); + String calName = choosenWrapper == null ? "" : StringHelper.escapeHtml(choosenWrapper.getDisplayName()); calendarName = uifactory.addStaticTextElement("calendarname", "cal.form.calendarname", calName, formLayout); calendarName.setVisible(!isNew); boolean fb = readOnly && event.getClassification() == KalendarEvent.CLASS_X_FREEBUSY; + String subject = fb ? translate("cal.form.subject.hidden") : event.getSubject(); + if(subject != null && subject.length() > 64) { + subjectEl = uifactory.addTextAreaElement("subject", "cal.form.subject", -1, 3, 40, true, subject, formLayout); + } else { + subjectEl = uifactory.addTextElement("subject", "cal.form.subject", 255, subject, formLayout); + } + subjectEl.setMandatory(true); + subjectEl.setNotEmptyCheck("cal.form.error.mandatory"); + subjectEl.setEnabled(!CalendarManagedFlag.isManaged(event, CalendarManagedFlag.subject)); - subject = uifactory.addTextAreaElement("subject", "cal.form.subject", -1, 3, 40, true, fb?translate("cal.form.subject.hidden"):event.getSubject(), formLayout); - subject.setMandatory(true); - subject.setNotEmptyCheck("cal.form.error.mandatory"); + String description = event.getDescription(); + descriptionEl = uifactory.addTextAreaElement("description", "cal.form.description", -1, 3, 40, true, description, formLayout); + descriptionEl.setMandatory(true); + descriptionEl.setNotEmptyCheck("cal.form.error.mandatory"); + descriptionEl.setEnabled(!CalendarManagedFlag.isManaged(event, CalendarManagedFlag.subject)); - location = uifactory.addTextAreaElement("location", "cal.form.location", -1, 3, 40, true, fb?translate("cal.form.location.hidden"):event.getLocation(), formLayout); + String location = fb ? translate("cal.form.location.hidden") : event.getLocation(); + if(location != null && location.length() > 64) { + locationEl = uifactory.addTextAreaElement("location", "cal.form.location", -1, 3, 40, true, location, formLayout); + } else { + locationEl = uifactory.addTextElement("location", "cal.form.location", 255, location, formLayout); + } + locationEl.setEnabled(!CalendarManagedFlag.isManaged(event, CalendarManagedFlag.location)); + boolean managedDates = CalendarManagedFlag.isManaged(event, CalendarManagedFlag.dates); allDayEvent = uifactory.addCheckboxesHorizontal("allday", "cal.form.allday", formLayout, new String[]{"xx"}, new String[]{null}); allDayEvent.select("xx", event.isAllDayEvent()); allDayEvent.addActionListener(FormEvent.ONCHANGE); + allDayEvent.setEnabled(!managedDates); begin = uifactory.addDateChooser("begin", "cal.form.begin", null, formLayout); begin.setDisplaySize(21); begin.setDateChooserTimeEnabled(!event.isAllDayEvent()); begin.setMandatory(true); begin.setDate(event.getBegin()); + begin.setEnabled(!managedDates); end = uifactory.addDateChooser("end", "cal.form.end", null, formLayout); end.setDisplaySize(21); end.setDateChooserTimeEnabled(!event.isAllDayEvent()); end.setMandatory(true); end.setDate(event.getEnd()); + end.setEnabled(!managedDates); chooseRecurrence = uifactory.addDropdownSingleselect("cal.form.recurrence", formLayout, keysRecurrence, valuesRecurrence, null); String currentRecur = CalendarUtils.getRecurrence(event.getRecurrenceRule()); boolean rk = currentRecur != null && !currentRecur.equals(""); chooseRecurrence.select(rk ? currentRecur:RECURRENCE_NONE, true); chooseRecurrence.addActionListener(FormEvent.ONCHANGE); + chooseRecurrence.setEnabled(!managedDates); recurrenceEnd = uifactory.addDateChooser("recurrence", "cal.form.recurrence.end", null, formLayout); recurrenceEnd.setDisplaySize(21); recurrenceEnd.setDateChooserTimeEnabled(false); recurrenceEnd.setMandatory(true); Date recurEnd = CalendarUtils.getRecurrenceEndDate(event.getRecurrenceRule()); - if(recurEnd != null) recurrenceEnd.setDate(recurEnd); + if(recurEnd != null) { + recurrenceEnd.setDate(recurEnd); + } + recurrenceEnd.setEnabled(!managedDates); recurrenceEnd.setVisible(!chooseRecurrence.getSelectedKey().equals(RECURRENCE_NONE)); classification = uifactory.addRadiosVertical("classification", "cal.form.class", formLayout, classKeys, classValues); + classification.setEnabled(!CalendarManagedFlag.isManaged(event, CalendarManagedFlag.classification)); switch (event.getClassification()) { case KalendarEvent.CLASS_PRIVATE: classification.select("0", true); break; case KalendarEvent.CLASS_X_FREEBUSY: classification.select("1", true); break; diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarExportController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarExportController.java deleted file mode 100644 index f6c2dbf3d6b..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarExportController.java +++ /dev/null @@ -1,66 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.ui; - -import java.util.Locale; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.velocity.VelocityContainer; -import org.olat.core.gui.control.DefaultController; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.translator.Translator; -import org.olat.core.util.Util; - - -public class CalendarExportController extends DefaultController { - - private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); - - private Translator translator; - private VelocityContainer colorVC; - - public CalendarExportController(Locale locale, WindowControl wControl, String icalFeedLink) { - super(wControl); - translator = Util.createPackageTranslator(CalendarManager.class, locale); - - colorVC = new VelocityContainer("calEdit", VELOCITY_ROOT + "/calIcalFeed.html", translator, this); - colorVC.contextPut("icalFeedLink", icalFeedLink); - - setInitialComponent(colorVC); - } - - public void event(UserRequest ureq, Component source, Event event) { - // - } - - protected void doDispose() { - // nothing to dispose - } -} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarImportByUrlController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarImportByUrlController.java deleted file mode 100644 index 661e191415b..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarImportByUrlController.java +++ /dev/null @@ -1,144 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.ui; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.ImportCalendarManager; -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.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.logging.OLATRuntimeException; - -/** - * Description:<BR> - * <P> - * Initial Date: August 22, 2008 - * - * @author Udit Sajjanhar - */ -public class CalendarImportByUrlController extends BasicController { - - private VelocityContainer importVC; - private CalendarImportUrlForm importUrlForm; - private Link cancelButton; - private Panel panel; - - - CalendarImportByUrlController(UserRequest ureq, WindowControl wControl) { - super(ureq, wControl); - - setBasePackage(CalendarManager.class); - - importVC = createVelocityContainer("calImportByUrl"); - - importUrlForm = new CalendarImportUrlForm(ureq, wControl); - listenTo(importUrlForm); - - panel = new Panel("panel"); - panel.setContent(importUrlForm.getInitialComponent()); - - importVC.put("urlinput", panel); - cancelButton = LinkFactory.createButton("cancel", importVC, this); - putInitialPanel(importVC); - } - - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) - */ - public void event(UserRequest ureq, Component source, Event event) { - if (source == cancelButton) { - fireEvent(ureq, Event.CANCELLED_EVENT); - } - } - - public void event(UserRequest ureq, Controller source, Event event) { - if (source == importUrlForm) { - if (event == Event.DONE_EVENT) { - try { - String calendarContent = ImportCalendarManager.getContentFromUrl(importUrlForm.getCalendarUrl()); - processCalendarUrl(ureq, calendarContent); - } catch (IOException e) { - getWindowControl().setError(translate("cal.import.url.invalid")); - return; - } - } else if (event == Event.CANCELLED_EVENT) { - fireEvent(ureq, Event.CANCELLED_EVENT); - } - } - } - - private void processCalendarUrl(UserRequest ureq, String content) { - try { - // store the content of the url in a file by a temporary name - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - String calID = ImportCalendarManager.getTempCalendarIDForUpload(ureq); - File tmpFile = calManager.getCalendarFile(CalendarManager.TYPE_USER, calID); - BufferedWriter output = new BufferedWriter(new FileWriter(tmpFile)); - output.write(content); - output.close(); - - // try to parse the tmp file - Object calendar = calManager.readCalendar(CalendarManager.TYPE_USER, calID); - if (calendar != null) { - fireEvent(ureq, Event.DONE_EVENT); - } else { - getWindowControl().setError(translate("cal.import.url.content.invalid")); - } - } catch (IOException e){ - getWindowControl().setError(translate("cal.import.url.file.write.error")); - } catch (OLATRuntimeException e) { - getWindowControl().setError(translate("cal.import.url.content.invalid")); - } - } - - - /** - * - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ - protected void doDispose() { - // do nothing here yet - } - - public String getImportUrl() { - return importUrlForm.getCalendarUrl(); - } - -} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarImportNameForm.java b/src/main/java/org/olat/commons/calendar/ui/CalendarImportNameForm.java deleted file mode 100644 index 9be317798ac..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarImportNameForm.java +++ /dev/null @@ -1,116 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.ui; - - -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.ImportCalendarManager; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -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.FormLayoutContainer; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; - -public class CalendarImportNameForm extends FormBasicController { - - private TextElement calendarName; - - /** - * Display an event for modification or to add a new event. - * @param name - */ - public CalendarImportNameForm(UserRequest ureq, WindowControl wControl) { - super(ureq, wControl); - setBasePackage(CalendarManager.class); - - initForm (ureq); - } - - @Override - protected boolean validateFormLogic (UserRequest ureq) { - if (calendarName.isEmpty()) { - calendarName.setErrorKey("cal.import.calname.empty.error", null); - return false; - } else { - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - String calID = ImportCalendarManager.getImportedCalendarID(getIdentity(), calendarName.getValue()); - if (calManager.calendarExists(CalendarManager.TYPE_USER, calID)) { - calendarName.setErrorKey("cal.import.calname.exists.error", null); - return false; - } - } - return true; - } - - public String getCalendarName() { - return calendarName.getValue(); - } - - - @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 initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - // title - // addFormElement("cal.import.calname.title", new TitleElement("cal.import.calname.title")); - - // prompt for the calendar name - int identityLen = ureq.getIdentity().getName().length(); - - // 41=OresHelper.ORES_TYPE_LENGTH - 2 - 7 - // 2 because: 1 for the '_' which is added between identity and calendar name, - // and 1 for fuzzy counting which TextElement seems to do... - // 7 because: the CalendarManager.TYPE is prepended to the whole thing adding a _ - // and the max length of TYPE is 6 - hence 7 - // @see jira OLAT-4202 - - calendarName = uifactory.addTextElement("calname", "cal.import.calname.prompt", 41-identityLen, "", formLayout); - - FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); - formLayout.add(buttonLayout); - uifactory.addFormSubmitButton("submit", "cal.import.calname.submit", buttonLayout); - uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); - } - - - @Override - protected void doDispose() { - // - } -} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarImportUrlForm.java b/src/main/java/org/olat/commons/calendar/ui/CalendarImportUrlForm.java deleted file mode 100644 index c4728131f8d..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarImportUrlForm.java +++ /dev/null @@ -1,93 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.ui; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -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.FormLayoutContainer; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; - - - - - -public class CalendarImportUrlForm extends FormBasicController { - - private TextElement calendarUrl; - - - public CalendarImportUrlForm(UserRequest ureq, WindowControl wControl) { - super(ureq, wControl); - setBasePackage(CalendarManager.class); - initForm(ureq); - } - - @Override - protected boolean validateFormLogic (UserRequest ureq) { - if (calendarUrl.isEmpty()) { - calendarUrl.setErrorKey("cal.import.url.empty.error", null); - return false; - } - return true; - } - - public String getCalendarUrl() { - return calendarUrl.getValue(); - } - - - @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 initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - calendarUrl = uifactory.addTextElement("url", "cal.import.url.prompt", 200, "", formLayout); - - FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); - formLayout.add(buttonLayout); - uifactory.addFormSubmitButton("submit", "cal.form.submitSingle", buttonLayout); - uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); - } - - - @Override - protected void doDispose() { - // - } -} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarImportedRenderer.java b/src/main/java/org/olat/commons/calendar/ui/CalendarImportedRenderer.java new file mode 100644 index 00000000000..36d01e4f06a --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarImportedRenderer.java @@ -0,0 +1,56 @@ +/** + * <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.commons.calendar.ui; + +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.render.URLBuilder; +import org.olat.core.gui.translator.Translator; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarImportedRenderer implements FlexiCellRenderer { + + @Override + public void render(Renderer renderer, StringOutput target, Object cellValue, int row, + FlexiTableComponent source, URLBuilder ubu, Translator translator) { + if(cellValue instanceof KalendarRenderWrapper) { + KalendarRenderWrapper wrapper = (KalendarRenderWrapper)cellValue; + if(wrapper.isImported()) { + renderImported(target); + } + } else if(cellValue instanceof Boolean) { + if(((Boolean)cellValue).booleanValue()) { + renderImported(target); + } + } + } + + private void renderImported(StringOutput target) { + target.append(" <i class='o_icon o_icon_external_link'> </i>"); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationController.java new file mode 100644 index 00000000000..b4bdc509f10 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationController.java @@ -0,0 +1,440 @@ +/** + * <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.commons.calendar.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.RandomStringUtils; +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.manager.ImportCalendarManager; +import org.olat.commons.calendar.model.Kalendar; +import org.olat.commons.calendar.ui.CalendarPersonalConfigurationDataModel.ConfigCols; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.commons.calendar.ui.events.CalendarGUIEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIImportEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIRemoveEvent; +import org.olat.commons.calendar.ui.events.CalendarGUISettingEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Manage your calendars. + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarPersonalConfigurationController extends FormBasicController { + + private FlexiTableElement tableEl; + private CalendarPersonalConfigurationDataModel model; + + private FormLink importTypeFileButton; + private FormLink importTypeUrlButton; + + private CloseableModalController cmc; + private CalendarURLController feedUrlCtrl; + private DialogBoxController confirmRemoveTokenDialog; + private CalendarToolsController calendarToolsCtrl; + private CloseableCalloutWindowController calloutCtrl; + private DialogBoxController confirmDeleteCalendarDialog; + private ImportCalendarFileController calendarFileUploadCtrl; + private ImportCalendarByUrlController calendarUrlImportCtrl; + private InjectCalendarFileController injectCalendarFileCtrl; + private SynchronizedCalendarUrlController synchronizedCalendarUrlCtrl; + private CalendarColorChooserController colorChooserCtrl; + + private int counter; + private final boolean allowImport; + private List<KalendarRenderWrapper> calendars; + + @Autowired + private CalendarManager calendarManager; + @Autowired + private ImportCalendarManager importCalendarManager; + + public CalendarPersonalConfigurationController(UserRequest ureq, WindowControl wControl, + List<KalendarRenderWrapper> calendars, boolean allowImport) { + super(ureq, wControl, "configuration"); + this.calendars = calendars; + this.allowImport = allowImport; + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + if(allowImport) { + importTypeFileButton = uifactory.addFormLink("cal.import.type.file", formLayout, Link.BUTTON); + importTypeFileButton.setIconLeftCSS("o_icon o_icon_import"); + importTypeUrlButton = uifactory.addFormLink("cal.synchronize.type.url", formLayout, Link.BUTTON); + importTypeUrlButton.setIconLeftCSS("o_icon o_icon_calendar_sync"); + } + + //add the table + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigCols.type.i18nKey(), ConfigCols.type.ordinal(), + new CalendarTypeClassRenderer())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigCols.cssClass.i18nKey(), ConfigCols.cssClass.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigCols.name.i18nKey(), ConfigCols.name.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigCols.visible.i18nKey(), ConfigCols.visible.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigCols.aggregated.i18nKey(), ConfigCols.aggregated.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigCols.feed.i18nKey(), ConfigCols.feed.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigCols.tools.i18nKey(), ConfigCols.tools.ordinal())); + + model = new CalendarPersonalConfigurationDataModel(columnsModel); + List<CalendarPersonalConfigurationRow> rows = new ArrayList<>(calendars.size()); + for(KalendarRenderWrapper calendar:calendars) { + CalendarPersonalConfigurationRow row = new CalendarPersonalConfigurationRow(calendar); + initLinks(row); + rows.add(row); + } + model.setObjects(rows); + + tableEl = uifactory.addTableElement(getWindowControl(), "table", model, 20, false, getTranslator(), formLayout); + tableEl.setMultiSelect(false); + tableEl.setPageSize(50); + tableEl.setCustomizeColumns(false); + tableEl.setNumOfRowsEnabled(false); + } + + private void initLinks(CalendarPersonalConfigurationRow row) { + FormLink colorLink = uifactory.addFormLink("col_" + (++counter), "color", "", null, null, Link.NONTRANSLATED); + colorLink.setIconLeftCSS("o_cal_config_color ".concat(row.getCssClass())); + colorLink.setUserObject(row); + row.setColorLink(colorLink); + + FormLink visibleLink = uifactory.addFormLink("vis_" + (++counter), "visible", "", null, null, Link.NONTRANSLATED); + enableDisableIcons(visibleLink, row.isVisible()); + visibleLink.setUserObject(row); + row.setVisibleLink(visibleLink); + + FormLink aggregatedLink = uifactory.addFormLink("agg_" + (++counter), "aggregated", "", null, null, Link.NONTRANSLATED); + enableDisableIcons(aggregatedLink, row.isAggregated()); + aggregatedLink.setUserObject(row); + row.setAggregatedLink(aggregatedLink); + + FormLink feedLink = uifactory.addFormLink("fee_" + (++counter), "feed", "", null, null, Link.NONTRANSLATED); + feedLink.setIconLeftCSS("o_icon o_icon-lg o_icon_rss"); + feedLink.setUserObject(row); + row.setFeedLink(feedLink); + + FormLink toolsLink = uifactory.addFormLink("tools_" + (++counter), "tools", "", null, null, Link.NONTRANSLATED); + toolsLink.setIconLeftCSS("o_icon o_icon-lg o_icon_actions"); + toolsLink.setUserObject(row); + row.setToolsLink(toolsLink); + } + + private void enableDisableIcons(FormLink link, boolean enabled) { + link.setIconLeftCSS(enabled ? "o_icon o_icon-lg o_icon_calendar_enabled" : "o_icon o_icon-lg o_icon_calendar_disabled"); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(calloutCtrl == source || cmc == source) { + cleanUp(); + } else if (source == confirmRemoveTokenDialog ) { + if (DialogBoxUIFactory.isOkEvent(event)) { + doDeleteToken((CalendarPersonalConfigurationRow)confirmRemoveTokenDialog.getUserObject()); + showInfo("cal.icalfeed.remove.info"); + fireEvent(ureq, Event.CHANGED_EVENT); + } + } else if (source == confirmDeleteCalendarDialog ) { + if (DialogBoxUIFactory.isOkEvent(event)) { + doDeleteCalendar(ureq, (CalendarPersonalConfigurationRow)confirmDeleteCalendarDialog.getUserObject()); + showInfo("cal.import.remove.info"); + fireEvent(ureq, Event.CHANGED_EVENT); + } + } else if(calendarFileUploadCtrl == source) { + KalendarRenderWrapper calendar = calendarFileUploadCtrl.getImportedCalendar(); + cmc.deactivate(); + cleanUp(); + if(event == Event.DONE_EVENT) { + doImportCalendar(calendar); + fireEvent(ureq, new CalendarGUIImportEvent(calendar)); + } + } else if(calendarUrlImportCtrl == source) { + KalendarRenderWrapper calendar = calendarUrlImportCtrl.getImportedCalendar(); + cmc.deactivate(); + cleanUp(); + + if(event == Event.DONE_EVENT) { + doImportCalendar(calendar); + } + } else if(calendarToolsCtrl == source) { + CalendarPersonalConfigurationRow row = calendarToolsCtrl.getRow(); + calloutCtrl.deactivate(); + cleanUp(); + + if(CalendarGUIEvent.IMPORT_BY_FILE.equals(event.getCommand())) { + doOpenInjectCalendarFile(ureq, row); + } else if(CalendarGUIEvent.IMPORT_SYNCHRONIZED_URL.equals(event.getCommand())) { + doOpenSynchronizedCalendarUrl(ureq, row); + } else if(CalendarGUIEvent.DELETE_TOKEN.equals(event.getCommand())) { + doConfirmDeleteToken(ureq, row); + } else if(CalendarGUIEvent.DELETE_CALENDAR.equals(event.getCommand())) { + doConfirmDeleteCalendar(ureq, row); + } + + } else if(injectCalendarFileCtrl == source || synchronizedCalendarUrlCtrl == source) { + cmc.deactivate(); + cleanUp(); + } else if(colorChooserCtrl == source) { + if(event == Event.DONE_EVENT) { + doSetColor(ureq, colorChooserCtrl.getRow(), colorChooserCtrl.getChoosenColor()); + } + calloutCtrl.deactivate(); + cleanUp(); + } + super.event(ureq, source, event); + } + + private void cleanUp() { + removeAsListenerAndDispose(calendarFileUploadCtrl); + removeAsListenerAndDispose(calendarUrlImportCtrl); + removeAsListenerAndDispose(calendarToolsCtrl); + removeAsListenerAndDispose(colorChooserCtrl); + removeAsListenerAndDispose(calloutCtrl); + removeAsListenerAndDispose(feedUrlCtrl); + removeAsListenerAndDispose(cmc); + calendarFileUploadCtrl = null; + calendarUrlImportCtrl = null; + calendarToolsCtrl = null; + colorChooserCtrl = null; + calloutCtrl = null; + feedUrlCtrl = null; + cmc = null; + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source instanceof FormLink) { + if(importTypeFileButton == source) { + doOpenImportCalendarFile(ureq); + } else if(importTypeUrlButton == source) { + doOpenImportCalendarUrl(ureq); + } else { + FormLink link = (FormLink)source; + String cmd = link.getCmd(); + if("visible".equals(cmd)) { + doToogleVisibility(ureq, (CalendarPersonalConfigurationRow)link.getUserObject()); + } else if("aggregated".equals(cmd)) { + doToogleAggregated(ureq, (CalendarPersonalConfigurationRow)link.getUserObject()); + } else if("feed".equals(cmd)) { + doShowFeedURL(ureq, link, (CalendarPersonalConfigurationRow)link.getUserObject()); + } else if("tools".equals(cmd)) { + doTools(ureq, link, (CalendarPersonalConfigurationRow)link.getUserObject()); + } else if("color".equals(cmd)) { + doChooseColor(ureq, link, (CalendarPersonalConfigurationRow)link.getUserObject()); + } + } + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void propagateDirtinessToContainer(FormItem fiSrc) { + // + } + + private void doToogleVisibility(UserRequest ureq, CalendarPersonalConfigurationRow row) { + KalendarRenderWrapper calendarWrapper = row.getWrapper(); + calendarWrapper.setVisible(!calendarWrapper.isVisible()); + calendarManager.saveCalendarConfigForIdentity(calendarWrapper, getIdentity()); + enableDisableIcons(row.getVisibleLink(), calendarWrapper.isVisible()); + fireEvent(ureq, new CalendarGUISettingEvent(calendarWrapper)); + } + + private void doToogleAggregated(UserRequest ureq, CalendarPersonalConfigurationRow row) { + KalendarRenderWrapper calendarWrapper = row.getWrapper(); + calendarWrapper.setInAggregatedFeed(!calendarWrapper.isInAggregatedFeed()); + calendarManager.saveCalendarConfigForIdentity(calendarWrapper, getIdentity()); + enableDisableIcons(row.getAggregatedLink(), calendarWrapper.isInAggregatedFeed()); + fireEvent(ureq, Event.CHANGED_EVENT); + } + + private void doShowFeedURL(UserRequest ureq, FormLink link, CalendarPersonalConfigurationRow row) { + removeAsListenerAndDispose(feedUrlCtrl); + removeAsListenerAndDispose(calloutCtrl); + + KalendarRenderWrapper calendarWrapper = row.getWrapper(); + if(!StringHelper.containsNonWhitespace(row.getToken())) { + calendarWrapper.setToken(RandomStringUtils.randomAlphanumeric(6)); + calendarManager.saveCalendarConfigForIdentity(calendarWrapper, getIdentity()); + } + + String calFeedLink = row.getFeedUrl(getIdentity()); + feedUrlCtrl = new CalendarURLController(ureq, getWindowControl(), calFeedLink); + listenTo(feedUrlCtrl); + + calloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), + feedUrlCtrl.getInitialComponent(), link.getFormDispatchId(), "", true, ""); + listenTo(calloutCtrl); + calloutCtrl.activate(); + } + + private void doConfirmDeleteToken(UserRequest ureq, CalendarPersonalConfigurationRow row) { + String title = translate("cal.icalfeed.remove.title"); + String msg = translate("cal.icalfeed.remove.confirmation_message"); + confirmRemoveTokenDialog = activateOkCancelDialog(ureq, title, msg, confirmRemoveTokenDialog); + confirmRemoveTokenDialog.setUserObject(row); + } + + private void doDeleteToken(CalendarPersonalConfigurationRow row) { + KalendarRenderWrapper calendarWrapper = row.getWrapper(); + calendarWrapper.setToken(null); + calendarManager.saveCalendarConfigForIdentity(calendarWrapper, getIdentity()); + tableEl.reloadData(); + } + + private void doTools(UserRequest ureq, FormLink link, CalendarPersonalConfigurationRow row) { + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(calendarToolsCtrl); + + calendarToolsCtrl = new CalendarToolsController(ureq, getWindowControl(), row); + listenTo(calendarToolsCtrl); + + calloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), + calendarToolsCtrl.getInitialComponent(), link.getFormDispatchId(), "", true, ""); + listenTo(calloutCtrl); + calloutCtrl.activate(); + } + + private void doConfirmDeleteCalendar(UserRequest ureq, CalendarPersonalConfigurationRow row) { + String title = translate("cal.import.remove.title"); + String msg = translate("cal.import.remove.confirmation_message"); + confirmDeleteCalendarDialog = activateOkCancelDialog(ureq, title, msg, confirmDeleteCalendarDialog); + confirmDeleteCalendarDialog.setUserObject(row); + } + + private void doDeleteCalendar(UserRequest ureq, CalendarPersonalConfigurationRow row) { + if(!row.isImported()) return; + + Kalendar calendar = row.getWrapper().getKalendar(); + importCalendarManager.deleteCalendar(getIdentity(), calendar); + + List<CalendarPersonalConfigurationRow> currentRows = model.getObjects(); + currentRows.remove(row); + model.setObjects(currentRows); + tableEl.reloadData(); + fireEvent(ureq, new CalendarGUIRemoveEvent(row.getWrapper())); + } + + private void doOpenImportCalendarFile(UserRequest ureq) { + calendarFileUploadCtrl = new ImportCalendarFileController(ureq, getWindowControl()); + listenTo(calendarFileUploadCtrl); + + String title = translate("cal.import.type.file"); + cmc = new CloseableModalController(getWindowControl(), translate("close"), calendarFileUploadCtrl.getInitialComponent(), true, title); + cmc.activate(); + listenTo(cmc); + } + + private void doOpenImportCalendarUrl(UserRequest ureq) { + calendarUrlImportCtrl = new ImportCalendarByUrlController(ureq, getWindowControl()); + listenTo(calendarUrlImportCtrl); + + String title = translate("cal.synchronize.type.url"); + cmc = new CloseableModalController(getWindowControl(), translate("close"), calendarUrlImportCtrl.getInitialComponent(), true, title); + cmc.activate(); + listenTo(cmc); + } + + private void doImportCalendar(KalendarRenderWrapper wrapper) { + CalendarPersonalConfigurationRow row = new CalendarPersonalConfigurationRow(wrapper); + initLinks(row); + List<CalendarPersonalConfigurationRow> rows = model.getObjects(); + rows.add(row); + model.setObjects(rows); + tableEl.reloadData(); + } + + private void doOpenInjectCalendarFile(UserRequest ureq, CalendarPersonalConfigurationRow row) { + injectCalendarFileCtrl = new InjectCalendarFileController(ureq, getWindowControl(), row); + listenTo(injectCalendarFileCtrl); + + String title = translate("cal.import.type.file"); + cmc = new CloseableModalController(getWindowControl(), translate("close"), injectCalendarFileCtrl.getInitialComponent(), true, title); + cmc.activate(); + listenTo(cmc); + } + + private void doOpenSynchronizedCalendarUrl(UserRequest ureq, CalendarPersonalConfigurationRow row) { + synchronizedCalendarUrlCtrl = new SynchronizedCalendarUrlController(ureq, getWindowControl(), row); + listenTo(synchronizedCalendarUrlCtrl); + + String title = translate("cal.synchronize.type.url"); + cmc = new CloseableModalController(getWindowControl(), translate("close"), synchronizedCalendarUrlCtrl.getInitialComponent(), true, title); + cmc.activate(); + listenTo(cmc); + } + + private void doChooseColor(UserRequest ureq, FormLink link, CalendarPersonalConfigurationRow row) { + removeAsListenerAndDispose(calloutCtrl); + removeAsListenerAndDispose(colorChooserCtrl); + + colorChooserCtrl = new CalendarColorChooserController(ureq, getWindowControl(), row); + listenTo(colorChooserCtrl); + + calloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), + colorChooserCtrl.getInitialComponent(), link.getFormDispatchId(), "", true, ""); + listenTo(calloutCtrl); + calloutCtrl.activate(); + } + + private void doSetColor(UserRequest ureq, CalendarPersonalConfigurationRow row, String cssColor) { + KalendarRenderWrapper calendarWrapper = row.getWrapper(); + calendarWrapper.setCssClass(cssColor); + calendarManager.saveCalendarConfigForIdentity(calendarWrapper, getIdentity()); + row.getColorLink().setIconLeftCSS("o_cal_config_color ".concat(row.getCssClass())); + fireEvent(ureq, new CalendarGUISettingEvent(calendarWrapper)); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationDataModel.java b/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationDataModel.java new file mode 100644 index 00000000000..4b4be0e06c5 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationDataModel.java @@ -0,0 +1,76 @@ +/** + * <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.commons.calendar.ui; + +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarPersonalConfigurationDataModel extends DefaultFlexiTableDataModel<CalendarPersonalConfigurationRow> { + + public CalendarPersonalConfigurationDataModel(FlexiTableColumnModel columnModel) { + super(columnModel); + } + + @Override + public DefaultFlexiTableDataModel<CalendarPersonalConfigurationRow> createCopyWithEmptyList() { + return new CalendarPersonalConfigurationDataModel(getTableColumnModel()); + } + + @Override + public Object getValueAt(int row, int col) { + CalendarPersonalConfigurationRow configRow = getObject(row); + switch(ConfigCols.values()[col]) { + case type: return configRow.getWrapper(); + case name: return configRow.getDisplayName(); + case cssClass: return configRow.getColorLink();//.getCssClass(); + case visible: return configRow.getVisibleLink(); + case aggregated: return configRow.getAggregatedLink(); + case feed: return configRow.getFeedLink(); + case tools: return configRow.getToolsLink(); + } + return null; + } + + public enum ConfigCols { + type("table.header.type"), + name("table.header.name"), + cssClass("table.header.color"), + visible("table.header.visible"), + aggregated("table.header.aggregated.feed"), + feed("table.header.url"), + tools("table.header.tools"); + + private final String i18nKey; + + private ConfigCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + public String i18nKey() { + return i18nKey; + } + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationRow.java b/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationRow.java new file mode 100644 index 00000000000..ab97330b3b6 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarPersonalConfigurationRow.java @@ -0,0 +1,131 @@ +/** + * <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.commons.calendar.ui; + +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.id.Identity; + +/** + * Needed for the personal configuratio. + * + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarPersonalConfigurationRow { + + private final KalendarRenderWrapper wrapper; + + private FormLink colorLink; + private FormLink visibleLink; + private FormLink aggregatedLink; + private FormLink feedLink; + private FormLink toolsLink; + + public CalendarPersonalConfigurationRow(KalendarRenderWrapper wrapper) { + this.wrapper = wrapper; + } + + public KalendarRenderWrapper getWrapper() { + return wrapper; + } + + public int getAccess() { + return wrapper.getAccess(); + } + + public String getCalendarType() { + return wrapper.getKalendar().getType(); + } + + public String getCalendarId() { + return wrapper.getKalendar().getCalendarID(); + } + + public String getDisplayName() { + return wrapper.getDisplayName(); + } + + public String getCssClass() { + return wrapper.getCssClass(); + } + + public boolean isVisible() { + return wrapper.isVisible(); + } + + public boolean isAggregated() { + return wrapper.isInAggregatedFeed(); + } + + public boolean isImported() { + return wrapper.isImported(); + } + + public String getToken() { + return wrapper.getToken(); + } + + public String getFeedUrl(Identity identity) { + return wrapper.getFeedUrl(identity); + } + + public FormLink getColorLink() { + return colorLink; + } + + public void setColorLink(FormLink colorLink) { + this.colorLink = colorLink; + } + + public FormLink getVisibleLink() { + return visibleLink; + } + + public void setVisibleLink(FormLink visibleLink) { + this.visibleLink = visibleLink; + } + + public FormLink getAggregatedLink() { + return aggregatedLink; + } + + public void setAggregatedLink(FormLink aggregatedLink) { + this.aggregatedLink = aggregatedLink; + } + + public FormLink getFeedLink() { + return feedLink; + } + + public void setFeedLink(FormLink feedLink) { + this.feedLink = feedLink; + } + + public FormLink getToolsLink() { + return toolsLink; + } + + public void setToolsLink(FormLink toolsLink) { + this.toolsLink = toolsLink; + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarPrintController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarPrintController.java index e3429ad484a..824bcb4ba7d 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarPrintController.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarPrintController.java @@ -24,7 +24,7 @@ import java.util.Date; import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.CalendarUtils; -import org.olat.commons.calendar.ui.events.KalendarGUIPrintEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIPrintEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -81,7 +81,7 @@ public class CalendarPrintController extends FormBasicController { if(source == printButton) { Date from = fromEl.getDate(); Date to = toEl.getDate(); - fireEvent(ureq, new KalendarGUIPrintEvent(from, to)); + fireEvent(ureq, new CalendarGUIPrintEvent(from, to)); } super.formInnerEvent(ureq, source, event); } diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarPrintMapper.java b/src/main/java/org/olat/commons/calendar/ui/CalendarPrintMapper.java index 3eb33a30163..3eb11bcd2f0 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarPrintMapper.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarPrintMapper.java @@ -52,7 +52,6 @@ public class CalendarPrintMapper implements Mapper { private Date from, to; private List<KalendarRenderWrapper> calendarWrappers; - private List<KalendarRenderWrapper> importedCalendarWrappers; public CalendarPrintMapper(String themeBaseUri, Translator translator) { this.themeBaseUri = themeBaseUri; @@ -83,14 +82,6 @@ public class CalendarPrintMapper implements Mapper { this.calendarWrappers = calendarWrappers; } - public List<KalendarRenderWrapper> getImportedCalendarWrappers() { - return importedCalendarWrappers; - } - - public void setImportedCalendarWrappers(List<KalendarRenderWrapper> importedCalendarWrappers) { - this.importedCalendarWrappers = importedCalendarWrappers; - } - @Override public MediaResource handle(String relPath, HttpServletRequest request) { StringBuilder sb = new StringBuilder(); @@ -105,7 +96,6 @@ public class CalendarPrintMapper implements Mapper { //collect all events List<KalendarEventRenderWrapper> sortedEventsWithin = new ArrayList<KalendarEventRenderWrapper>(); collectEvents(sortedEventsWithin, calendarWrappers); - collectEvents(sortedEventsWithin, importedCalendarWrappers); Collections.sort(sortedEventsWithin, KalendarEventDateComparator.getInstance()); //list of events @@ -123,7 +113,7 @@ public class CalendarPrintMapper implements Mapper { private void collectEvents(List<KalendarEventRenderWrapper> eventList, List<KalendarRenderWrapper> wrappers) { for (KalendarRenderWrapper calendarWrapper:wrappers) { - if (calendarWrapper.getKalendarConfig().isVis()) { + if (calendarWrapper.isVisible()) { List<KalendarEvent> events = CalendarUtils.listEventsForPeriod(calendarWrapper.getKalendar(), from, to); for (KalendarEvent event : events) { //private filter??? @@ -266,21 +256,23 @@ public class CalendarPrintMapper implements Mapper { private void renderCalendars(StringBuilder sb) { sb.append("<div id='o_cal_config'>") .append("<fieldset><legend>").append(translator.translate("cal.list")).append("</legend>"); - renderCalendar(sb, calendarWrappers); + renderCalendar(sb, calendarWrappers, false); sb.append("</fieldset>"); //list of imported calendars sb.append("<fieldset><legend>").append(translator.translate("cal.import.list")).append("</legend>"); - renderCalendar(sb, importedCalendarWrappers); + renderCalendar(sb, calendarWrappers, true); sb.append("</fieldset>") .append("</div>"); } - private void renderCalendar(StringBuilder sb, List<KalendarRenderWrapper> calendarWrapperList) { + private void renderCalendar(StringBuilder sb, List<KalendarRenderWrapper> calendarWrapperList, boolean imported) { for(KalendarRenderWrapper calendarWrapper:calendarWrapperList) { - String cssClass = calendarWrapper.getKalendarConfig().getCss(); - sb.append("<div class='o_cal_config_row'><div class='o_cal_config_calendar ").append(cssClass).append("'>") - .append(StringHelper.escapeHtml(calendarWrapper.getKalendarConfig().getDisplayName())) - .append("</div></div>"); + if(calendarWrapper.isImported() == imported) { + String cssClass = calendarWrapper.getCssClass(); + sb.append("<div class='o_cal_config_row'><div class='o_cal_config_calendar ").append(cssClass).append("'>") + .append(StringHelper.escapeHtml(calendarWrapper.getDisplayName())) + .append("</div></div>"); + } } } } \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarToolsController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarToolsController.java new file mode 100644 index 00000000000..19d2149b6cc --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarToolsController.java @@ -0,0 +1,93 @@ +/** + * <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.commons.calendar.ui; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.ui.events.CalendarGUIEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarToolsController extends BasicController { + + private Link injectFileLink, injectSynchronizedUrlLink, deleteTokenLink, deleteCalendarLink; + + private final CalendarPersonalConfigurationRow row; + + public CalendarToolsController(UserRequest ureq, WindowControl wControl, CalendarPersonalConfigurationRow row) { + super(ureq, wControl); + this.row = row; + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); + + VelocityContainer mainVC = createVelocityContainer("tools"); + if(row.getAccess() == 0 && !row.isImported()) { + injectFileLink = LinkFactory.createLink("cal.import.type.file", mainVC, this); + injectFileLink.setIconLeftCSS("o_icon o_icon_import"); + injectSynchronizedUrlLink = LinkFactory.createLink("cal.synchronize.type.url", mainVC, this); + injectSynchronizedUrlLink.setIconLeftCSS("o_icon o_icon_calendar_sync"); + } + + if(StringHelper.containsNonWhitespace(row.getToken())) { + deleteTokenLink = LinkFactory.createLink("cal.icalfeed.subscribe.remove", mainVC, this); + deleteTokenLink.setIconLeftCSS("o_icon o_icon_delete"); + } + if(row.isImported()) { + deleteCalendarLink = LinkFactory.createLink("cal.delete.imported.calendar", mainVC, this); + deleteCalendarLink.setIconLeftCSS("o_icon o_icon_remove"); + } + + putInitialPanel(mainVC); + } + + public CalendarPersonalConfigurationRow getRow() { + return row; + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(injectFileLink == source) { + fireEvent(ureq, new CalendarGUIEvent(CalendarGUIEvent.IMPORT_BY_FILE)); + } else if(injectSynchronizedUrlLink == source) { + fireEvent(ureq, new CalendarGUIEvent(CalendarGUIEvent.IMPORT_SYNCHRONIZED_URL)); + } else if(deleteTokenLink == source) { + fireEvent(ureq, new CalendarGUIEvent(CalendarGUIEvent.DELETE_TOKEN)); + } else if(deleteCalendarLink == source) { + fireEvent(ureq, new CalendarGUIEvent(CalendarGUIEvent.DELETE_CALENDAR)); + } + } + + @Override + protected void doDispose() { + // + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarTypeClassRenderer.java b/src/main/java/org/olat/commons/calendar/ui/CalendarTypeClassRenderer.java new file mode 100644 index 00000000000..c604c4e9957 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarTypeClassRenderer.java @@ -0,0 +1,69 @@ +/** + * <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.commons.calendar.ui; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.render.URLBuilder; +import org.olat.core.gui.translator.Translator; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarTypeClassRenderer implements FlexiCellRenderer { + + @Override + public void render(Renderer renderer, StringOutput target, Object cellValue, int row, + FlexiTableComponent source, URLBuilder ubu, Translator translator) { + if(cellValue instanceof KalendarRenderWrapper) { + KalendarRenderWrapper wrapper = (KalendarRenderWrapper)cellValue; + if(wrapper.isImported()) { + renderImported(target); + } else { + renderType(target, wrapper.getKalendar().getType()); + } + } else if(cellValue instanceof String) { + renderType(target, (String)cellValue); + } + } + + private void renderImported(StringOutput target) { + target.append(" <i class='o_icon o_icon_external_link'> </i>"); + } + + private void renderType(StringOutput target, String type) { + if(CalendarManager.TYPE_USER.equals(type)) { + target.append("<i class='o_icon o_icon_user'> </i>"); + } else if(CalendarManager.TYPE_GROUP.equals(type)) { + target.append("<i class='o_icon o_icon_group'> </i>"); + } else if(CalendarManager.TYPE_COURSE.equals(type)) { + target.append("<i class='o_icon o_CourseModule_icon'> </i>"); + } else { + target.append("<i class='o_cal_config_color ").append(type).append("'> </i>"); + } + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarURLController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarURLController.java new file mode 100644 index 00000000000..807ca9cc537 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarURLController.java @@ -0,0 +1,55 @@ +/** + * <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.commons.calendar.ui; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.util.Util; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarURLController extends BasicController { + + public CalendarURLController(UserRequest ureq, WindowControl wControl, String icalFeedLink) { + super(ureq, wControl); + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); + + VelocityContainer colorVC = createVelocityContainer("calIcalFeed"); + colorVC.contextPut("icalFeedLink", icalFeedLink); + putInitialPanel(colorVC); + } + + public void event(UserRequest ureq, Component source, Event event) { + // + } + + protected void doDispose() { + // nothing to dispose + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/CopyEventToCalendarController.java b/src/main/java/org/olat/commons/calendar/ui/CopyEventToCalendarController.java index 285edbe96b9..13f820bcc00 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CopyEventToCalendarController.java +++ b/src/main/java/org/olat/commons/calendar/ui/CopyEventToCalendarController.java @@ -30,7 +30,6 @@ import java.util.Collection; import java.util.List; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.model.KalendarEventLink; @@ -44,6 +43,7 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.Util; import org.olat.core.util.xml.XStreamHelper; +import org.springframework.beans.factory.annotation.Autowired; public class CopyEventToCalendarController extends FormBasicController { @@ -52,6 +52,9 @@ public class CopyEventToCalendarController extends FormBasicController { private final KalendarEvent kalendarEvent; private final Collection<KalendarRenderWrapper> calendars; private List<MultipleSelectionElement> copyEls; + + @Autowired + private CalendarManager calendarManager; public CopyEventToCalendarController(UserRequest ureq, WindowControl wControl, KalendarEvent kalendarEvent, Collection<KalendarRenderWrapper> calendars) { @@ -71,7 +74,7 @@ public class CopyEventToCalendarController extends FormBasicController { copyEls = new ArrayList<>(calendars.size()); for (KalendarRenderWrapper calendarWrapper : calendars) { String calId = calendarWrapper.getKalendar().getCalendarID(); - String value = calendarWrapper.getKalendarConfig().getDisplayName(); + String value = calendarWrapper.getDisplayName(); MultipleSelectionElement copyEl = uifactory .addCheckboxesHorizontal("cal_" + calId, null, formLayout, copy, new String[]{ value }); copyEl.setUserObject(calendarWrapper); @@ -92,7 +95,6 @@ public class CopyEventToCalendarController extends FormBasicController { @Override protected void formOK(UserRequest ureq) { - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); for (MultipleSelectionElement copyEl : copyEls) { if(copyEl.isEnabled() && copyEl.isAtLeastSelected(1)) { KalendarRenderWrapper calendarWrapper = (KalendarRenderWrapper)copyEl.getUserObject(); diff --git a/src/main/java/org/olat/commons/calendar/ui/ExternalLinksController.java b/src/main/java/org/olat/commons/calendar/ui/ExternalLinksController.java index d5909540f1f..35516978c53 100644 --- a/src/main/java/org/olat/commons/calendar/ui/ExternalLinksController.java +++ b/src/main/java/org/olat/commons/calendar/ui/ExternalLinksController.java @@ -65,7 +65,7 @@ public class ExternalLinksController extends FormBasicController { public static final String EXTERNAL_LINKS_PROVIDER = "external-links"; - private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); + private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(ExternalLinksController.class); private FormLink newButton; private final KalendarEvent kalendarEvent; diff --git a/src/main/java/org/olat/commons/calendar/ui/ImportCalendarByUrlController.java b/src/main/java/org/olat/commons/calendar/ui/ImportCalendarByUrlController.java new file mode 100644 index 00000000000..4341f1896d3 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/ImportCalendarByUrlController.java @@ -0,0 +1,132 @@ +/** + * <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.commons.calendar.ui; + +import java.io.IOException; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.manager.ImportCalendarManager; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +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.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.logging.OLATRuntimeException; +import org.olat.core.util.Util; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ImportCalendarByUrlController extends FormBasicController { + + private TextElement calendarUrl; + private TextElement calendarName; + + private KalendarRenderWrapper importedCalendar; + + @Autowired + private CalendarManager calendarManager; + @Autowired + private ImportCalendarManager importCalendarManager; + + public ImportCalendarByUrlController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); + + initForm(ureq); + } + + public KalendarRenderWrapper getImportedCalendar() { + return importedCalendar; + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormDescription("cal.synchronize.personal.type.url.desc"); + + int identityLen = ureq.getIdentity().getName().length(); + calendarName = uifactory.addTextElement("calname", "cal.import.calname.prompt", 41-identityLen, "", formLayout); + calendarUrl = uifactory.addTextElement("url", "cal.import.url.prompt", 200, "", formLayout); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); + formLayout.add(buttonLayout); + uifactory.addFormSubmitButton("submit", "cal.form.submitSingle", buttonLayout); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + } + + @Override + protected void doDispose() { + // do nothing here yet + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + + calendarName.clearError(); + if (calendarName.isEmpty()) { + calendarName.setErrorKey("cal.import.calname.empty.error", null); + allOk = false; + } else { + String calID = ImportCalendarManager.getImportedCalendarID(getIdentity(), calendarName.getValue()); + if (calendarManager.calendarExists(CalendarManager.TYPE_USER, calID)) { + calendarName.setErrorKey("cal.import.calname.exists.error", null); + allOk &= false; + } + } + + calendarUrl.clearError(); + if (calendarUrl.isEmpty()) { + calendarUrl.setErrorKey("cal.import.url.empty.error", null); + allOk &= false; + } + + return allOk & super.validateFormLogic(ureq); + } + + @Override + protected void formOK(UserRequest ureq) { + String name = calendarName.getValue(); + String url = calendarUrl.getValue(); + + try { + importedCalendar = importCalendarManager.importCalendar(getIdentity(), name, CalendarManager.TYPE_USER, url); + fireEvent(ureq, Event.DONE_EVENT); + } catch (IOException e) { + showError("cal.import.url.file.write.error"); + logError("", e); + } catch (OLATRuntimeException e) { + showError("cal.import.url.content.invalid"); + } + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/ImportCalendarController.java b/src/main/java/org/olat/commons/calendar/ui/ImportCalendarController.java deleted file mode 100644 index 49fa25712fd..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/ImportCalendarController.java +++ /dev/null @@ -1,197 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.ui; - -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.ImportCalendarManager; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItem; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.FileElement; -import org.olat.core.gui.components.form.flexible.elements.FormLink; -import org.olat.core.gui.components.form.flexible.elements.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.components.form.flexible.impl.elements.FormSubmit; -import org.olat.core.gui.components.link.Link; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.util.FileUtils; -import org.olat.core.util.StringHelper; -import org.olat.core.util.Util; - -/** - * - * <h3>Description:</h3> - * <p> - * Initial Date: 4 feb. 2011 <br> - * @author srosse, stephane.rosse@frentix.com, www.frentix.com - */ -public class ImportCalendarController extends FormBasicController { - - private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); - - private FormLink importTypeUrlButton; - private FormLink importTypeFileButton; - private FormSubmit importButton; - private TextElement importUrl; - private FileElement importFile; - private final KalendarRenderWrapper calendarWrapper; - - private FormLayoutContainer urlLayout; - private FormLayoutContainer fileLayout; - private FormLayoutContainer chooseLayout; - - public ImportCalendarController(UserRequest ureq, WindowControl wControl, KalendarRenderWrapper calendarWrapper) { - super(ureq, wControl, "importEvents"); - this.calendarWrapper = calendarWrapper; - - initForm(ureq); - } - - @Override - protected void constructorInit(String id, String pageName) { - velocity_root = VELOCITY_ROOT; - super.constructorInit(id, pageName); - setBasePackage(CalendarManager.class); - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - //choose panel - chooseLayout = FormLayoutContainer.createVerticalFormLayout("chooseContainer", getTranslator()); - chooseLayout.setRootForm(mainForm); - uifactory.addStaticTextElement("cal.import.type.file.desc", "", chooseLayout); - importTypeFileButton = uifactory.addFormLink("cal.import.type.file", chooseLayout, Link.BUTTON); - uifactory.addSpacerElement("choose-spacer", chooseLayout, false); - uifactory.addStaticTextElement("cal.import.type.url.desc", "", chooseLayout); - importTypeUrlButton = uifactory.addFormLink("cal.import.type.url", chooseLayout, Link.BUTTON); - chooseLayout.setVisible(true); - formLayout.add("chooseContainer", chooseLayout); - - //url panel - urlLayout = FormLayoutContainer.createDefaultFormLayout("urlContainer", getTranslator()); - urlLayout.setRootForm(mainForm); - importUrl = uifactory.addTextElement("cal.import.url.prompt", "cal.import.url.prompt", 200, "", urlLayout); - urlLayout.setVisible(false); - formLayout.add("urlContainer", urlLayout); - - //file panel - fileLayout = FormLayoutContainer.createDefaultFormLayout("fileContainer", getTranslator()); - fileLayout.setRootForm(mainForm); - importFile = uifactory.addFileElement("cal.import.form.prompt", "cal.import.form.prompt", fileLayout); - fileLayout.setVisible(false); - formLayout.add("fileContainer", fileLayout); - - //standard cancel panel - FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); - buttonGroupLayout.setRootForm(mainForm); - formLayout.add("buttonGroupLayout", buttonGroupLayout); - importButton = uifactory.addFormSubmitButton("ok", buttonGroupLayout); - importButton.setVisible(false); - uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl()); - } - - /** - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ - protected void doDispose() { - // - } - - @Override - protected boolean validateFormLogic(UserRequest ureq) { - boolean allOk = true; - importUrl.clearError(); - if(urlLayout.isVisible()) { - String url = importUrl.getValue(); - if(StringHelper.containsNonWhitespace(url)) { - try { - String host = new URL(url).getHost(); - if(host == null) { - importUrl.setErrorKey("cal.import.url.invalid", null); - } - } catch (MalformedURLException e) { - importUrl.setErrorKey("cal.import.url.invalid", null); - allOk &= false; - } - } else { - importUrl.setErrorKey("cal.import.url.empty.error", null); - allOk &= false; - } - } - return allOk && super.validateFormLogic(ureq); - } - - @Override - protected void formOK(UserRequest ureq) { - if(urlLayout.isVisible()) { - String url = importUrl.getValue(); - if(ImportCalendarManager.importCalendarIn(calendarWrapper.getKalendar(), url)) { - showInfo("cal.import.success"); - fireEvent(ureq, Event.DONE_EVENT); - } else { - showError("cal.import.url.content.invalid"); - } - } else if(fileLayout.isVisible()) { - InputStream in = importFile.getUploadInputStream(); - if(ImportCalendarManager.importCalendarIn(calendarWrapper, in)) { - showInfo("cal.import.success"); - fireEvent(ureq, Event.DONE_EVENT); - } else { - showError("cal.import.form.format.error"); - } - FileUtils.closeSafely(in); - } - } - - @Override - protected void formCancelled(UserRequest ureq) { - fireEvent(ureq, Event.CANCELLED_EVENT); - } - - @Override - protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if(source == importTypeFileButton) { - chooseLayout.setVisible(false); - fileLayout.setVisible(true); - importButton.setVisible(true); - } else if (source == importTypeUrlButton) { - chooseLayout.setVisible(false); - urlLayout.setVisible(true); - importButton.setVisible(true); - } - super.formInnerEvent(ureq, source, event); - } -} diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarFileUploadController.java b/src/main/java/org/olat/commons/calendar/ui/ImportCalendarFileController.java similarity index 64% rename from src/main/java/org/olat/commons/calendar/ui/CalendarFileUploadController.java rename to src/main/java/org/olat/commons/calendar/ui/ImportCalendarFileController.java index 6e4401c4cee..9c1149d9b55 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarFileUploadController.java +++ b/src/main/java/org/olat/commons/calendar/ui/ImportCalendarFileController.java @@ -27,20 +27,22 @@ package org.olat.commons.calendar.ui; import java.io.File; +import java.io.IOException; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.ImportCalendarManager; +import org.olat.commons.calendar.manager.ImportCalendarManager; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.FileElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; 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.FileUtils; import org.olat.core.util.Util; +import org.springframework.beans.factory.annotation.Autowired; /** * Description:<BR> @@ -49,18 +51,32 @@ import org.olat.core.util.Util; * * @author Udit Sajjanhar */ -public class CalendarFileUploadController extends FormBasicController { +public class ImportCalendarFileController extends FormBasicController { private FileElement uploadEl; + private TextElement calendarName; - CalendarFileUploadController(UserRequest ureq, WindowControl wControl) { + private KalendarRenderWrapper importedCalendar; + + @Autowired + private CalendarManager calendarManager; + @Autowired + private ImportCalendarManager importCalendarManager; + + public ImportCalendarFileController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); setTranslator(Util.createPackageTranslator(CalendarManager.class, ureq.getLocale(), getTranslator())); initForm(ureq); } + + public KalendarRenderWrapper getImportedCalendar() { + return importedCalendar; + } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + int identityLen = ureq.getIdentity().getName().length(); + calendarName = uifactory.addTextElement("calname", "cal.import.calname.prompt", 41-identityLen, "", formLayout); uploadEl = uifactory.addFileElement("upload", "cal.import.form.prompt", formLayout); FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); @@ -69,11 +85,28 @@ public class CalendarFileUploadController extends FormBasicController { uifactory.addFormSubmitButton("cal.import.form.submit", buttonsCont); uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl()); } + + @Override + protected void doDispose() { + // do nothing here yet + } @Override protected boolean validateFormLogic(UserRequest ureq) { boolean allOk = true; + calendarName.clearError(); + if (calendarName.isEmpty()) { + calendarName.setErrorKey("cal.import.calname.empty.error", null); + allOk = false; + } else { + String calID = ImportCalendarManager.getImportedCalendarID(getIdentity(), calendarName.getValue()); + if (calendarManager.calendarExists(CalendarManager.TYPE_USER, calID)) { + calendarName.setErrorKey("cal.import.calname.exists.error", null); + allOk &= false; + } + } + uploadEl.clearError(); if(uploadEl.getUploadFile() == null) { uploadEl.setErrorKey("form.legende.mandatory", null); @@ -85,33 +118,19 @@ public class CalendarFileUploadController extends FormBasicController { @Override protected void formOK(UserRequest ureq) { + String name = calendarName.getValue(); File uploadedFile = uploadEl.getUploadFile(); - - // store the uploaded file by a temporary name - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - String calID = ImportCalendarManager.getTempCalendarIDForUpload(ureq); - File tmpFile = calManager.getCalendarFile(CalendarManager.TYPE_USER, calID); - if(FileUtils.copyFileToFile(uploadedFile, tmpFile, false)) { - // try to parse the tmp file - Object calendar = calManager.readCalendar(CalendarManager.TYPE_USER, calID); - if (calendar != null) { - //the uploaded calendar file is ok. - fireEvent(ureq, Event.DONE_EVENT); - } else { - getWindowControl().setError(translate("cal.import.form.format.error")); - } - } else { - getWindowControl().setError(translate("cal.import.form.format.error")); + try { + importedCalendar = importCalendarManager.importCalendar(getIdentity(), name, CalendarManager.TYPE_USER, uploadedFile); + fireEvent(ureq, Event.DONE_EVENT); + } catch (IOException e) { + logError("Cannot upload calendar file", e); + showError("cal.import.form.format.error"); } } @Override protected void formCancelled(UserRequest ureq) { fireEvent(ureq, Event.CANCELLED_EVENT); - } - - @Override - protected void doDispose() { - // do nothing here yet } } diff --git a/src/main/java/org/olat/commons/calendar/ui/ImportedCalendarConfigurationController.java b/src/main/java/org/olat/commons/calendar/ui/ImportedCalendarConfigurationController.java deleted file mode 100644 index 0cfdbcfe358..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/ImportedCalendarConfigurationController.java +++ /dev/null @@ -1,178 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.ui; - -import java.util.Date; -import java.util.List; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.ImportCalendarManager; -import org.olat.commons.calendar.model.KalendarConfig; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarGUIAddEvent; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.link.Link; -import org.olat.core.gui.components.link.LinkFactory; -import org.olat.core.gui.components.velocity.VelocityContainer; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.controller.BasicController; -import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; -import org.olat.core.gui.control.generic.modal.DialogBoxController; -import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; -import org.olat.core.util.Util; - - - -public class ImportedCalendarConfigurationController extends BasicController { - - private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); - - private static final Object CMD_ADD = "add"; - private static final Object CMD_TOGGLE_DISPLAY = "tglvis"; - private static final Object CMD_CHOOSE_COLOR = "cc"; - private static final Object CMD_REMOVE_CALENDAR = "rm"; - private static final String PARAM_ID = "id"; - - private VelocityContainer configVC; - private List<KalendarRenderWrapper> importedCalendarWrappers; - private CalendarColorChooserController colorChooser; - private KalendarRenderWrapper lastCalendarWrapper; - private CloseableModalController cmc; - private DialogBoxController confirmRemoveDialog; - private String currentCalendarID; - private Link manageCalendarsButton; - private ManageCalendarsController manageCalendarsController; - - public ImportedCalendarConfigurationController(UserRequest ureq, WindowControl wControl, - List<KalendarRenderWrapper> importedCalendarWrappers, boolean insideManager) { - super(ureq, wControl); - this.importedCalendarWrappers = importedCalendarWrappers; - setTranslator(Util.createPackageTranslator(CalendarManager.class, ureq.getLocale())); - - configVC = new VelocityContainer("calEdit", VELOCITY_ROOT + "/importedCalConfig.html", getTranslator(), this); - configVC.contextPut("calendars", importedCalendarWrappers); - configVC.contextPut("insideManager", insideManager); - manageCalendarsButton = LinkFactory.createButton("cal.managecalendars", configVC, this); - - putInitialPanel(configVC); - } - - public void setCalendars(List<KalendarRenderWrapper> calendars) { - this.importedCalendarWrappers = calendars; - configVC.contextPut("calendars", calendars); - } - - public void event(UserRequest ureq, Component source, Event event) { - if (source == configVC) { - String command = event.getCommand(); - if (command.equals(CMD_ADD)) { - // add new event to calendar - String calendarID = ureq.getParameter(PARAM_ID); - fireEvent(ureq, new KalendarGUIAddEvent(calendarID, new Date())); - } else if (command.equals(CMD_TOGGLE_DISPLAY)) { - String calendarID = ureq.getParameter(PARAM_ID); - KalendarRenderWrapper calendarWrapper = findKalendarRenderWrapper(calendarID); - KalendarConfig config = calendarWrapper.getKalendarConfig(); - config.setVis(!config.isVis()); - CalendarManagerFactory.getInstance().getCalendarManager().saveKalendarConfigForIdentity( - config, calendarWrapper.getKalendar(), ureq); - fireEvent(ureq, Event.CHANGED_EVENT); - } else if (command.equals(CMD_CHOOSE_COLOR)) { - String calendarID = ureq.getParameter(PARAM_ID); - lastCalendarWrapper = findKalendarRenderWrapper(calendarID); - removeAsListenerAndDispose(colorChooser); - colorChooser = new CalendarColorChooserController(ureq, getWindowControl(), lastCalendarWrapper, lastCalendarWrapper.getKalendarConfig().getCss()); - listenTo(colorChooser); - removeAsListenerAndDispose(cmc); - cmc = new CloseableModalController(getWindowControl(), translate("close"), colorChooser.getInitialComponent()); - cmc.activate(); - listenTo(cmc); - } else if (command.equals(CMD_REMOVE_CALENDAR)) { - currentCalendarID = ureq.getParameter(PARAM_ID); - confirmRemoveDialog = activateOkCancelDialog(ureq, translate("cal.import.remove.title"), translate("cal.import.remove.confirmation_message"), confirmRemoveDialog); - } - } else if (source == manageCalendarsButton){ - removeAsListenerAndDispose(manageCalendarsController); - importedCalendarWrappers = ImportCalendarManager.getImportedCalendarsForIdentity(ureq); - manageCalendarsController = new ManageCalendarsController(ureq, getWindowControl(), importedCalendarWrappers); - listenTo(manageCalendarsController); - removeAsListenerAndDispose(cmc); - cmc = new CloseableModalController(getWindowControl(), this.translate("close"), manageCalendarsController.getInitialComponent()); - cmc.activate(); - listenTo(cmc); - } - } - - public void event(UserRequest ureq, Controller source, Event event) { - if (source == colorChooser) { - cmc.deactivate(); - if (event == Event.DONE_EVENT) { - String choosenColor = colorChooser.getChoosenColor(); - KalendarConfig config = lastCalendarWrapper.getKalendarConfig(); - config.setCss(choosenColor); - CalendarManagerFactory.getInstance().getCalendarManager().saveKalendarConfigForIdentity( - config, lastCalendarWrapper.getKalendar(), ureq); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } else if (source == confirmRemoveDialog ) { - if (DialogBoxUIFactory.isOkEvent(event)) { - // remove the imported calendar - ImportCalendarManager.deleteCalendar(currentCalendarID, ureq); - - // update the calendar list - importedCalendarWrappers = ImportCalendarManager.getImportedCalendarsForIdentity(ureq); - configVC.contextPut("calendars", importedCalendarWrappers); - - // show the information that the calendar has been deleted - showInfo("cal.import.remove.info"); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } else if (source == cmc) { - importedCalendarWrappers = ImportCalendarManager.getImportedCalendarsForIdentity(ureq); - configVC.setDirty(true); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } - - private KalendarRenderWrapper findKalendarRenderWrapper(String calendarID) { - for (KalendarRenderWrapper calendarWrapper : importedCalendarWrappers) { - if (calendarWrapper.getKalendar().getCalendarID().equals(calendarID)) - return calendarWrapper; - } - return null; - } - - protected void doDispose() { - // controllers disposed by BasicController - cmc = null; - colorChooser = null; - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ui/InjectCalendarFileController.java b/src/main/java/org/olat/commons/calendar/ui/InjectCalendarFileController.java new file mode 100644 index 00000000000..ef8b41319f6 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/InjectCalendarFileController.java @@ -0,0 +1,95 @@ +/** + * <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.commons.calendar.ui; + +import java.io.InputStream; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.manager.ImportToCalendarManager; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FileElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.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.Util; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class InjectCalendarFileController extends FormBasicController { + + private FileElement importFile; + private CalendarPersonalConfigurationRow row; + + @Autowired + private ImportToCalendarManager importToCalendarManager; + + public InjectCalendarFileController(UserRequest ureq, WindowControl wControl, CalendarPersonalConfigurationRow row) { + super(ureq, wControl); + this.row = row; + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + importFile = uifactory.addFileElement("cal.import.form.prompt", "cal.import.form.prompt", formLayout); + + //standard cancel panel + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); + buttonGroupLayout.setRootForm(mainForm); + formLayout.add("buttonGroupLayout", buttonGroupLayout); + uifactory.addFormSubmitButton("ok", buttonGroupLayout); + uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl()); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + try(InputStream in = importFile.getUploadInputStream()) { + if(importToCalendarManager.importCalendarIn(row.getWrapper(), in)) { + showInfo("cal.import.success"); + fireEvent(ureq, Event.DONE_EVENT); + } else { + showError("cal.import.form.format.error"); + } + } catch(Exception e) { + logError("Cannot upload calendar file", e); + showError("cal.import.form.format.error"); + } + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/ManageCalendarsController.java b/src/main/java/org/olat/commons/calendar/ui/ManageCalendarsController.java deleted file mode 100644 index 6b6b518c8b2..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/ManageCalendarsController.java +++ /dev/null @@ -1,171 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.ui; - -import java.util.List; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.ImportCalendarManager; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -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.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; - -/** - * Description:<BR> - * Manager for: - * 1. export settings of OLAT calendars. - * 2. import of external calendars - * 3. settings of imported calendars - * <P> - * Initial Date: June 28, 2008 - * - * @author Udit Sajjanhar - */ -public class ManageCalendarsController extends BasicController { - - - private VelocityContainer manageVC; - private ImportedCalendarConfigurationController importedCalendarConfig; - private CalendarFileUploadController calFileUpload; - private CalendarImportByUrlController calImportByUrl; - private Link importTypeFileButton; - private Link importTypeUrlButton; - private CalendarImportNameForm nameForm; - private Panel panel; - private String importUrl; - - ManageCalendarsController(UserRequest ureq, WindowControl wControl, List<KalendarRenderWrapper> importedCalendarWrappers) { - super(ureq, wControl); - - setBasePackage(CalendarManager.class); - - manageVC = createVelocityContainer("manageCalendars"); - - // Import calendar functionalities - importedCalendarConfig = new ImportedCalendarConfigurationController(ureq, getWindowControl(), importedCalendarWrappers, true); - listenTo(importedCalendarConfig); - manageVC.put("importedCalendarConfig", importedCalendarConfig.getInitialComponent()); - manageVC.contextPut("importedCalendarWrappers", importedCalendarWrappers); - - - calFileUpload = new CalendarFileUploadController(ureq, wControl); - listenTo(calFileUpload); - - calImportByUrl = new CalendarImportByUrlController(ureq, wControl); - listenTo(calImportByUrl); - - panel = new Panel("panel"); - manageVC.put("fileupload", panel); - manageVC.contextPut("choose", 1); - - importTypeFileButton = LinkFactory.createButton("cal.import.type.file", manageVC, this); - importTypeUrlButton = LinkFactory.createButton("cal.import.type.url", manageVC, this); - putInitialPanel(manageVC); - } - - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) - */ - public void event(UserRequest ureq, Component source, Event event) { - if (source == importTypeFileButton){ - manageVC.contextPut("choose", 0); - panel.setContent(calFileUpload.getInitialComponent()); - } else if (source == importTypeUrlButton){ - manageVC.contextPut("choose", 0); - panel.setContent(calImportByUrl.getInitialComponent()); - }else { - fireEvent(ureq, Event.DONE_EVENT); - } - } - - public void event(UserRequest ureq, Controller source, Event event) { - if (source == nameForm) { - if (event == Event.DONE_EVENT) { - ImportCalendarManager.persistCalendar(nameForm.getCalendarName(), ureq, importUrl); - importUrl = null; // reset importUrl - // inform the user of successful import - showInfo("cal.import.success"); - - // reset the panel to import an another calendar - manageVC.contextPut("choose", 1); - - // update the imported calendar list - importedCalendarConfig.setCalendars(ImportCalendarManager.getImportedCalendarsForIdentity(ureq)); - - manageVC.contextPut("importedCalendarWrappers",ImportCalendarManager.getImportedCalendarsForIdentity(ureq)); - } else if (event == Event.CANCELLED_EVENT) { - // reset the panel to import an another calendar - manageVC.contextPut("choose", 1); - panel.setContent(calFileUpload.getInitialComponent()); - } - } else - if (source == calFileUpload || source == calImportByUrl) { - if (event == Event.DONE_EVENT) { - // correct file has been uploaded. ask user the name of the calendar - removeAsListenerAndDispose(nameForm); - nameForm = new CalendarImportNameForm(ureq, getWindowControl()); - listenTo(nameForm); - panel.setContent(nameForm.getInitialComponent()); - if (source == calImportByUrl) { - // store import url for persistCalendar call - importUrl = calImportByUrl.getImportUrl(); - } - } else if (event == Event.CANCELLED_EVENT) { - // reset the panel to import an another calendar - manageVC.contextPut("choose", 1); - if (source == calFileUpload) { - panel.setContent(calFileUpload.getInitialComponent()); - } else { - panel.setContent(calImportByUrl.getInitialComponent()); - } - } - } else if (source == importedCalendarConfig) { - if (event == Event.CHANGED_EVENT) { - manageVC.contextPut("importedCalendarWrappers",ImportCalendarManager.getImportedCalendarsForIdentity(ureq)); - manageVC.setDirty(true); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } - } - - /** - * - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ - protected void doDispose() { - // - } - -} diff --git a/src/main/java/org/olat/commons/calendar/ui/MediaLinksController.java b/src/main/java/org/olat/commons/calendar/ui/MediaLinksController.java index 0f137e22cac..34f32dcc19b 100644 --- a/src/main/java/org/olat/commons/calendar/ui/MediaLinksController.java +++ b/src/main/java/org/olat/commons/calendar/ui/MediaLinksController.java @@ -67,7 +67,7 @@ public class MediaLinksController extends FormBasicController { public final String provider; - private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); + private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(MediaLinksController.class); private FormLink newButton; private final KalendarEvent kalendarEvent; diff --git a/src/main/java/org/olat/commons/calendar/ui/SynchronizedCalendarUrlController.java b/src/main/java/org/olat/commons/calendar/ui/SynchronizedCalendarUrlController.java new file mode 100644 index 00000000000..5cf5fc26a52 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/SynchronizedCalendarUrlController.java @@ -0,0 +1,120 @@ +/** + * <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.commons.calendar.ui; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.manager.ImportToCalendarManager; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +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.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.core.util.Util; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class SynchronizedCalendarUrlController extends FormBasicController { + + private TextElement importUrl; + private final CalendarPersonalConfigurationRow row; + + @Autowired + private ImportToCalendarManager importToCalendarManager; + + public SynchronizedCalendarUrlController(UserRequest ureq, WindowControl wControl, CalendarPersonalConfigurationRow row) { + super(ureq, wControl); + this.row = row; + setTranslator(Util.createPackageTranslator(CalendarManager.class, getLocale(), getTranslator())); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormDescription("cal.synchronize.type.url.desc"); + + //choose panel + importUrl = uifactory.addTextElement("cal.import.url.prompt", "cal.import.url.prompt", 200, "", formLayout); + + //standard cancel panel + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); + buttonGroupLayout.setRootForm(mainForm); + formLayout.add("buttonGroupLayout", buttonGroupLayout); + uifactory.addFormSubmitButton("ok", buttonGroupLayout); + uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl()); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + + importUrl.clearError(); + String url = importUrl.getValue(); + if(StringHelper.containsNonWhitespace(url)) { + try { + String host = new URL(url).getHost(); + if(host == null) { + importUrl.setErrorKey("cal.import.url.invalid", null); + } + } catch (MalformedURLException e) { + importUrl.setErrorKey("cal.import.url.invalid", null); + allOk &= false; + } + } else { + importUrl.setErrorKey("cal.import.url.empty.error", null); + allOk &= false; + } + + return allOk && super.validateFormLogic(ureq); + } + + @Override + protected void formOK(UserRequest ureq) { + String url = importUrl.getValue(); + if(importToCalendarManager.importCalendarIn(row.getWrapper().getKalendar(), url)) { + showInfo("cal.import.success"); + fireEvent(ureq, Event.DONE_EVENT); + } else { + showError("cal.import.url.content.invalid"); + } + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java b/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java index bd0e77fdedc..ecadf4e619f 100644 --- a/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java +++ b/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java @@ -32,24 +32,26 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import org.apache.commons.lang.RandomStringUtils; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; +import org.olat.commons.calendar.CalendarNotificationManager; import org.olat.commons.calendar.CalendarUtils; -import org.olat.commons.calendar.ImportCalendarManager; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarComparator; import org.olat.commons.calendar.model.KalendarEvent; -import org.olat.commons.calendar.notification.CalendarNotificationManager; import org.olat.commons.calendar.ui.components.FullCalendarElement; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarGUIAddEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIEditEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIImportEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIMoveEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIPrintEvent; -import org.olat.commons.calendar.ui.events.KalendarGUISelectEvent; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; -import org.olat.core.CoreSpringFactory; +import org.olat.commons.calendar.ui.events.CalendarGUIAddEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIEditEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIFormEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIImportEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIMoveEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIPrintEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIRemoveEvent; +import org.olat.commons.calendar.ui.events.CalendarGUISelectEvent; +import org.olat.commons.calendar.ui.events.CalendarGUISettingEvent; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.PublisherData; import org.olat.core.commons.services.notifications.SubscriptionContext; @@ -71,6 +73,7 @@ import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.gui.control.winmgr.JSCommand; +import org.olat.core.helpers.Settings; import org.olat.core.id.UserConstants; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; @@ -78,12 +81,14 @@ import org.olat.core.id.context.StateEntry; import org.olat.core.logging.activity.ILoggingAction; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.CodeHelper; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.resource.OresHelper; import org.olat.group.BusinessGroup; import org.olat.util.logging.activity.LoggingResourceable; +import org.springframework.beans.factory.annotation.Autowired; public class WeeklyCalendarController extends FormBasicController implements Activateable2, CalendarController, GenericEventListener { @@ -92,15 +97,11 @@ public class WeeklyCalendarController extends FormBasicController implements Act public static final String CALLER_COLLAB = "collab"; public static final String CALLER_COURSE = "course"; - private List<KalendarRenderWrapper> calendarWrappers; - private List<KalendarRenderWrapper> importedCalendarWrappers; - private FullCalendarElement weeklyCalendar; - private CalendarConfigurationController calendarConfig; - private ImportedCalendarConfigurationController importedCalendarConfig; + private FullCalendarElement calendarEl; + + private CalendarAggregatedURLController feedUrlCtrl; private CalendarEntryDetailsController editController; - private ImportCalendarController importCalendarController; - private String caller; - private boolean dirty = false; + private CalendarPersonalConfigurationController configurationCtrl; private CloseableModalController cmc; private SubscriptionContext subsContext; @@ -120,27 +121,22 @@ public class WeeklyCalendarController extends FormBasicController implements Act private DialogBoxController deleteSingleYesNoController, deleteSequenceYesNoController; private String modifiedCalendarId; private boolean modifiedCalenderDirty = false; - private final boolean eventAlwaysVisible; + + private final String caller; + private boolean dirty = false; + private final boolean allowImport; + + private List<KalendarRenderWrapper> calendarWrappers; private CalendarPrintMapper printMapper; - private String printUrl; + private final String printUrl; private ILoggingAction calLoggingAction; - private final CalendarNotificationManager calendarNotificationsManager; - - /** - * Display week view of calendar. Add the calendars to be displayed via - * addKalendarWrapper(KalendarRenderWrapper kalendarWrapper) method. - * @param ureq - * @param wControl - * @param calendarWrappers - * @param caller - * @param eventAlwaysVisible When true, the 'isVis()' check is disabled and events will be displayed always. - */ - public WeeklyCalendarController(UserRequest ureq, WindowControl wControl, List<KalendarRenderWrapper> calendarWrappers, String caller, boolean eventAlwaysVisible) { - this(ureq, wControl, calendarWrappers, new ArrayList<KalendarRenderWrapper>(), caller, eventAlwaysVisible); - } + @Autowired + private CalendarManager calendarManager; + @Autowired + private CalendarNotificationManager calendarNotificationsManager; /** * Display week view of calendar. Add the calendars to be displayed via @@ -153,16 +149,13 @@ public class WeeklyCalendarController extends FormBasicController implements Act * @param calendarSubscription * @param eventAlwaysVisible When true, the 'isVis()' check is disabled and events will be displayed always. */ - public WeeklyCalendarController(UserRequest ureq, WindowControl wControl, List<KalendarRenderWrapper> calendarWrappers, List<KalendarRenderWrapper> importedCalendarWrappers, - String caller, boolean eventAlwaysVisible) { + public WeeklyCalendarController(UserRequest ureq, WindowControl wControl, List<KalendarRenderWrapper> calendarWrappers, + String caller, boolean allowImport) { super(ureq,wControl, "indexWeekly"); setTranslator(Util.createPackageTranslator(CalendarManager.class, ureq.getLocale(), getTranslator())); - - calendarNotificationsManager = CoreSpringFactory.getImpl(CalendarNotificationManager.class); - this.eventAlwaysVisible = eventAlwaysVisible; + this.allowImport = allowImport; this.calendarWrappers = calendarWrappers; - this.importedCalendarWrappers = importedCalendarWrappers; this.caller = caller; String themeBaseUri = wControl.getWindowBackOffice().getWindow().getGuiTheme().getBaseURI(); @@ -179,27 +172,14 @@ public class WeeklyCalendarController extends FormBasicController implements Act boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); Collections.sort(calendarWrappers, KalendarComparator.getInstance()); - Collections.sort(importedCalendarWrappers, KalendarComparator.getInstance()); - List<KalendarRenderWrapper> allCalendarWrappers = new ArrayList<KalendarRenderWrapper>(calendarWrappers); - allCalendarWrappers.addAll(importedCalendarWrappers); - weeklyCalendar = new FullCalendarElement(ureq, "weeklyCalendar", allCalendarWrappers, getTranslator(), eventAlwaysVisible); - formLayout.add("calendar", weeklyCalendar); + calendarEl = new FullCalendarElement(ureq, "weeklyCalendar", calendarWrappers, getTranslator()); + formLayout.add("calendar", calendarEl); + calendarEl.setConfigurationEnabled(true); + calendarEl.setAggregatedFeedEnabled(true); if(formLayout instanceof FormLayoutContainer) { FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout; - layoutCont.contextPut("caller", caller); - - // calendarConfiguration component - calendarConfig = new CalendarConfigurationController(calendarWrappers, ureq, getWindowControl(), eventAlwaysVisible); - listenTo(calendarConfig); - layoutCont.put("calendarConfig", calendarConfig.getInitialComponent()); - - //imported calendar list - importedCalendarConfig = new ImportedCalendarConfigurationController(ureq, getWindowControl(),importedCalendarWrappers, false); - listenTo(importedCalendarConfig); - layoutCont.put("importedCalendarConfig", importedCalendarConfig.getInitialComponent()); - if (!isGuest && !calendarWrappers.isEmpty()) { subsContext = calendarNotificationsManager.getSubscriptionContext(calendarWrappers.get(0)); // if sc is null, then no subscription is desired @@ -235,14 +215,14 @@ public class WeeklyCalendarController extends FormBasicController implements Act } public Date getFocus() { - return weeklyCalendar.getFocusDate(); + return calendarEl.getFocusDate(); } @Override public void setFocus(Date date) { Calendar focus = CalendarUtils.createCalendarInstance(getLocale()); focus.setTime(date); - weeklyCalendar.setFocusDate(focus.getTime()); + calendarEl.setFocusDate(focus.getTime()); } @Override @@ -258,24 +238,14 @@ public class WeeklyCalendarController extends FormBasicController implements Act } } + @Override public void setCalendars(List<KalendarRenderWrapper> calendars) { - setCalendars(calendars, new ArrayList<KalendarRenderWrapper>()); - } - - public void setCalendars(List<KalendarRenderWrapper> calendars, List<KalendarRenderWrapper> importedCalendars) { - this.calendarWrappers = calendars; - this.importedCalendarWrappers = importedCalendars; + calendarWrappers = calendars; Collections.sort(calendarWrappers, KalendarComparator.getInstance()); - Collections.sort(importedCalendarWrappers, KalendarComparator.getInstance()); - - List<KalendarRenderWrapper> allCalendarWrappers = new ArrayList<KalendarRenderWrapper>(calendarWrappers); - allCalendarWrappers.addAll(importedCalendarWrappers); - weeklyCalendar.setKalendars(allCalendarWrappers); - - calendarConfig.setCalendars(calendarWrappers); - importedCalendarConfig.setCalendars(importedCalendarWrappers); + calendarEl.setCalendars(calendarWrappers); } + @Override public void setDirty() { dirty = true; } @@ -290,9 +260,9 @@ public class WeeklyCalendarController extends FormBasicController implements Act @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if (source == weeklyCalendar) { - if (event instanceof KalendarGUIEditEvent) { - KalendarGUIEditEvent guiEvent = (KalendarGUIEditEvent) event; + if (source == calendarEl) { + if (event instanceof CalendarGUIEditEvent) { + CalendarGUIEditEvent guiEvent = (CalendarGUIEditEvent) event; KalendarEvent kalendarEvent = guiEvent.getKalendarEvent(); if (kalendarEvent == null) { // event already deleted @@ -319,26 +289,34 @@ public class WeeklyCalendarController extends FormBasicController implements Act } KalendarRenderWrapper kalendarWrapper = guiEvent.getKalendarRenderWrapper(); pushEditEventController(ureq, kalendarEvent, kalendarWrapper); - } else if (event instanceof KalendarGUIAddEvent) { - pushAddEventController((KalendarGUIAddEvent)event, ureq); - } else if (event instanceof KalendarGUISelectEvent) { - KalendarGUISelectEvent selectEvent = (KalendarGUISelectEvent)event; + } else if (event instanceof CalendarGUIAddEvent) { + pushAddEventController((CalendarGUIAddEvent)event, ureq); + } else if (event instanceof CalendarGUISelectEvent) { + CalendarGUISelectEvent selectEvent = (CalendarGUISelectEvent)event; if(selectEvent.getKalendarEvent() != null) { doOpenEventCallout(ureq, selectEvent.getKalendarEvent(), selectEvent.getKalendarRenderWrapper(), selectEvent.getTargetDomId()); } - } else if (event instanceof KalendarGUIMoveEvent) { - KalendarGUIMoveEvent moveEvent = (KalendarGUIMoveEvent)event; + } else if (event instanceof CalendarGUIMoveEvent) { + CalendarGUIMoveEvent moveEvent = (CalendarGUIMoveEvent)event; doMove(moveEvent.getKalendarEvent(), moveEvent.getDayDelta(), moveEvent.getMinuteDelta(), moveEvent.getAllDay()); + } else if (event instanceof CalendarGUIFormEvent) { + String cmd = event.getCommand(); + if(CalendarGUIFormEvent.CONFIGURE.equals(cmd)) { + doConfigure(ureq); + } else if(CalendarGUIFormEvent.AGGREGATED_FEED.equals(cmd)) { + CalendarGUIFormEvent guiEvent = (CalendarGUIFormEvent)event; + doOpenAggregatedFeelUrl(ureq, guiEvent.getTargetDomId()); + } + } else if (event instanceof CalendarGUIPrintEvent) { + CalendarGUIPrintEvent printEvent = (CalendarGUIPrintEvent)event; + if(printEvent.getFrom() != null && printEvent.getTo() != null) { + doPrint(printEvent.getFrom(), printEvent.getTo()); + } else if(printEvent.getTargetDomId() != null) { + doPrintEventCallout(ureq, printEvent.getTargetDomId()); + } } - } else if (event instanceof KalendarGUIPrintEvent) { - KalendarGUIPrintEvent printEvent = (KalendarGUIPrintEvent)event; - if(printEvent.getFrom() != null && printEvent.getTo() != null) { - doPrint(printEvent.getFrom(), printEvent.getTo()); - } else if(printEvent.getTargetDomId() != null) { - doPrintEventCallout(ureq, printEvent.getTargetDomId()); - } - } + } super.formInnerEvent(ureq, source, event); } @@ -346,9 +324,9 @@ public class WeeklyCalendarController extends FormBasicController implements Act public void event(UserRequest ureq, Component source, Event event) { if (event == ComponentUtil.VALIDATE_EVENT && dirty) { dirty = false; - fireEvent(ureq, new KalendarModifiedEvent()); - } else if (event == ComponentUtil.VALIDATE_EVENT && weeklyCalendar.getComponent().isDirty() && modifiedCalenderDirty ) { - KalendarRenderWrapper kalendarRenderWrapper = weeklyCalendar.getKalendarRenderWrapper(modifiedCalendarId); + fireEvent(ureq, new CalendarGUIModifiedEvent()); + } else if (event == ComponentUtil.VALIDATE_EVENT && calendarEl.getComponent().isDirty() && modifiedCalenderDirty ) { + KalendarRenderWrapper kalendarRenderWrapper = calendarEl.getCalendar(modifiedCalendarId); kalendarRenderWrapper.reloadKalendar(); } super.event(ureq, source, event); @@ -359,52 +337,39 @@ public class WeeklyCalendarController extends FormBasicController implements Act Kalendar affectedCal = null; if (dirty) { dirty = false; - fireEvent(ureq, new KalendarModifiedEvent()); + fireEvent(ureq, new CalendarGUIModifiedEvent()); } if (source == editController) { affectedCal = editController.getKalendarEvent().getCalendar(); cmc.deactivate(); - weeklyCalendar.getComponent().setDirty(true); + calendarEl.getComponent().setDirty(true); // do logging if affectedCal not null if(affectedCal!=null) { ThreadLocalUserActivityLogger.log(getCalLoggingAction(), getClass(), LoggingResourceable.wrap(ureq.getIdentity()), LoggingResourceable.wrap(affectedCal)); } cleanUp(); } else if (source == eventDetailsCtr) { - if(event instanceof KalendarGUIEditEvent) { + if(event instanceof CalendarGUIEditEvent) { eventCalloutCtr.deactivate(); - KalendarGUIEditEvent editEvent = (KalendarGUIEditEvent)event; + CalendarGUIEditEvent editEvent = (CalendarGUIEditEvent)event; pushEditEventController(ureq, editEvent.getKalendarEvent(), editEvent.getKalendarRenderWrapper()); } } else if(source == printCtrl) { - if (event instanceof KalendarGUIPrintEvent) { - KalendarGUIPrintEvent printEvent = (KalendarGUIPrintEvent)event; + if (event instanceof CalendarGUIPrintEvent) { + CalendarGUIPrintEvent printEvent = (CalendarGUIPrintEvent)event; if(printEvent.getFrom() != null && printEvent.getTo() != null) { doPrint(printEvent.getFrom(), printEvent.getTo()); } } eventCalloutCtr.deactivate(); - } else if (source == importCalendarController) { - cmc.deactivate(); } else if(source == cmc) { - weeklyCalendar.getComponent().setDirty(true); + calendarEl.getComponent().setDirty(true); cleanUp(); - } else if (source == calendarConfig || source == importedCalendarConfig) { - if (event instanceof KalendarGUIAddEvent) { - pushAddEventController((KalendarGUIAddEvent)event, ureq); - } else if (event instanceof KalendarGUIImportEvent) { - pushImportEventController((KalendarGUIImportEvent)event, ureq); - } else if (event == Event.CHANGED_EVENT) { - importedCalendarWrappers = ImportCalendarManager.getImportedCalendarsForIdentity(ureq); - importedCalendarConfig.setCalendars(importedCalendarWrappers); - setCalendars(calendarWrappers, importedCalendarWrappers); - weeklyCalendar.getComponent().setDirty(true); - } } else if (source == dbcSequence) { if(event != Event.CANCELLED_EVENT) { int pos = DialogBoxUIFactory.getButtonPos(event); - KalendarGUIEditEvent guiEvent = (KalendarGUIEditEvent)dbcSequence.getUserObject(); + CalendarGUIEditEvent guiEvent = (CalendarGUIEditEvent)dbcSequence.getUserObject(); KalendarRenderWrapper kalendarWrapper = guiEvent.getKalendarRenderWrapper(); KalendarEvent kalendarEvent = guiEvent.getKalendarEvent(); if(pos == 0) { // edit the sequence @@ -426,20 +391,34 @@ public class WeeklyCalendarController extends FormBasicController implements Act affectedCal = kalendarEvent.getCalendar(); KalendarEvent kEvent = affectedCal.getEvent(kalendarEvent.getID()); kEvent.addRecurrenceExc(kalendarEvent.getBegin()); - CalendarManagerFactory.getInstance().getCalendarManager().updateEventFrom(affectedCal, kEvent); + calendarManager.updateEventFrom(affectedCal, kEvent); deleteSingleYesNoController.dispose(); - weeklyCalendar.getComponent().setDirty(true); + calendarEl.getComponent().setDirty(true); } } else if (source == deleteSequenceYesNoController) { if (DialogBoxUIFactory.isYesEvent(event)) { KalendarEvent kalendarEvent = (KalendarEvent)deleteSequenceYesNoController.getUserObject(); affectedCal = kalendarEvent.getCalendar(); - CalendarManagerFactory.getInstance().getCalendarManager().removeEventFrom(affectedCal, kalendarEvent); + calendarManager.removeEventFrom(affectedCal, kalendarEvent); deleteSequenceYesNoController.dispose(); - weeklyCalendar.getComponent().setDirty(true); + calendarEl.getComponent().setDirty(true); } - } - if (weeklyCalendar.getComponent().isDirty()) { + } else if (configurationCtrl == source) { + if(event instanceof CalendarGUIImportEvent) { + CalendarGUIImportEvent importEvent = (CalendarGUIImportEvent)event; + calendarWrappers.add(importEvent.getCalendar()); + calendarEl.setCalendars(calendarWrappers); + } else if(event instanceof CalendarGUIRemoveEvent) { + CalendarGUIRemoveEvent removeEvent = (CalendarGUIRemoveEvent)event; + calendarWrappers.remove(removeEvent.getCalendar()); + calendarEl.setCalendars(calendarWrappers); + } else if(event instanceof CalendarGUISettingEvent) { + //CalendarGUISettingEvent settingEvent = (CalendarGUISettingEvent)event; + calendarEl.setCalendars(calendarWrappers); + } + } + + if (calendarEl.getComponent().isDirty()) { if (subsContext != null) { // group or course calendar -> prepared subscription context is the right one NotificationsManager.getInstance().markPublisherNews(subsContext, ureq.getIdentity(), true); @@ -456,8 +435,10 @@ public class WeeklyCalendarController extends FormBasicController implements Act } private void cleanUp() { + removeAsListenerAndDispose(configurationCtrl); removeAsListenerAndDispose(editController); removeAsListenerAndDispose(cmc); + configurationCtrl = null; editController = null; cmc = null; } @@ -470,18 +451,58 @@ public class WeeklyCalendarController extends FormBasicController implements Act if(dateEntry.startsWith("date")) { Date gotoDate = BusinessControlFactory.getInstance().getDateFromContextEntry(entries.get(0)); if(gotoDate != null) { - weeklyCalendar.setFocusDate(gotoDate); + calendarEl.setFocusDate(gotoDate); } } } + private void doOpenAggregatedFeelUrl(UserRequest ureq, String targetDomId) { + List<CalendarUserConfiguration> configurations = calendarManager + .getCalendarUserConfigurationsList(getIdentity(), CalendarManager.TYPE_USER_AGGREGATED); + + CalendarUserConfiguration config; + if(configurations.isEmpty()) { + config = calendarManager.createAggregatedCalendarConfig(getIdentity()); + } else if(StringHelper.containsNonWhitespace(configurations.get(0).getToken())) { + config = configurations.get(0); + } else { + config = configurations.get(0); + config.setToken(RandomStringUtils.randomAlphanumeric(6)); + config = calendarManager.saveCalendarConfig(config); + } + + String token = config.getToken(); + Long configKey = config.getKey(); + String url = Settings.getServerContextPathURI() + "/ical/" + CalendarManager.TYPE_USER_AGGREGATED + "/" + configKey + "/" + token + ".ics"; + feedUrlCtrl = new CalendarAggregatedURLController(ureq, getWindowControl(), url); + listenTo(feedUrlCtrl); + + eventCalloutCtr = new CloseableCalloutWindowController(ureq, getWindowControl(), feedUrlCtrl.getInitialComponent(), targetDomId, + translate("print"), true, "o_cal_event_callout"); + listenTo(eventCalloutCtr); + eventCalloutCtr.activate(); + } + + private void doConfigure(UserRequest ureq) { + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(configurationCtrl); + + List<KalendarRenderWrapper> allCalendars = new ArrayList<>(calendarWrappers); + configurationCtrl = new CalendarPersonalConfigurationController(ureq, getWindowControl(), allCalendars, allowImport); + listenTo(configurationCtrl); + + String title = ""; + cmc = new CloseableModalController(getWindowControl(), "c", configurationCtrl.getInitialComponent(), true, title); + listenTo(cmc); + cmc.activate(); + } + private void doPrint(Date from, Date to) { StringBuilder sb = new StringBuilder(); sb.append("window.open('" + printUrl + "/print.html', '_print','height=800,left=100,top=100,width=800,toolbar=no,titlebar=0,status=0,menubar=yes,location= no,scrollbars=1');"); printMapper.setFrom(from); printMapper.setTo(to); printMapper.setCalendarWrappers(calendarWrappers); - printMapper.setImportedCalendarWrappers(importedCalendarWrappers); getWindowControl().getWindowBackOffice().sendCommandTo(new JSCommand(sb.toString())); } @@ -522,8 +543,8 @@ public class WeeklyCalendarController extends FormBasicController implements Act calEvent.setAllDayEvent(allDay.booleanValue()); } - CalendarManagerFactory.getInstance().getCalendarManager().updateEventFrom(cal, calEvent); - weeklyCalendar.getComponent().setDirty(true); + calendarManager.updateEventFrom(cal, calEvent); + calendarEl.getComponent().setDirty(true); } private Date doMove(Date date, Long dayDelta, Long minuteDelta) { @@ -550,6 +571,7 @@ public class WeeklyCalendarController extends FormBasicController implements Act private void pushEditEventController(UserRequest ureq, KalendarEvent kalendarEvent, KalendarRenderWrapper kalendarWrapper) { if(editController != null) return; + removeAsListenerAndDispose(cmc); removeAsListenerAndDispose(editController); boolean canEdit = false; @@ -568,7 +590,6 @@ public class WeeklyCalendarController extends FormBasicController implements Act removeAsListenerAndDispose(cmc); cmc = new CloseableModalController(getWindowControl(), translate("close"), editController.getInitialComponent()); listenTo(cmc); - cmc.activate(); // set logging action @@ -577,13 +598,15 @@ public class WeeklyCalendarController extends FormBasicController implements Act showError("cal.error.readonly"); } } - - private void pushAddEventController(KalendarGUIAddEvent addEvent, UserRequest ureq) { + + private void pushAddEventController(CalendarGUIAddEvent addEvent, UserRequest ureq) { if(editController != null || ureq.getUserSession().getRoles().isGuestOnly()) { return; } + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(editController); - KalendarRenderWrapper calendarWrapper = weeklyCalendar.getKalendarRenderWrapper(addEvent.getCalendarID()); + KalendarRenderWrapper calendarWrapper = calendarEl.getCalendar(addEvent.getCalendarID()); // create new KalendarEvent Date begin = addEvent.getStartDate(); @@ -605,54 +628,40 @@ public class WeeklyCalendarController extends FormBasicController implements Act String firstName = ureq.getIdentity().getUser().getProperty(UserConstants.FIRSTNAME, getLocale()); newEvent.setCreatedBy(firstName + " " + lastName); newEvent.setCreated(new Date().getTime()); - ArrayList<KalendarRenderWrapper> allCalendarWrappers = new ArrayList<KalendarRenderWrapper>(calendarWrappers); - allCalendarWrappers.addAll(importedCalendarWrappers); - removeAsListenerAndDispose(editController); - editController = new CalendarEntryDetailsController(ureq, newEvent, calendarWrapper, allCalendarWrappers, true, caller, getWindowControl()); + List<KalendarRenderWrapper> copyCalendarWrappers = new ArrayList<KalendarRenderWrapper>(calendarWrappers); + + editController = new CalendarEntryDetailsController(ureq, newEvent, calendarWrapper, copyCalendarWrappers, true, caller, getWindowControl()); listenTo(editController); - removeAsListenerAndDispose(cmc); cmc = new CloseableModalController(getWindowControl(), translate("close"), editController.getInitialComponent()); listenTo(cmc); - cmc.activate(); // set logging action setCalLoggingAction(CalendarLoggingAction.CALENDAR_ENTRY_CREATED); } - private void pushImportEventController(KalendarGUIImportEvent importEvent, UserRequest ureq) { - KalendarRenderWrapper calendarWrapper = weeklyCalendar.getKalendarRenderWrapper(importEvent.getCalendarID()); - - removeAsListenerAndDispose(importCalendarController); - - importCalendarController = new ImportCalendarController(ureq, getWindowControl(), calendarWrapper); - listenTo(importCalendarController); - removeAsListenerAndDispose(cmc); - cmc = new CloseableModalController(getWindowControl(), translate("close"), importCalendarController.getInitialComponent()); - cmc.activate(); - listenTo(cmc); - } - + @Override protected void doDispose() { CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, OresHelper.lookupType(CalendarManager.class)); } + @Override public void event(Event event) { - if (event instanceof KalendarModifiedEvent) { - KalendarModifiedEvent kalendarModifiedEvent = (KalendarModifiedEvent)event; + if (event instanceof CalendarGUIModifiedEvent) { + CalendarGUIModifiedEvent kalendarModifiedEvent = (CalendarGUIModifiedEvent)event; if (kalendarModifiedEvent.getType()!=null && kalendarModifiedEvent.getCalendarId()!=null - && weeklyCalendar.getKalendarRenderWrapper(kalendarModifiedEvent.getCalendarId())!=null - && kalendarModifiedEvent.getType().equals(weeklyCalendar.getKalendarRenderWrapper(kalendarModifiedEvent.getCalendarId()).getKalendar().getType()) - && kalendarModifiedEvent.getCalendarId().equals(weeklyCalendar.getKalendarRenderWrapper(kalendarModifiedEvent.getCalendarId()).getKalendar().getCalendarID())) { + && calendarEl.getCalendar(kalendarModifiedEvent.getCalendarId())!=null + && kalendarModifiedEvent.getType().equals(calendarEl.getCalendar(kalendarModifiedEvent.getCalendarId()).getKalendar().getType()) + && kalendarModifiedEvent.getCalendarId().equals(calendarEl.getCalendar(kalendarModifiedEvent.getCalendarId()).getKalendar().getCalendarID())) { // the event is for my calendar => reload it //keeping a reference to the dirty calendar as reloading here raises an nested do in sync error. Using the component validation event to reload modifiedCalendarId = kalendarModifiedEvent.getCalendarId(); modifiedCalenderDirty = true; - weeklyCalendar.getComponent().setDirty(true); + calendarEl.getComponent().setDirty(true); } } } diff --git a/src/main/java/org/olat/commons/calendar/ui/_content/calAggregatedFeed.html b/src/main/java/org/olat/commons/calendar/ui/_content/calAggregatedFeed.html new file mode 100644 index 00000000000..52340295784 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/_content/calAggregatedFeed.html @@ -0,0 +1,3 @@ +<h4>$r.translate("cal.icalfeed.aggregated.title")</h4> +<div class="o_info">$r.translate("cal.icalfeed.aggregated.info")</div> +<div class="clearfix"><a href="$icalFeedLink">$icalFeedLink</a></div> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_content/calEditDetails.html b/src/main/java/org/olat/commons/calendar/ui/_content/calEditDetails.html similarity index 100% rename from src/main/java/org/olat/commons/calendar/_content/calEditDetails.html rename to src/main/java/org/olat/commons/calendar/ui/_content/calEditDetails.html diff --git a/src/main/java/org/olat/commons/calendar/_content/calEditLinks.html b/src/main/java/org/olat/commons/calendar/ui/_content/calEditLinks.html similarity index 100% rename from src/main/java/org/olat/commons/calendar/_content/calEditLinks.html rename to src/main/java/org/olat/commons/calendar/ui/_content/calEditLinks.html diff --git a/src/main/java/org/olat/commons/calendar/_content/calEditMain.html b/src/main/java/org/olat/commons/calendar/ui/_content/calEditMain.html similarity index 100% rename from src/main/java/org/olat/commons/calendar/_content/calEditMain.html rename to src/main/java/org/olat/commons/calendar/ui/_content/calEditMain.html diff --git a/src/main/java/org/olat/commons/calendar/_content/calExternalLinks.html b/src/main/java/org/olat/commons/calendar/ui/_content/calExternalLinks.html similarity index 100% rename from src/main/java/org/olat/commons/calendar/_content/calExternalLinks.html rename to src/main/java/org/olat/commons/calendar/ui/_content/calExternalLinks.html diff --git a/src/main/java/org/olat/commons/calendar/_content/calExternalMedias.html b/src/main/java/org/olat/commons/calendar/ui/_content/calExternalMedias.html similarity index 100% rename from src/main/java/org/olat/commons/calendar/_content/calExternalMedias.html rename to src/main/java/org/olat/commons/calendar/ui/_content/calExternalMedias.html diff --git a/src/main/java/org/olat/commons/calendar/ui/_content/calIcalFeed.html b/src/main/java/org/olat/commons/calendar/ui/_content/calIcalFeed.html new file mode 100644 index 00000000000..1d4305f3d8b --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/_content/calIcalFeed.html @@ -0,0 +1,3 @@ +<h4>$r.translate("cal.icalfeed.title")</h4> +<div class="o_info">$r.translate("cal.icalfeed.info")</div> +<div class="clearfix"><a href="$icalFeedLink">$icalFeedLink</a></div> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ui/_content/configuration.html b/src/main/java/org/olat/commons/calendar/ui/_content/configuration.html new file mode 100644 index 00000000000..bb9718d7db1 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/_content/configuration.html @@ -0,0 +1,13 @@ +#if($r.available("cal.import.type.file") || $r.available("cal.synchronize.type.url")) +<div class="o_button_group_right"> + <div class="btn-group"> + #if($r.available("cal.import.type.file")) + $r.render("cal.import.type.file") + #end + #if($r.available("cal.synchronize.type.url")) + $r.render("cal.synchronize.type.url") + #end + </div> +</div> +#end +$r.render("table") \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ui/_content/event_details.html b/src/main/java/org/olat/commons/calendar/ui/_content/event_details.html index a47c04af7e8..08d87bbaa40 100644 --- a/src/main/java/org/olat/commons/calendar/ui/_content/event_details.html +++ b/src/main/java/org/olat/commons/calendar/ui/_content/event_details.html @@ -1,11 +1,16 @@ <div class="o_cal_time">$date</div> <div class="o_cal_wv_event_tooltip_content"> $r.escapeHtml($subject) - #if($location) + #if($location && !${location.isEmpty()}) <div class="o_cal_location"> <b>$r.translate("cal.form.location"):</b> $r.escapeHtml($location) </div> #end + #if($description && !${description.isEmpty()}) + <div class="o_cal_description"> + <b>$r.translate("cal.form.description"):</b> $r.xssScan($description) + </div> + #end <div class="o_cal_links"> #foreach($link in $links) #if($link.intern) diff --git a/src/main/java/org/olat/commons/calendar/ui/_content/indexWeekly.html b/src/main/java/org/olat/commons/calendar/ui/_content/indexWeekly.html index bd24db9e93a..7db03d41e03 100644 --- a/src/main/java/org/olat/commons/calendar/ui/_content/indexWeekly.html +++ b/src/main/java/org/olat/commons/calendar/ui/_content/indexWeekly.html @@ -18,8 +18,4 @@ <div class="o_cal"> $r.render("calendar") </div> - $r.render("calendarConfig") - #if ($caller == "home") - $r.render("importedCalendarConfig") - #end </div> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ui/_content/tools.html b/src/main/java/org/olat/commons/calendar/ui/_content/tools.html new file mode 100644 index 00000000000..ea9cd479e89 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/_content/tools.html @@ -0,0 +1,13 @@ +<ul class="o_dropdown list-unstyled"> +#if($r.available("cal.import.type.file") && $r.available("cal.import.type.file")) + <li>$r.render("cal.import.type.file")</li> + <li>$r.render("cal.synchronize.type.url")</li> + <li class="divider"></li> +#end +#if($r.available("cal.icalfeed.subscribe.remove")) + <li>$r.render("cal.icalfeed.subscribe.remove")</li> +#end +#if($r.available("cal.delete.imported.calendar")) + <li>$r.render("cal.delete.imported.calendar")</li> +#end +</ul> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponent.java b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponent.java index 5a53a2ad902..953c21072b2 100644 --- a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponent.java +++ b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponent.java @@ -45,10 +45,11 @@ public class FullCalendarComponent extends AbstractComponent { private static final FullCalendarComponentRenderer RENDERER = new FullCalendarComponentRenderer(); - private List<KalendarRenderWrapper> calWrappers = new ArrayList<KalendarRenderWrapper>(); + private List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); private Date currentDate; private String viewName = "month"; - private boolean eventAlwaysVisible; + private boolean configurationEnabled; + private boolean aggregatedFeedEnabled; private final MapperKey mapperKey; private final FullCalendarElement calendarEl; @@ -61,12 +62,11 @@ public class FullCalendarComponent extends AbstractComponent { * @param translator * @param eventAlwaysVisible When true, the 'isVis()' check is disabled and events will be displayed always. */ - public FullCalendarComponent(UserRequest ureq, FullCalendarElement calendarEl, String name, - Collection<KalendarRenderWrapper> calendarWrappers, Translator translator, Boolean eventAlwaysVisible) { + FullCalendarComponent(UserRequest ureq, FullCalendarElement calendarEl, String name, + Collection<KalendarRenderWrapper> calendarWrappers, Translator translator) { super(name, translator); - this.eventAlwaysVisible = eventAlwaysVisible; setCurrentDate(new Date()); - calWrappers = new ArrayList<KalendarRenderWrapper>(calendarWrappers); + calendars = new ArrayList<KalendarRenderWrapper>(calendarWrappers); this.calendarEl = calendarEl; MapperService mapper = CoreSpringFactory.getImpl(MapperService.class); @@ -97,6 +97,22 @@ public class FullCalendarComponent extends AbstractComponent { this.currentDate = currentDate; } + public boolean isConfigurationEnabled() { + return configurationEnabled; + } + + public void setConfigurationEnabled(boolean configurationEnabled) { + this.configurationEnabled = configurationEnabled; + } + + public boolean isAggregatedFeedEnabled() { + return aggregatedFeedEnabled; + } + + public void setAggregatedFeedEnabled(boolean aggregatedFeedEnabled) { + this.aggregatedFeedEnabled = aggregatedFeedEnabled; + } + /** * @see org.olat.core.gui.components.Component#doDispatchRequest(org.olat.core.gui.UserRequest) */ @@ -122,17 +138,9 @@ public class FullCalendarComponent extends AbstractComponent { vr.getJsAndCSSAdder().addRequiredStaticJsFile("js/jquery/fullcalendar/fullcalendar.min.js"); vr.getJsAndCSSAdder().addRequiredStaticJsFile("js/jquery/ui/jquery-ui-1.11.4.custom.dnd.min.js"); } - - /** - * Returns true when events should be visible always (renderer does not check isVis() ) - * @return - */ - public boolean isEventAlwaysVisible() { - return eventAlwaysVisible; - } - public KalendarEvent getKalendarEvent(String id) { - for(KalendarRenderWrapper cal:calWrappers) { + public KalendarEvent getCalendarEvent(String id) { + for(KalendarRenderWrapper cal:calendars) { for(KalendarEvent event:cal.getKalendar().getEvents()) { if(id.equals(normalizeId(event.getID()))) { return event; @@ -142,8 +150,8 @@ public class FullCalendarComponent extends AbstractComponent { return null; } - public KalendarRenderWrapper getKalendarRenderWrapperOf(String id) { - for(KalendarRenderWrapper cal:calWrappers) { + public KalendarRenderWrapper getCalendarByNormalizedId(String id) { + for(KalendarRenderWrapper cal:calendars) { for(KalendarEvent event:cal.getKalendar().getEvents()) { if(id.equals(normalizeId(event.getID()))) { return cal; @@ -153,10 +161,10 @@ public class FullCalendarComponent extends AbstractComponent { return null; } - public List<KalendarEvent> getKalendarRenderWrapper(Date from, Date to) { + public List<KalendarEvent> getCalendarRenderWrapper(Date from, Date to) { List<KalendarEvent> events = new ArrayList<KalendarEvent>(); - for(KalendarRenderWrapper cal:calWrappers) { + for(KalendarRenderWrapper cal:calendars) { for(KalendarEvent event:cal.getKalendar().getEvents()) { Date end = event.getEnd(); Date begin = event.getBegin(); @@ -169,10 +177,10 @@ public class FullCalendarComponent extends AbstractComponent { return events; } - public KalendarRenderWrapper getKalendarRenderWrapper(String calendarID) { + public KalendarRenderWrapper getCalendar(String calendarID) { if(calendarID == null) return null; - for(KalendarRenderWrapper cal:calWrappers) { + for(KalendarRenderWrapper cal:calendars) { if(calendarID.equals(cal.getKalendar().getCalendarID())) { return cal; } @@ -180,12 +188,18 @@ public class FullCalendarComponent extends AbstractComponent { return null; } - public List<KalendarRenderWrapper> getKalendarRenderWrappers() { - return calWrappers; + public List<KalendarRenderWrapper> getCalendars() { + return calendars; } - public void setKalendars(List<KalendarRenderWrapper> calendarWrappers) { - this.calWrappers = new ArrayList<KalendarRenderWrapper>(calendarWrappers); + public void setCalendars(List<KalendarRenderWrapper> calendarWrappers) { + calendars = new ArrayList<KalendarRenderWrapper>(calendarWrappers); + setDirty(true); + } + + public void addCalendar(KalendarRenderWrapper calendarWrapper) { + calendars.add(calendarWrapper); + setDirty(true); } protected static final String normalizeId(String id) { diff --git a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponentRenderer.java b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponentRenderer.java index 27c96938754..b0c490e2536 100644 --- a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponentRenderer.java +++ b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarComponentRenderer.java @@ -50,9 +50,11 @@ public class FullCalendarComponentRenderer extends DefaultComponentRenderer { FullCalendarComponent fcC = (FullCalendarComponent)source; FullCalendarElement fcE = fcC.getCalendarElement(); Form rootForm = fcE.getRootForm(); - String id = "o_c" + fcC.getDispatchID(); + String id = "o_c".concat(fcC.getDispatchID()); String formId = fcE.getFormDispatchId(); - String printId = "fc_p" + fcC.getDispatchID(); + String printId = "fc_p".concat(fcC.getDispatchID()); + String configId = "fc_x".concat(fcC.getDispatchID()); + String aggregatedId = "fc_g".concat(fcC.getDispatchID()); Calendar cal = Calendar.getInstance(fcC.getTranslator().getLocale()); int firstDay = cal.getFirstDayOfWeek() - 1; @@ -69,55 +71,55 @@ public class FullCalendarComponentRenderer extends DefaultComponentRenderer { String amFormatted = DateFormat.getTimeInstance(DateFormat.SHORT, fcC.getTranslator().getLocale()).format(calRef.getTime()); boolean ampm = amFormatted.contains("AM") || amFormatted.contains("PM"); - + sb.append("<script type='text/javascript'>\n") .append("/* <![CDATA[ */ \n") - .append("jQuery(function() {\n") - .append(" jQuery('#").append(id).append("').fullCalendar( {\n") - .append(" header: {\n") - .append(" left: 'prev,print,next today',\n") - .append(" center: 'title',\n") - .append(" right: 'month,agendaWeek,agendaDay'\n") - .append(" },\n") - .append(" buttonText: {\n") - .append(" today: '").append(escapeJavaScript(translator.translate("cal.thisweek"))).append("',\n") - .append(" month: '").append(escapeJavaScript(translator.translate("cal.month"))).append("',\n") - .append(" day: '").append(escapeJavaScript(translator.translate("cal.day"))).append("',\n") - .append(" week: '").append(escapeJavaScript(translator.translate("cal.week"))).append("',\n") - .append(" print: '").append(escapeJavaScript(translator.translate("print"))).append("'\n") - .append(" },\n") - .append(" monthNames: ").append(getMonthLong(translator)).append(",\n") - .append(" monthNamesShort: ").append(getMonthShort(translator)).append(",\n") - .append(" dayNames: ").append(getDayLong(translator)).append(",\n") - .append(" dayNamesShort: ").append(getDayShort(translator)).append(",\n") - .append(" allDayText:'").append(translator.translate("cal.form.allday")).append("',\n") - .append(" axisFormat:'").append(ampm ? "h(:mm)tt" : "H.mm").append("',\n") - .append(" timeFormat:'").append(ampm ? "h(:mm)tt" : "H.mm").append("',\n") - .append(" titleFormat: {\n") - .append(" month: 'MMMM yyyy',\n") - .append(" week: ").append(firstMonth ? "\"MMM d[ yyyy]{ '—'[ MMM] d yyyy}\"" : "\"d. [ MMM] [ yyyy]{ '—' d. MMM yyyy}\"").append(",\n") - .append(" day: ").append(firstMonth ? "'dddd, MMM d, yyyy'" : "'dddd, d. MMM yyyy'").append("\n") - .append(" },\n") - .append(" columnFormat: {\n") - .append(" month: 'ddd',\n") - .append(" week: ").append(firstMonth ? "'ddd M/d'" : "'ddd d.M.'").append(",\n") - .append(" day: ").append(firstMonth ? "'dddd M/d'" : "'dddd d.M.'").append("\n") - .append(" },\n") - .append(" year:").append(cal.get(Calendar.YEAR)).append(",\n") - .append(" month:").append(cal.get(Calendar.MONTH)).append(",\n") - .append(" date:").append(cal.get(Calendar.DAY_OF_MONTH)).append(",\n") - .append(" firstDay:").append(firstDay).append(",\n") - .append(" defaultView:'").append(fcC.getViewName()).append("',\n") - .append(" weekNumbers: true,\n") - .append(" editable: true,\n") - .append(" selectable: true,\n") - .append(" selectHelper: true,\n") - .append(" eventSources:["); + .append("jQuery(function() {\n") + .append(" jQuery('#").append(id).append("').fullCalendar( {\n") + .append(" header: {\n") + .append(" left: 'prev,print,next today',\n") + .append(" center: 'title',\n") + .append(" right: 'month,agendaWeek,agendaDay'\n") + .append(" },\n") + .append(" buttonText: {\n") + .append(" today: '").append(escapeJavaScript(translator.translate("cal.thisweek"))).append("',\n") + .append(" month: '").append(escapeJavaScript(translator.translate("cal.month"))).append("',\n") + .append(" day: '").append(escapeJavaScript(translator.translate("cal.day"))).append("',\n") + .append(" week: '").append(escapeJavaScript(translator.translate("cal.week"))).append("',\n") + .append(" print: '").append(escapeJavaScript(translator.translate("print"))).append("'\n") + .append(" },\n") + .append(" monthNames: ").append(getMonthLong(translator)).append(",\n") + .append(" monthNamesShort: ").append(getMonthShort(translator)).append(",\n") + .append(" dayNames: ").append(getDayLong(translator)).append(",\n") + .append(" dayNamesShort: ").append(getDayShort(translator)).append(",\n") + .append(" allDayText:'").append(translator.translate("cal.form.allday")).append("',\n") + .append(" axisFormat:'").append(ampm ? "h(:mm)tt" : "H.mm").append("',\n") + .append(" timeFormat:'").append(ampm ? "h(:mm)tt" : "H.mm").append("',\n") + .append(" titleFormat: {\n") + .append(" month: 'MMMM yyyy',\n") + .append(" week: ").append(firstMonth ? "\"MMM d[ yyyy]{ '—'[ MMM] d yyyy}\"" : "\"d. [ MMM] [ yyyy]{ '—' d. MMM yyyy}\"").append(",\n") + .append(" day: ").append(firstMonth ? "'dddd, MMM d, yyyy'" : "'dddd, d. MMM yyyy'").append("\n") + .append(" },\n") + .append(" columnFormat: {\n") + .append(" month: 'ddd',\n") + .append(" week: ").append(firstMonth ? "'ddd M/d'" : "'ddd d.M.'").append(",\n") + .append(" day: ").append(firstMonth ? "'dddd M/d'" : "'dddd d.M.'").append("\n") + .append(" },\n") + .append(" year:").append(cal.get(Calendar.YEAR)).append(",\n") + .append(" month:").append(cal.get(Calendar.MONTH)).append(",\n") + .append(" date:").append(cal.get(Calendar.DAY_OF_MONTH)).append(",\n") + .append(" firstDay:").append(firstDay).append(",\n") + .append(" defaultView:'").append(fcC.getViewName()).append("',\n") + .append(" weekNumbers: true,\n") + .append(" editable: true,\n") + .append(" selectable: true,\n") + .append(" selectHelper: true,\n") + .append(" eventSources:["); int count = 0; - for(KalendarRenderWrapper calWrapper: fcC.getKalendarRenderWrappers()) { - if(calWrapper.getKalendarConfig().isVis()) { + for(KalendarRenderWrapper calWrapper: fcC.getCalendars()) { + if(calWrapper.isVisible()) { String calId = calWrapper.getKalendar().getCalendarID(); - String color = calWrapper.getKalendarConfig().getCss(); + String color = calWrapper.getCssClass(); if(StringHelper.containsNonWhitespace(color) && color.startsWith("o_cal_")) { color = color.substring(6, color.length()); } @@ -128,38 +130,58 @@ public class FullCalendarComponentRenderer extends DefaultComponentRenderer { .append("}"); } } - sb.append(" ],\n") - .append(" eventAfterRender: function(event, element, view) {\n") - .append(" element.each(function(index, el) {\n") - .append(" jQuery(el).attr('id', 'o_cev_' + view.name + '_' + event.id);\n") - .append(" });\n") - .append(" },\n") - .append(" viewDisplay: function(view) {\n") - .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) - .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evChangeView',view.name,'start',view.start.getTime());\n") - .append(" },\n") - .append(" eventDrop: function(calEvent, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {\n") - .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) - .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evMove',calEvent.id,'dayDelta',dayDelta,'minuteDelta',minuteDelta,'allDay',allDay);\n") - .append(" },\n") - .append(" select: function(startDate, endDate, allDay, jsEvent, view) {\n") - .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) - .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evAdd','new','start',startDate.getTime(),'end',endDate.getTime(),'allDay',allDay);\n") - .append(" },\n") - .append(" eventClick: function(calEvent, jsEvent, view) {\n") - .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) - .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evSelect',calEvent.id,'evDomId','o_cev_' + view.name + '_' + calEvent.id);\n") - .append(" }\n") - .append(" });\n") - .append(" jQuery('.fc-header-left').append('<span class=\"fc-header-space\"></span><span id=\"").append(printId).append("\" class=\"fc-button fc-button-print fc-state-default fc-corner-left fc-corner-right\"><span>") - .append(translator.translate("print")).append("</span></span>');\n") - .append(" jQuery('.fc-button-print').click(function () {\n") - .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) - .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'print','print');\n") - .append(" });\n") - .append("});\n") - .append("/* ]]> */\n") - .append("</script>"); + sb.append(" ],\n") + .append(" eventAfterRender: function(event, element, view) {\n") + .append(" element.each(function(index, el) {\n") + .append(" jQuery(el).attr('id', 'o_cev_' + view.name + '_' + event.id);\n") + .append(" });\n") + .append(" },\n") + .append(" viewDisplay: function(view) {\n") + .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) + .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evChangeView',view.name,'start',view.start.getTime());\n") + .append(" },\n") + .append(" eventDrop: function(calEvent, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {\n") + .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) + .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evMove',calEvent.id,'dayDelta',dayDelta,'minuteDelta',minuteDelta,'allDay',allDay);\n") + .append(" },\n") + .append(" select: function(startDate, endDate, allDay, jsEvent, view) {\n") + .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) + .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evAdd','new','start',startDate.getTime(),'end',endDate.getTime(),'allDay',allDay);\n") + .append(" },\n") + .append(" eventClick: function(calEvent, jsEvent, view) {\n") + .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) + .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'evSelect',calEvent.id,'evDomId','o_cev_' + view.name + '_' + calEvent.id);\n") + .append(" }\n") + .append(" });\n") + //print button + .append(" jQuery('.fc-header-left').append('<span class=\"fc-header-space\"></span><span id=\"").append(printId).append("\" class=\"fc-button fc-button-print fc-state-default fc-corner-left fc-corner-right\">") + .append(" <span title=\"").append(translator.translate("print")).append("\"><i class=\"o_icon o_icon_print\"> </i></span></span>');\n") + .append(" jQuery('.fc-button-print').click(function () {\n") + .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) + .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'print','print');\n") + .append(" });\n"); + if(fcC.isConfigurationEnabled()) { + //config button + sb.append(" jQuery('.fc-header-left').append('<span class=\"fc-header-space\"></span><span id=\"").append(configId).append("\" class=\"fc-button fc-button-config fc-state-default fc-corner-left fc-corner-right\">") + .append(" <span title=\"").append(translator.translate("cal.configuration.tooltip")).append("\"><i class=\"o_icon o_icon_customize\"> </i></span></span>');\n") + .append(" jQuery('.fc-button-config').click(function () {\n") + .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) + .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'config','config');\n") + .append(" });\n"); + } + if(fcC.isAggregatedFeedEnabled()) { + //aggregated button + sb.append(" jQuery('.fc-header-left').append('<span class=\"fc-header-space\"></span><span id=\"").append(aggregatedId).append("\" class=\"fc-button fc-button-aggregate fc-state-default fc-corner-left fc-corner-right\">") + .append("<span title=\"").append(translator.translate("cal.icalfeed.aggregated.tooltip")).append("\"><i class=\"o_icon o_icon_rss\"> </i></span></span>');\n") + .append(" jQuery('.fc-button-aggregate').click(function () {\n") + .append(FormJSHelper.generateXHRFnCallVariables(rootForm, formId, 1)) + .append(" o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt,'aggregate','aggregate');\n") + .append(" });\n"); + } + + sb.append("});\n") + .append("/* ]]> */\n") + .append("</script>"); } private String getDayShort(Translator translator) { diff --git a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarElement.java b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarElement.java index 9e90c13b1b0..39941d4e309 100644 --- a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarElement.java +++ b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarElement.java @@ -24,10 +24,11 @@ import java.util.Date; import java.util.List; import org.olat.commons.calendar.model.KalendarEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIAddEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIMoveEvent; -import org.olat.commons.calendar.ui.events.KalendarGUIPrintEvent; -import org.olat.commons.calendar.ui.events.KalendarGUISelectEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIAddEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIFormEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIMoveEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIPrintEvent; +import org.olat.commons.calendar.ui.events.CalendarGUISelectEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.impl.FormItemImpl; import org.olat.core.gui.translator.Translator; @@ -43,22 +44,18 @@ import org.olat.core.util.ValidationStatus; */ public class FullCalendarElement extends FormItemImpl { - private FullCalendarComponent component; + private final FullCalendarComponent component; - public FullCalendarElement(UserRequest ureq, String name, List<KalendarRenderWrapper> calendarWrappers, - Translator translator, Boolean eventAlwaysVisible) { + public FullCalendarElement(UserRequest ureq, String name, + List<KalendarRenderWrapper> calendarWrappers, Translator translator) { super(name); - component = new FullCalendarComponent(ureq, this, name, calendarWrappers, translator, eventAlwaysVisible); + component = new FullCalendarComponent(ureq, this, name, calendarWrappers, translator); } public String getMapperUrl() { return component.getMapperUrl(); } - - public boolean isEventAlwaysVisible() { - return component.isEventAlwaysVisible(); - } public Date getFocusDate() { return component.getCurrentDate(); @@ -68,12 +65,32 @@ public class FullCalendarElement extends FormItemImpl { component.setCurrentDate(date); } - public KalendarRenderWrapper getKalendarRenderWrapper(String calendarID) { - return component.getKalendarRenderWrapper(calendarID); + public boolean isConfigurationEnabled() { + return component.isConfigurationEnabled(); + } + + public void setConfigurationEnabled(boolean configurationEnabled) { + component.setConfigurationEnabled(configurationEnabled); + } + + public boolean isAggregatedFeedEnabled() { + return component.isAggregatedFeedEnabled(); } - public void setKalendars(List<KalendarRenderWrapper> calendarWrappers) { - component.setKalendars(calendarWrappers); + public void setAggregatedFeedEnabled(boolean aggregatedFeedEnabled) { + component.setAggregatedFeedEnabled(aggregatedFeedEnabled); + } + + public KalendarRenderWrapper getCalendar(String calendarID) { + return component.getCalendar(calendarID); + } + + public void setCalendars(List<KalendarRenderWrapper> calendarWrappers) { + component.setCalendars(calendarWrappers); + } + + public void addCalendar(KalendarRenderWrapper calendarWrapper) { + component.addCalendar(calendarWrapper); } /** @@ -86,13 +103,21 @@ public class FullCalendarElement extends FormItemImpl { String movedEventId = getRootForm().getRequestParameter("evMove"); String changeViewName = getRootForm().getRequestParameter("evChangeView"); String print = getRootForm().getRequestParameter("print"); + String config = getRootForm().getRequestParameter("config"); + String aggregate = getRootForm().getRequestParameter("aggregate"); String dispatchuri = getRootForm().getRequestParameter("dispatchuri"); if("undefined".equals(dispatchuri)) { //to nothing } else if(StringHelper.containsNonWhitespace(print)) { String targetDomId = "fc_p" + component.getDispatchID(); - getRootForm().fireFormEvent(ureq, new KalendarGUIPrintEvent(targetDomId)); + getRootForm().fireFormEvent(ureq, new CalendarGUIPrintEvent(this, targetDomId)); + } else if(StringHelper.containsNonWhitespace(config)) { + String targetDomId = "fc_x" + component.getDispatchID(); + getRootForm().fireFormEvent(ureq, new CalendarGUIFormEvent(CalendarGUIFormEvent.CONFIGURE, this, targetDomId)); + } else if(StringHelper.containsNonWhitespace(aggregate)) { + String targetDomId = "fc_g" + component.getDispatchID(); + getRootForm().fireFormEvent(ureq, new CalendarGUIFormEvent(CalendarGUIFormEvent.AGGREGATED_FEED, this, targetDomId)); } else if(StringHelper.containsNonWhitespace(selectedEventId)) { String targetDomId = getRootForm().getRequestParameter("evDomId"); doSelect(ureq, selectedEventId, targetDomId); @@ -144,9 +169,9 @@ public class FullCalendarElement extends FormItemImpl { allDay = Boolean.FALSE; } - KalendarEvent event = component.getKalendarEvent(eventId); - KalendarRenderWrapper calWrapper = component.getKalendarRenderWrapperOf(eventId); - getRootForm().fireFormEvent(ureq, new KalendarGUIMoveEvent(this, event, calWrapper, day, minute, allDay)); + KalendarEvent event = component.getCalendarEvent(eventId); + KalendarRenderWrapper calWrapper = component.getCalendarByNormalizedId(eventId); + getRootForm().fireFormEvent(ureq, new CalendarGUIMoveEvent(this, event, calWrapper, day, minute, allDay)); } protected void doAdd(UserRequest ureq, String start, String end, String allDay) { @@ -159,13 +184,13 @@ public class FullCalendarElement extends FormItemImpl { endTime = Long.parseLong(end); } boolean allDayEvent = "true".equalsIgnoreCase(allDay); - getRootForm().fireFormEvent(ureq, new KalendarGUIAddEvent(this, null, new Date(startTime), new Date(endTime), allDayEvent)); + getRootForm().fireFormEvent(ureq, new CalendarGUIAddEvent(this, null, new Date(startTime), new Date(endTime), allDayEvent)); } protected void doSelect(UserRequest ureq, String eventId, String targetDomId) { - KalendarEvent event = component.getKalendarEvent(eventId); - KalendarRenderWrapper calWrapper = component.getKalendarRenderWrapperOf(eventId); - getRootForm().fireFormEvent(ureq, new KalendarGUISelectEvent(this, event, calWrapper, targetDomId)); + KalendarEvent event = component.getCalendarEvent(eventId); + KalendarRenderWrapper calWrapper = component.getCalendarByNormalizedId(eventId); + getRootForm().fireFormEvent(ureq, new CalendarGUISelectEvent(this, event, calWrapper, targetDomId)); } @Override diff --git a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarMapper.java b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarMapper.java index 788f11e8f4a..aebf650c7c0 100644 --- a/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarMapper.java +++ b/src/main/java/org/olat/commons/calendar/ui/components/FullCalendarMapper.java @@ -116,18 +116,24 @@ public class FullCalendarMapper implements Mapper { } private void collectKalendarEvents(JSONArray ja, String calendarId, Date from, Date to) throws JSONException { - KalendarRenderWrapper cal = fcC.getKalendarRenderWrapper(calendarId); + KalendarRenderWrapper cal = fcC.getCalendar(calendarId); + boolean privateEventsVisible = cal.isPrivateEventsVisible(); if(cal != null) { for(KalendarEvent event:cal.getKalendar().getEvents()) { + if(!privateEventsVisible && event.getClassification() == KalendarEvent.CLASS_PRIVATE) { + continue; + } + + boolean timeOnly = !privateEventsVisible && event.getClassification() == KalendarEvent.CLASS_X_FREEBUSY; if(isInRange(from, to, event)) { - JSONObject jsonEvent = getJSONEvent(event, cal); + JSONObject jsonEvent = getJSONEvent(event, cal, timeOnly); ja.put(jsonEvent); } if (StringHelper.containsNonWhitespace(event.getRecurrenceRule())) { TimeZone tz = CoreSpringFactory.getImpl(CalendarModule.class).getDefaultTimeZone(); List<KalendarRecurEvent> kalList = CalendarUtils.getRecurringDatesInPeriod (from, to, event, tz); for (KalendarRecurEvent recurEvent:kalList) { - JSONObject jsonEvent = getJSONEvent(recurEvent, cal); + JSONObject jsonEvent = getJSONEvent(recurEvent, cal, timeOnly); ja.put(jsonEvent); } } @@ -160,13 +166,18 @@ public class FullCalendarMapper implements Mapper { return false; } - private JSONObject getJSONEvent(KalendarEvent event, KalendarRenderWrapper cal) throws JSONException { + private JSONObject getJSONEvent(KalendarEvent event, KalendarRenderWrapper cal, boolean timeOnly) + throws JSONException { JSONObject jsonEvent = new JSONObject(); jsonEvent.put("id", FullCalendarComponent.normalizeId(event.getID())); - jsonEvent.put("title", event.getSubject()); + if(timeOnly) { + jsonEvent.put("title", ""); + } else { + jsonEvent.put("title", event.getSubject()); + } jsonEvent.put("allDay", new Boolean(event.isAllDayEvent())); - if(StringHelper.containsNonWhitespace(cal.getKalendarConfig().getCss())) { - jsonEvent.put("className", cal.getKalendarConfig().getCss()); + if(StringHelper.containsNonWhitespace(cal.getCssClass())) { + jsonEvent.put("className", cal.getCssClass()); } jsonEvent.put("editable", new Boolean(cal.getAccess() == KalendarRenderWrapper.ACCESS_READ_WRITE)); if(event.getBegin() != null) { diff --git a/src/main/java/org/olat/commons/calendar/ui/components/KalendarEventRenderWrapper.java b/src/main/java/org/olat/commons/calendar/ui/components/KalendarEventRenderWrapper.java index dde6b12080f..298934e0117 100644 --- a/src/main/java/org/olat/commons/calendar/ui/components/KalendarEventRenderWrapper.java +++ b/src/main/java/org/olat/commons/calendar/ui/components/KalendarEventRenderWrapper.java @@ -119,7 +119,7 @@ public class KalendarEventRenderWrapper { } public String getCssClass() { - return calendarWrapper.getKalendarConfig().getCss(); + return calendarWrapper.getCssClass(); } } diff --git a/src/main/java/org/olat/commons/calendar/ui/components/KalendarRenderWrapper.java b/src/main/java/org/olat/commons/calendar/ui/components/KalendarRenderWrapper.java index 878265da643..1a7c6a225f4 100644 --- a/src/main/java/org/olat/commons/calendar/ui/components/KalendarRenderWrapper.java +++ b/src/main/java/org/olat/commons/calendar/ui/components/KalendarRenderWrapper.java @@ -25,10 +25,16 @@ package org.olat.commons.calendar.ui.components; -import org.olat.commons.calendar.CalendarManagerFactory; +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.manager.ICalFileCalendarManager; +import org.olat.commons.calendar.model.CalendarKey; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarConfig; import org.olat.commons.calendar.ui.LinkProvider; +import org.olat.core.CoreSpringFactory; +import org.olat.core.helpers.Settings; +import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; public class KalendarRenderWrapper { @@ -48,14 +54,20 @@ public class KalendarRenderWrapper { public static final int ACCESS_READ_WRITE = 0; public static final int ACCESS_READ_ONLY = 1; + private String displayName; private Kalendar kalendar; + private LinkProvider linkProvider; + private int access = ACCESS_READ_ONLY; private boolean imported = false; private boolean subscribed = false; - private KalendarConfig kalendarConfig = new KalendarConfig(); - private LinkProvider linkProvider; - + private boolean visible; + private boolean inAggregatedFeed; + private boolean privateEventsVisible; + private String cssClass; + private String token; + /** * Configure a calendar for rendering. Set default values * for calendar color (BLUE) and access (READ_ONLY). @@ -64,26 +76,69 @@ public class KalendarRenderWrapper { * @param calendarColor * @param access */ - public KalendarRenderWrapper(Kalendar kalendar) { + public KalendarRenderWrapper(Kalendar kalendar, String displayName) { this.kalendar = kalendar; + this.displayName = displayName; + setConfiguration(null); + } + + public KalendarRenderWrapper(Kalendar kalendar, CalendarUserConfiguration config, String displayName) { + this.kalendar = kalendar; + this.displayName = displayName; + setConfiguration(config); + } + + public void setConfiguration(CalendarUserConfiguration config) { + if(config == null) { + visible = true; + inAggregatedFeed = true; + cssClass = CALENDAR_COLOR_BLUE; + } else { + visible = config.isVisible(); + inAggregatedFeed = config.isInAggregatedFeed(); + token = config.getToken(); + if(StringHelper.containsNonWhitespace(config.getCssClass())) { + cssClass = config.getCssClass(); + } + } + } + + public CalendarKey getCalendarKey() { + return new CalendarKey(kalendar.getCalendarID(), kalendar.getType()); + } + + public String getDisplayName() { + return displayName; } - public void setAccess(int access) { - this.access = access; + public void setDisplayName(String displayName) { + this.displayName = displayName; } public int getAccess() { return access; } - - public void setImported(boolean imported) { - this.imported = imported; + + public void setAccess(int access) { + this.access = access; } public boolean isImported() { return imported; } - + + public void setImported(boolean imported) { + this.imported = imported; + } + + public boolean isPrivateEventsVisible() { + return privateEventsVisible; + } + + public void setPrivateEventsVisible(boolean privateEventsVisible) { + this.privateEventsVisible = privateEventsVisible; + } + public boolean isSubscribed() { return subscribed; } @@ -97,16 +152,40 @@ public class KalendarRenderWrapper { } public Kalendar reloadKalendar() { - kalendar = CalendarManagerFactory.getInstance().getCalendarManager().getCalendar(this.getKalendar().getType(), this.getKalendar().getCalendarID()); + kalendar = CoreSpringFactory.getImpl(CalendarManager.class).getCalendar(this.getKalendar().getType(), this.getKalendar().getCalendarID()); return kalendar; } - public KalendarConfig getKalendarConfig() { - return kalendarConfig; + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public boolean isInAggregatedFeed() { + return inAggregatedFeed; + } + + public void setInAggregatedFeed(boolean inAggregatedFeed) { + this.inAggregatedFeed = inAggregatedFeed; } - public void setKalendarConfig(KalendarConfig calendarConfig) { - this.kalendarConfig = calendarConfig; + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getCssClass() { + return cssClass; + } + + public void setCssClass(String cssClass) { + this.cssClass = cssClass; } /** @@ -122,4 +201,37 @@ public class KalendarRenderWrapper { public void setLinkProvider(LinkProvider linkProvider) { this.linkProvider = linkProvider; } + + public String getFeedUrl(Identity identity) { + if(token == null) { + return null; + } + + String calendarType = kalendar.getType(); + String calendarId = kalendar.getCalendarID(); + if (calendarType.equals(ICalFileCalendarManager.TYPE_USER)) { + return Settings.getServerContextPathURI() + "/ical" + "/" + calendarType + "/" + identity.getName() + "/" + token + ".ics"; + } else { + return Settings.getServerContextPathURI() + "/ical" + "/" + calendarType + "/" + identity.getName() + "/" + token + "/" + calendarId + ".ics"; + } + } + + @Override + public int hashCode() { + return kalendar.getCalendarID().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + + if(obj instanceof KalendarRenderWrapper) { + KalendarRenderWrapper wrapper = (KalendarRenderWrapper)obj; + return wrapper.getCalendarKey().equals(getCalendarKey()); + + } + return false; + } } diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIAddEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIAddEvent.java similarity index 90% rename from src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIAddEvent.java rename to src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIAddEvent.java index 6d9d5a77389..f3024e54651 100644 --- a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIAddEvent.java +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIAddEvent.java @@ -30,7 +30,7 @@ import java.util.Date; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.impl.FormEvent; -public class KalendarGUIAddEvent extends FormEvent { +public class CalendarGUIAddEvent extends FormEvent { private static final long serialVersionUID = 7056011141923427419L; public static final String CMD_ADD = "acalevent"; @@ -38,7 +38,7 @@ public class KalendarGUIAddEvent extends FormEvent { private Date startDate, endDate; private boolean allDayEvent = false; - public KalendarGUIAddEvent(String calendarID, Date startDate) { + public CalendarGUIAddEvent(String calendarID, Date startDate) { super(CMD_ADD, null); this.calendarID = calendarID; this.startDate = startDate; @@ -50,7 +50,7 @@ public class KalendarGUIAddEvent extends FormEvent { * @param startDate * @param allDayEvent When true, the new event should be an all-day event */ - public KalendarGUIAddEvent(String calendarID, Date startDate, boolean allDayEvent) { + public CalendarGUIAddEvent(String calendarID, Date startDate, boolean allDayEvent) { super(CMD_ADD, null); this.calendarID = calendarID; this.startDate = startDate; @@ -63,7 +63,7 @@ public class KalendarGUIAddEvent extends FormEvent { * @param startDate * @param allDayEvent When true, the new event should be an all-day event */ - public KalendarGUIAddEvent(FormItem item, String calendarID, Date startDate, Date endDate, boolean allDayEvent) { + public CalendarGUIAddEvent(FormItem item, String calendarID, Date startDate, Date endDate, boolean allDayEvent) { super(CMD_ADD, item); this.calendarID = calendarID; this.startDate = startDate; diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIEditEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIEditEvent.java similarity index 93% rename from src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIEditEvent.java rename to src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIEditEvent.java index fb03ced4aed..b68defce43d 100644 --- a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIEditEvent.java +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIEditEvent.java @@ -29,14 +29,14 @@ import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.gui.components.form.flexible.impl.FormEvent; -public class KalendarGUIEditEvent extends FormEvent { +public class CalendarGUIEditEvent extends FormEvent { private static final long serialVersionUID = -3539867971891194310L; public static final String CMD_EDIT = "ecalevent"; private KalendarEvent event; private KalendarRenderWrapper calendarWrapper; - public KalendarGUIEditEvent(KalendarEvent event, KalendarRenderWrapper calendarWrapper) { + public CalendarGUIEditEvent(KalendarEvent event, KalendarRenderWrapper calendarWrapper) { super(CMD_EDIT, null); this.event = event; this.calendarWrapper = calendarWrapper; diff --git a/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIEvent.java new file mode 100644 index 00000000000..33210ef3adf --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIEvent.java @@ -0,0 +1,42 @@ +/** + * <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.commons.calendar.ui.events; + +import org.olat.core.gui.control.Event; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarGUIEvent extends Event { + + private static final long serialVersionUID = 4101088530001012299L; + + public static final String IMPORT_BY_FILE = "import-file"; + public static final String IMPORT_SYNCHRONIZED_URL = "import-url"; + public static final String DELETE_TOKEN = "delete-token"; + public static final String DELETE_CALENDAR = "delete-calendar"; + + public CalendarGUIEvent(String cmd) { + super(cmd); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIFormEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIFormEvent.java new file mode 100644 index 00000000000..2f8fd66c573 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIFormEvent.java @@ -0,0 +1,48 @@ +/** + * <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.commons.calendar.ui.events; + +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarGUIFormEvent extends FormEvent { + + private static final long serialVersionUID = 4101088530001012299L; + + public static final String CONFIGURE = "configure"; + public static final String AGGREGATED_FEED = "aggregated-feed"; + + private final String targetDomId; + + public CalendarGUIFormEvent(String cmd, FormItem source, String targetDomId) { + super(cmd, source); + this.targetDomId = targetDomId; + } + + public String getTargetDomId() { + return targetDomId; + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIImportEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIImportEvent.java new file mode 100644 index 00000000000..7b29f48e729 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIImportEvent.java @@ -0,0 +1,46 @@ +/** + * <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.commons.calendar.ui.events; + +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.control.Event; + +/** + * + * <h3>Description:</h3> + * <p> + * Initial Date: 4 feb. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class CalendarGUIImportEvent extends Event { + + private static final long serialVersionUID = 6716347235221049088L; + public static final String CMD_IMPORT = "acalevent"; + private final KalendarRenderWrapper calendar; + + public CalendarGUIImportEvent(KalendarRenderWrapper calendar) { + super(CMD_IMPORT); + this.calendar = calendar; + } + + public KalendarRenderWrapper getCalendar() { + return calendar; + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarModifiedEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIModifiedEvent.java similarity index 90% rename from src/main/java/org/olat/commons/calendar/ui/events/KalendarModifiedEvent.java rename to src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIModifiedEvent.java index 4d88768729a..3d7a84ef6da 100644 --- a/src/main/java/org/olat/commons/calendar/ui/events/KalendarModifiedEvent.java +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIModifiedEvent.java @@ -28,17 +28,17 @@ package org.olat.commons.calendar.ui.events; import org.olat.commons.calendar.model.Kalendar; import org.olat.core.util.event.MultiUserEvent; -public class KalendarModifiedEvent extends MultiUserEvent { +public class CalendarGUIModifiedEvent extends MultiUserEvent { private static final long serialVersionUID = 8926945159054996511L; private String type; private String calendarId; - public KalendarModifiedEvent() { + public CalendarGUIModifiedEvent() { super("KalendarModified"); } - public KalendarModifiedEvent(Kalendar cal) { + public CalendarGUIModifiedEvent(Kalendar cal) { this(); this.type = cal.getType(); this.calendarId = cal.getCalendarID(); diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIMoveEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIMoveEvent.java similarity index 94% rename from src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIMoveEvent.java rename to src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIMoveEvent.java index 724d79990b1..120c9f488da 100644 --- a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIMoveEvent.java +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIMoveEvent.java @@ -30,7 +30,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class KalendarGUIMoveEvent extends FormEvent { +public class CalendarGUIMoveEvent extends FormEvent { private static final long serialVersionUID = -5910033103406371704L; public static final String CMD_MOVE = "mvcalevent"; @@ -40,7 +40,7 @@ public class KalendarGUIMoveEvent extends FormEvent { private KalendarEvent event; private KalendarRenderWrapper calendarWrapper; - public KalendarGUIMoveEvent(FormItem item, KalendarEvent event, KalendarRenderWrapper calendarWrapper, + public CalendarGUIMoveEvent(FormItem item, KalendarEvent event, KalendarRenderWrapper calendarWrapper, Long dayDelta, Long minuteDelta, Boolean allDay) { super(CMD_MOVE, item); this.allDay = allDay; diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIPrintEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIPrintEvent.java similarity index 85% rename from src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIPrintEvent.java rename to src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIPrintEvent.java index e6b2637811f..23e80ab730b 100644 --- a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIPrintEvent.java +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIPrintEvent.java @@ -21,6 +21,7 @@ package org.olat.commons.calendar.ui.events; import java.util.Date; +import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.impl.FormEvent; /** @@ -29,7 +30,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class KalendarGUIPrintEvent extends FormEvent { +public class CalendarGUIPrintEvent extends FormEvent { private static final long serialVersionUID = -3476226273509422030L; public static final String CMD_PRINT = "printcalevent"; @@ -38,11 +39,11 @@ public class KalendarGUIPrintEvent extends FormEvent { private Date to; private String targetDomId; - public KalendarGUIPrintEvent(String targetDomId) { - super(CMD_PRINT, null); + public CalendarGUIPrintEvent(FormItem source, String targetDomId) { + super(CMD_PRINT, source); this.targetDomId = targetDomId; } - public KalendarGUIPrintEvent(Date from, Date to) { + public CalendarGUIPrintEvent(Date from, Date to) { super(CMD_PRINT, null); this.from = from; this.to = to; diff --git a/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIRemoveEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIRemoveEvent.java new file mode 100644 index 00000000000..c73b2058cf2 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUIRemoveEvent.java @@ -0,0 +1,45 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.commons.calendar.ui.events; + +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.control.Event; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarGUIRemoveEvent extends Event { + + private static final long serialVersionUID = -7248432770768130755L; + public static final String CMD_REMOVE = "rmcla"; + private final KalendarRenderWrapper calendar; + + public CalendarGUIRemoveEvent(KalendarRenderWrapper calendar) { + super(CMD_REMOVE); + this.calendar = calendar; + } + + public KalendarRenderWrapper getCalendar() { + return calendar; + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUISelectEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUISelectEvent.java similarity index 93% rename from src/main/java/org/olat/commons/calendar/ui/events/KalendarGUISelectEvent.java rename to src/main/java/org/olat/commons/calendar/ui/events/CalendarGUISelectEvent.java index 200d7a0b97b..7dfd40cb30b 100644 --- a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUISelectEvent.java +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUISelectEvent.java @@ -30,7 +30,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class KalendarGUISelectEvent extends FormEvent { +public class CalendarGUISelectEvent extends FormEvent { private static final long serialVersionUID = 2410000447684587428L; public static final String CMD_SELECT = "scalevent"; @@ -39,7 +39,7 @@ public class KalendarGUISelectEvent extends FormEvent { private KalendarEvent event; private KalendarRenderWrapper calendarWrapper; - public KalendarGUISelectEvent(FormItem item, KalendarEvent event, KalendarRenderWrapper calendarWrapper, String targetDomId) { + public CalendarGUISelectEvent(FormItem item, KalendarEvent event, KalendarRenderWrapper calendarWrapper, String targetDomId) { super(CMD_SELECT, item); this.targetDomId = targetDomId; this.event = event; diff --git a/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUISettingEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUISettingEvent.java new file mode 100644 index 00000000000..82c530a5da9 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/events/CalendarGUISettingEvent.java @@ -0,0 +1,45 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.commons.calendar.ui.events; + +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.control.Event; + +/** + * + * Initial date: 26.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarGUISettingEvent extends Event { + + private static final long serialVersionUID = -5821116764500481869L; + public static final String CMD_CHANGE = "chgsettingcla"; + private final KalendarRenderWrapper calendar; + + public CalendarGUISettingEvent(KalendarRenderWrapper calendar) { + super(CMD_CHANGE); + this.calendar = calendar; + } + + public KalendarRenderWrapper getCalendar() { + return calendar; + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIImportEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIImportEvent.java deleted file mode 100644 index 0a6915bf910..00000000000 --- a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIImportEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.commons.calendar.ui.events; - -import org.olat.core.gui.control.Event; - -/** - * - * <h3>Description:</h3> - * <p> - * Initial Date: 4 feb. 2011 <br> - * @author srosse, stephane.rosse@frentix.com, www.frentix.com - */ -public class KalendarGUIImportEvent extends Event { - - private static final long serialVersionUID = 6716347235221049088L; - public static final String CMD_IMPORT = "acalevent"; - private String calendarID; - - public KalendarGUIImportEvent(String calendarID) { - super(CMD_IMPORT); - this.calendarID = calendarID; - } - - public String getCalendarID() { - return calendarID; - } -} 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 80543f448eb..8282f853a4e 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 @@ -92,6 +92,9 @@ <class>org.olat.basesecurity.model.GroupMembershipImpl</class> <class>org.olat.basesecurity.model.UserProperty</class> <class>org.olat.core.dispatcher.mapper.model.PersistedMapper</class> + <class>org.olat.commons.calendar.model.ImportedCalendar</class> + <class>org.olat.commons.calendar.model.ImportedToCalendar</class> + <class>org.olat.commons.calendar.model.CalendarUserConfiguration</class> <class>org.olat.core.commons.services.notifications.model.SubscriberImpl</class> <class>org.olat.core.commons.services.notifications.model.PublisherImpl</class> <class>org.olat.core.commons.services.taskexecutor.model.PersistentTask</class> diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml b/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml index 35b93b4403f..c37251cb4d0 100644 --- a/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml +++ b/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml @@ -79,7 +79,7 @@ <prop key="hibernate.connection.url">jdbc:postgresql://${db.host}:${db.host.port}/${db.name}</prop> <prop key="hibernate.connection.username">${db.user}</prop> <prop key="hibernate.connection.password">${db.pass}</prop> - <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop> + <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop> </props> </constructor-arg> <property name="addMoreProperties" ref="hikariHibernateProperties" /> @@ -90,7 +90,7 @@ <props> <!-- Using datasource of hibernate --> <prop key="hibernate.connection.datasource">${db.jndi}</prop> - <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop> + <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop> </props> </constructor-arg> <property name="addMoreProperties" ref="hibernateProps" /> diff --git a/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml b/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml index 84b11a62c11..ebad9230977 100644 --- a/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml +++ b/src/main/java/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml @@ -14,7 +14,6 @@ <bean id="org.olat.course.nodes.ta.ReturnboxFileUploadNotificationHandler" class="org.olat.course.nodes.ta.ReturnboxFileUploadNotificationHandler"/> <bean id="org.olat.course.nodes.ta.SolutionFileUploadNotificationHandler" class="org.olat.course.nodes.ta.SolutionFileUploadNotificationHandler"/> <bean id="org.olat.modules.wiki.WikiPageChangeOrCreateNotificationHandler" class="org.olat.modules.wiki.WikiPageChangeOrCreateNotificationHandler" /> - <bean id="org.olat.commons.calendar.CalendarNotificationHandler" class="org.olat.commons.calendar.CalendarNotificationHandler" /> <bean id="org.olat.user.notification.NewUsersNotificationHandler" class="org.olat.user.notification.NewUsersNotificationHandler" /> <bean id="org.olat.commons.info.notification.InfoMessageNotificationHandler" class="org.olat.commons.info.notification.InfoMessageNotificationHandler" /> diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/ExtendedFlexiTableSearchController.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/ExtendedFlexiTableSearchController.java index 016fa22a17e..80761dcac6b 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/ExtendedFlexiTableSearchController.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/ExtendedFlexiTableSearchController.java @@ -33,7 +33,7 @@ import org.olat.core.gui.control.Controller; public interface ExtendedFlexiTableSearchController extends Controller { /** - * Enable or disable the extened search controller. If disabled, + * Enable or disable the extended search controller. If disabled, * the controller doesn't catch submit event, or do the validation */ public void setEnabled(boolean enable); diff --git a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java index b6034245da6..1dfcc787f16 100644 --- a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java +++ b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java @@ -98,8 +98,8 @@ public class CloseableCalloutWindowController extends BasicController { if (ajax) { final Panel guiMsgPlace = new Panel("guimessage_place"); calloutVC = new VelocityContainer("closeablewrapper", velocity_root + "/callout.html", null, this) { - public void validate(UserRequest ureq, ValidationResult vr) { - super.validate(ureq, vr); + public void validate(UserRequest uureq, ValidationResult vr) { + super.validate(uureq, vr); // just before rendering, we need to tell the windowbackoffice that we are a favorite for accepting gui-messages. // the windowbackoffice doesn't know about guimessages, it is only a container that keeps them for one render cycle WindowBackOffice wbo = getWindowControl().getWindowBackOffice(); diff --git a/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java b/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java index 6cc7743c642..56534ab18c7 100644 --- a/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java +++ b/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java @@ -28,7 +28,6 @@ package org.olat.core.gui.render.velocity; import java.io.Closeable; import java.io.IOException; -import java.io.InputStream; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -37,7 +36,6 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.contextHelp.ContextHelpModule; @@ -52,8 +50,6 @@ import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.gui.util.CSSHelper; import org.olat.core.helpers.Settings; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; import org.olat.core.util.ArrayHelper; import org.olat.core.util.CodeHelper; import org.olat.core.util.Formatter; @@ -63,13 +59,11 @@ import org.olat.core.util.filter.FilterFactory; import org.olat.core.util.filter.impl.OWASPAntiSamyXSSFilter; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.i18n.I18nModule; -import org.olat.login.LoginAuthprovidersController; /** * @author Felix Jost */ public class VelocityRenderDecorator implements Closeable { - private static final OLog log = Tracing.createLoggerFor(VelocityRenderDecorator.class); public static final String PARAM_CHELP_BUNDLE = "chelpbundle"; private VelocityContainer vc; diff --git a/src/main/java/org/olat/core/util/prefs/db/DbStorage.java b/src/main/java/org/olat/core/util/prefs/db/DbStorage.java index 2968e76aab7..d6732f8a059 100644 --- a/src/main/java/org/olat/core/util/prefs/db/DbStorage.java +++ b/src/main/java/org/olat/core/util/prefs/db/DbStorage.java @@ -55,7 +55,10 @@ public class DbStorage implements PreferencesStorage { static final String USER_PROPERTY_KEY = "v2guipreferences"; - private XStream xstream = XStreamHelper.createXStreamInstance(); + private static final XStream xstream = XStreamHelper.createXStreamInstance(); + static { + xstream.ignoreUnknownElements(); + } @Override public Preferences getPreferencesFor(Identity identity, boolean useTransientPreferences) { diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index 7056c821139..b093e154444 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -43,8 +43,7 @@ import java.util.zip.ZipOutputStream; import org.apache.poi.util.IOUtils; import org.olat.admin.quota.QuotaConstants; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.notification.CalendarNotificationManager; +import org.olat.commons.calendar.CalendarNotificationManager; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; @@ -370,7 +369,7 @@ public class CourseFactory extends BasicManager { CoursePropertyManager propertyManager = PersistingCoursePropertyManager.getInstance(res); propertyManager.deleteAllCourseProperties(); // delete course calendar - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); calManager.deleteCourseCalendar(res); // delete IM messages CoreSpringFactory.getImpl(InstantMessagingService.class).deleteMessages(res); @@ -396,7 +395,7 @@ public class CourseFactory extends BasicManager { */ private static void clearCalenderSubscriptions(OLATResourceable res) { //set Publisher state to 1 (= ressource is deleted) for all calendars of the course - CalendarManager calMan = CalendarManagerFactory.getInstance().getCalendarManager(); + CalendarManager calMan = CoreSpringFactory.getImpl(CalendarManager.class); CalendarNotificationManager notificationManager = CoreSpringFactory.getImpl(CalendarNotificationManager.class); NotificationsManager nfm = NotificationsManager.getInstance(); CourseGroupManager courseGroupManager = PersistingCourseGroupManager.getInstance(res); diff --git a/src/main/java/org/olat/course/config/ui/CourseOptionsController.java b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java index d9d52b96f32..599d42d58b5 100644 --- a/src/main/java/org/olat/course/config/ui/CourseOptionsController.java +++ b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java @@ -23,7 +23,7 @@ import java.util.List; import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.CalendarModule; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -362,7 +362,7 @@ public class CourseOptionsController extends FormBasicController { ThreadLocalUserActivityLogger.log(loggingAction, getClass()); CoordinatorManager.getInstance().getCoordinator().getEventBus() - .fireEventToListenersOf(new KalendarModifiedEvent(), OresHelper.lookupType(CalendarManager.class)); + .fireEventToListenersOf(new CalendarGUIModifiedEvent(), OresHelper.lookupType(CalendarManager.class)); CoordinatorManager.getInstance().getCoordinator().getEventBus() .fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.calendar, course.getResourceableId()), course); } diff --git a/src/main/java/org/olat/course/nodes/CalCourseNode.java b/src/main/java/org/olat/course/nodes/CalCourseNode.java index a269436a340..de04a9d9a6c 100644 --- a/src/main/java/org/olat/course/nodes/CalCourseNode.java +++ b/src/main/java/org/olat/course/nodes/CalCourseNode.java @@ -120,7 +120,7 @@ public class CalCourseNode extends AbstractAccessableCourseNode { public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) { updateModuleConfigDefaults(false); - CalRunController calCtlr = new CalRunController(wControl, ureq, this, userCourseEnv.getCourseEnvironment(), ne); + CalRunController calCtlr = new CalRunController(wControl, ureq, this, userCourseEnv, ne); Controller wrapperCtrl = TitledWrapperHelper.getWrapper(ureq, wControl, calCtlr, this, "o_cal_icon"); return new NodeRunConstructionResult(wrapperCtrl); } diff --git a/src/main/java/org/olat/course/nodes/cal/CalRunController.java b/src/main/java/org/olat/course/nodes/cal/CalRunController.java index 9a5fde2acdf..c1fd3bdb6a6 100644 --- a/src/main/java/org/olat/course/nodes/cal/CalRunController.java +++ b/src/main/java/org/olat/course/nodes/cal/CalRunController.java @@ -37,10 +37,9 @@ import org.olat.core.gui.control.generic.clone.CloneLayoutControllerCreatorCallb import org.olat.core.id.OLATResourceable; import org.olat.core.id.context.ContextEntry; import org.olat.course.CourseFactory; -import org.olat.course.ICourse; import org.olat.course.nodes.CalCourseNode; -import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.NodeEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.modules.ModuleConfiguration; /** @@ -53,9 +52,6 @@ import org.olat.modules.ModuleConfiguration; */ public class CalRunController extends BasicController { - private final VelocityContainer mainVC; - - private CourseEnvironment courseEnv; private CourseCalendarController calCtr; private ModuleConfiguration config; private CloneController cloneCtr; @@ -65,17 +61,15 @@ public class CalRunController extends BasicController { * @param wControl * @param ureq * @param calCourseNode - * @param cenv + * @param courseEnv */ - public CalRunController(WindowControl wControl, UserRequest ureq, CalCourseNode calCourseNode, CourseEnvironment cenv, NodeEvaluation ne) { + public CalRunController(WindowControl wControl, UserRequest ureq, CalCourseNode calCourseNode, UserCourseEnvironment courseEnv, NodeEvaluation ne) { super(ureq, wControl); - this.courseEnv = cenv; this.config = calCourseNode.getModuleConfiguration(); - mainVC = createVelocityContainer("run"); + VelocityContainer mainVC = createVelocityContainer("run"); - ICourse course = CourseFactory.loadCourse(cenv.getCourseGroupManager().getCourseEntry()); - CourseCalendars myCal = CourseCalendars.createCourseCalendarsWrapper(ureq, wControl, course, ne); - calCtr = new CourseCalendarController(ureq, wControl, myCal, course, ne); + CourseCalendars myCal = CourseCalendars.createCourseCalendarsWrapper(ureq, wControl, courseEnv, ne); + calCtr = new CourseCalendarController(ureq, wControl, myCal, courseEnv, ne); boolean focused = false; ContextEntry ce = wControl.getBusinessControl().popLauncherContextEntry(); @@ -110,7 +104,7 @@ public class CalRunController extends BasicController { // wrapp in column layout, popup window needs a layout controller Controller ctr = contentControllerCreator.createController(lureq, lwControl); LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(lureq, lwControl, ctr); - layoutCtr.setCustomCSS(CourseFactory.getCustomCourseCss(lureq.getUserSession(), courseEnv)); + layoutCtr.setCustomCSS(CourseFactory.getCustomCourseCss(lureq.getUserSession(), courseEnv.getCourseEnvironment())); layoutCtr.addDisposableChildController(ctr); return layoutCtr; } @@ -123,24 +117,12 @@ public class CalRunController extends BasicController { putInitialPanel(mainVC); } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) - */ + @Override public void event(UserRequest ureq, Component source, Event event) { //no events yet } - /** - * - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) - */ - public void event(UserRequest ureq, Controller source, Event event) { - // - } - /** - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ + @Override protected void doDispose() { if(calCtr != null){ calCtr.dispose(); diff --git a/src/main/java/org/olat/course/nodes/cal/CourseCalendarController.java b/src/main/java/org/olat/course/nodes/cal/CourseCalendarController.java index 4330e3b79b7..ad23f09b2bc 100644 --- a/src/main/java/org/olat/course/nodes/cal/CourseCalendarController.java +++ b/src/main/java/org/olat/course/nodes/cal/CourseCalendarController.java @@ -25,7 +25,7 @@ import java.util.List; import org.olat.commons.calendar.ui.WeeklyCalendarController; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.control.Controller; @@ -33,9 +33,9 @@ import org.olat.core.gui.control.DefaultController; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.clone.CloneableController; -import org.olat.core.id.OLATResourceable; import org.olat.course.run.calendar.CourseCalendarSubscription; import org.olat.course.run.userview.NodeEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; /** * @@ -47,23 +47,24 @@ import org.olat.course.run.userview.NodeEvaluation; */ public class CourseCalendarController extends DefaultController implements CloneableController { - private WeeklyCalendarController calendarController; + private final WeeklyCalendarController calendarController; private KalendarRenderWrapper courseKalendarWrapper; private CourseCalendarSubscription calendarSubscription; private NodeEvaluation nodeEvaluation; - private OLATResourceable ores; + private UserCourseEnvironment courseEnv; private List<KalendarRenderWrapper> calendars; public CourseCalendarController(UserRequest ureq, WindowControl wControl, CourseCalendars myCal, - OLATResourceable course, NodeEvaluation ne) { + UserCourseEnvironment courseEnv, NodeEvaluation ne) { super(wControl); - this.ores = course; + this.courseEnv = courseEnv; this.nodeEvaluation = ne; calendars = myCal.getCalendars(); courseKalendarWrapper = myCal.getCourseKalendarWrapper(); - calendarController = new WeeklyCalendarController(ureq, wControl, calendars, WeeklyCalendarController.CALLER_COURSE, true); + calendarController = new WeeklyCalendarController(ureq, wControl, calendars, + WeeklyCalendarController.CALLER_COURSE, false); setInitialComponent(calendarController.getInitialComponent()); } @@ -75,9 +76,7 @@ public class CourseCalendarController extends DefaultController implements Clone return courseKalendarWrapper; } - public OLATResourceable getOres() { - return ores; - } + public void setFocus(Date date) { calendarController.setFocus(date); @@ -94,8 +93,8 @@ public class CourseCalendarController extends DefaultController implements Clone @Override public void event(UserRequest ureq, Controller source, Event event) { - if (event instanceof KalendarModifiedEvent) { - CourseCalendars myCal = CourseCalendars.createCourseCalendarsWrapper(ureq, getWindowControl(), ores, nodeEvaluation); + if (event instanceof CalendarGUIModifiedEvent) { + CourseCalendars myCal = CourseCalendars.createCourseCalendarsWrapper(ureq, getWindowControl(), courseEnv, nodeEvaluation); calendars = myCal.getCalendars(); courseKalendarWrapper = myCal.getCourseKalendarWrapper(); calendarController.setCalendars(calendars); @@ -112,7 +111,7 @@ public class CourseCalendarController extends DefaultController implements Clone CourseCalendars myCal = new CourseCalendars(courseKalendarWrapper, calendars); Date focus = calendarController.getFocus(); - CourseCalendarController ctrl = new CourseCalendarController(ureq, wControl, myCal, ores, nodeEvaluation); + CourseCalendarController ctrl = new CourseCalendarController(ureq, wControl, myCal, courseEnv, nodeEvaluation); ctrl.calendarController.setFocus(focus); return ctrl; } diff --git a/src/main/java/org/olat/course/nodes/cal/CourseCalendarPeekViewController.java b/src/main/java/org/olat/course/nodes/cal/CourseCalendarPeekViewController.java index 881f1f838a3..99085d03229 100644 --- a/src/main/java/org/olat/course/nodes/cal/CourseCalendarPeekViewController.java +++ b/src/main/java/org/olat/course/nodes/cal/CourseCalendarPeekViewController.java @@ -39,8 +39,6 @@ import org.olat.core.gui.components.table.TableGuiConfiguration; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; -import org.olat.course.CourseFactory; -import org.olat.course.ICourse; import org.olat.course.nodes.CalCourseNode; import org.olat.course.run.userview.NodeEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; @@ -80,8 +78,7 @@ public class CourseCalendarPeekViewController extends BasicController { } private void init(UserRequest ureq, CalCourseNode courseNode, UserCourseEnvironment courseEnv, NodeEvaluation ne) { - ICourse course = CourseFactory.loadCourse(courseEnv.getCourseEnvironment().getCourseResourceableId()); - CourseCalendars myCal = CourseCalendars.createCourseCalendarsWrapper(ureq, getWindowControl(), course, ne); + CourseCalendars myCal = CourseCalendars.createCourseCalendarsWrapper(ureq, getWindowControl(), courseEnv, ne); Date refDate; ModuleConfiguration config = courseNode.getModuleConfiguration(); diff --git a/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java b/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java index 746d12cedd0..bf3d9fa1c07 100644 --- a/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java +++ b/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java @@ -22,19 +22,20 @@ package org.olat.course.nodes.cal; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import org.olat.collaboration.CollaborationTools; import org.olat.collaboration.CollaborationToolsFactory; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; +import org.olat.commons.calendar.model.CalendarKey; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarConfig; import org.olat.commons.calendar.ui.LinkProvider; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.WindowControl; import org.olat.core.id.Identity; -import org.olat.core.id.OLATResourceable; import org.olat.core.id.Roles; import org.olat.course.CourseFactory; import org.olat.course.ICourse; @@ -44,6 +45,7 @@ import org.olat.course.nodes.CalCourseNode; import org.olat.course.run.calendar.CourseCalendarSubscription; import org.olat.course.run.calendar.CourseLinkProviderController; import org.olat.course.run.userview.NodeEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.repository.RepositoryManager; @@ -90,10 +92,10 @@ public class CourseCalendars { * @param ne * @return */ - public static KalendarRenderWrapper getCourseCalendarWrapper(UserRequest ureq, OLATResourceable ores, NodeEvaluation ne) { - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); + public static KalendarRenderWrapper getCourseCalendarWrapper(UserRequest ureq, UserCourseEnvironment courseEnv, NodeEvaluation ne) { + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); // add course calendar - ICourse course = CourseFactory.loadCourse(ores); + ICourse course = CourseFactory.loadCourse(courseEnv.getCourseEnvironment().getCourseResourceableId()); KalendarRenderWrapper courseKalendarWrapper = calendarManager.getCourseCalendar(course); CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager(); Identity identity = ureq.getIdentity(); @@ -104,22 +106,24 @@ public class CourseCalendars { if (isPrivileged) { courseKalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); + courseKalendarWrapper.setPrivateEventsVisible(true); } else { courseKalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY); + courseKalendarWrapper.setPrivateEventsVisible(courseEnv.isAdmin() || courseEnv.isCoach() || courseEnv.isParticipant()); } - KalendarConfig config = calendarManager.findKalendarConfigForIdentity(courseKalendarWrapper.getKalendar(), ureq); + CalendarUserConfiguration config = calendarManager.findCalendarConfigForIdentity(courseKalendarWrapper.getKalendar(), ureq.getIdentity()); if (config != null) { - courseKalendarWrapper.getKalendarConfig().setCss(config.getCss()); - courseKalendarWrapper.getKalendarConfig().setVis(config.isVis()); + courseKalendarWrapper.setConfiguration(config); } return courseKalendarWrapper; } - public static CourseCalendars createCourseCalendarsWrapper(UserRequest ureq, WindowControl wControl, OLATResourceable ores, NodeEvaluation ne) { + public static CourseCalendars createCourseCalendarsWrapper(UserRequest ureq, WindowControl wControl, UserCourseEnvironment courseEnv, NodeEvaluation ne) { List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); - KalendarRenderWrapper courseKalendarWrapper = getCourseCalendarWrapper(ureq, ores, ne); + ICourse course = CourseFactory.loadCourse(courseEnv.getCourseEnvironment().getCourseResourceableId()); + KalendarRenderWrapper courseKalendarWrapper = getCourseCalendarWrapper(ureq, courseEnv, ne); // add link provider - ICourse course = CourseFactory.loadCourse(ores); + CourseLinkProviderController clpc = new CourseLinkProviderController(course, Collections.singletonList(course), ureq, wControl); courseKalendarWrapper.setLinkProvider(clpc); calendars.add(courseKalendarWrapper); @@ -130,34 +134,42 @@ public class CourseCalendars { // add course group calendars boolean isGroupManager = ureq.getUserSession().getRoles().isOLATAdmin() || ureq.getUserSession().getRoles().isGroupManager() || cgm.isIdentityCourseAdministrator(identity) || cgm.hasRight(identity, CourseRights.RIGHT_GROUPMANAGEMENT); + if (isGroupManager) { // learning groups List<BusinessGroup> allGroups = cgm.getAllBusinessGroups(); - addCalendars(ureq, allGroups, true, clpc, calendars); - + addCalendars(ureq, courseEnv, allGroups, true, clpc, calendars); } else { // learning groups List<BusinessGroup> ownerGroups = cgm.getOwnedBusinessGroups(identity); - addCalendars(ureq, ownerGroups, true, clpc, calendars); + addCalendars(ureq, courseEnv, ownerGroups, true, clpc, calendars); List<BusinessGroup> attendedGroups = cgm.getParticipatingBusinessGroups(identity); for (BusinessGroup ownerGroup : ownerGroups) { if (attendedGroups.contains(ownerGroup)) { attendedGroups.remove(ownerGroup); } } - addCalendars(ureq, attendedGroups, false, clpc, calendars); + addCalendars(ureq, courseEnv, attendedGroups, false, clpc, calendars); } return new CourseCalendars(courseKalendarWrapper, calendars); } - private static void addCalendars(UserRequest ureq, List<BusinessGroup> groups, boolean isOwner, LinkProvider linkProvider, - List<KalendarRenderWrapper> calendars) { + private static void addCalendars(UserRequest ureq, UserCourseEnvironment courseEnv, List<BusinessGroup> groups, boolean isOwner, + LinkProvider linkProvider, List<KalendarRenderWrapper> calendars) { + if(groups == null || groups.isEmpty()) return; + CollaborationToolsFactory collabFactory = CollaborationToolsFactory.getInstance(); - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); + CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class); + Map<CalendarKey, CalendarUserConfiguration> configMap = calendarManager + .getCalendarUserConfigurationsMap(ureq.getIdentity(), CalendarManager.TYPE_GROUP); for (BusinessGroup bGroup : groups) { CollaborationTools collabTools = collabFactory.getOrCreateCollaborationTools(bGroup); - if (!collabTools.isToolEnabled(CollaborationTools.TOOL_CALENDAR)) continue; + if (!collabTools.isToolEnabled(CollaborationTools.TOOL_CALENDAR)) { + continue; + } + boolean member = courseEnv.isIdentityInCourseGroup(bGroup.getKey()); KalendarRenderWrapper groupCalendarWrapper = calendarManager.getGroupCalendar(bGroup); + groupCalendarWrapper.setPrivateEventsVisible(member || isOwner); // set calendar access int iCalAccess = CollaborationTools.CALENDAR_ACCESS_OWNERS; Long lCalAccess = collabTools.lookupCalendarAccess(); @@ -167,10 +179,9 @@ public class CourseCalendars { } else { groupCalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); } - KalendarConfig config = calendarManager.findKalendarConfigForIdentity(groupCalendarWrapper.getKalendar(), ureq); + CalendarUserConfiguration config = configMap.get(groupCalendarWrapper.getCalendarKey()); if (config != null) { - groupCalendarWrapper.getKalendarConfig().setCss(config.getCss()); - groupCalendarWrapper.getKalendarConfig().setVis(config.isVis()); + groupCalendarWrapper.setConfiguration(config); } groupCalendarWrapper.setLinkProvider(linkProvider); calendars.add(groupCalendarWrapper); diff --git a/src/main/java/org/olat/course/run/calendar/CourseCalendarController.java b/src/main/java/org/olat/course/run/calendar/CourseCalendarController.java index 1f8420eaa74..f5aed5696be 100644 --- a/src/main/java/org/olat/course/run/calendar/CourseCalendarController.java +++ b/src/main/java/org/olat/course/run/calendar/CourseCalendarController.java @@ -33,13 +33,12 @@ import java.util.List; import org.olat.collaboration.CollaborationTools; import org.olat.collaboration.CollaborationToolsFactory; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.model.KalendarConfig; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.ui.CalendarController; import org.olat.commons.calendar.ui.LinkProvider; import org.olat.commons.calendar.ui.WeeklyCalendarController; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.control.Controller; @@ -55,26 +54,29 @@ import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.groupsandrights.CourseRights; import org.olat.group.BusinessGroup; import org.olat.repository.RepositoryManager; +import org.springframework.beans.factory.annotation.Autowired; public class CourseCalendarController extends BasicController { private CalendarController calendarController; private KalendarRenderWrapper courseKalendarWrapper; private OLATResourceable ores; + + @Autowired + private CalendarManager calendarManager; public CourseCalendarController(UserRequest ureq, WindowControl wControl, OLATResourceable course) { super(ureq, wControl); this.ores = course; List<KalendarRenderWrapper> calendars = getListOfCalendarWrappers(ureq); calendarController = new WeeklyCalendarController(ureq, wControl, calendars, - WeeklyCalendarController.CALLER_COURSE, true); + WeeklyCalendarController.CALLER_COURSE, false); listenTo(calendarController); putInitialPanel(calendarController.getInitialComponent()); } private List<KalendarRenderWrapper> getListOfCalendarWrappers(UserRequest ureq) { List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); // add course calendar ICourse course = CourseFactory.loadCourse(ores); courseKalendarWrapper = calendarManager.getCourseCalendar(course); @@ -89,10 +91,9 @@ public class CourseCalendarController extends BasicController { } else { courseKalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY); } - KalendarConfig config = calendarManager.findKalendarConfigForIdentity(courseKalendarWrapper.getKalendar(), ureq); + CalendarUserConfiguration config = calendarManager.findCalendarConfigForIdentity(courseKalendarWrapper.getKalendar(), getIdentity()); if (config != null) { - courseKalendarWrapper.getKalendarConfig().setCss(config.getCss()); - courseKalendarWrapper.getKalendarConfig().setVis(config.isVis()); + courseKalendarWrapper.setConfiguration(config); } // add link provider CourseLinkProviderController clpc = new CourseLinkProviderController(course, Collections.<ICourse>singletonList(course), ureq, getWindowControl()); @@ -103,26 +104,26 @@ public class CourseCalendarController extends BasicController { // learning groups List<BusinessGroup> ownerGroups = cgm.getOwnedBusinessGroups(identity); - addCalendars(ureq, ownerGroups, true, clpc, calendars); + addCalendars(ownerGroups, true, clpc, calendars); List<BusinessGroup> attendedGroups = cgm.getParticipatingBusinessGroups(identity); for (Iterator<BusinessGroup> ownerGroupsIterator = ownerGroups.iterator(); ownerGroupsIterator.hasNext();) { BusinessGroup ownerGroup = ownerGroupsIterator.next(); if (attendedGroups.contains(ownerGroup)) attendedGroups.remove(ownerGroup); } - addCalendars(ureq, attendedGroups, false, clpc, calendars); + addCalendars(attendedGroups, false, clpc, calendars); return calendars; } - private void addCalendars(UserRequest ureq, List<BusinessGroup> groups, boolean isOwner, LinkProvider linkProvider, + private void addCalendars(List<BusinessGroup> groups, boolean isOwner, LinkProvider linkProvider, List<KalendarRenderWrapper> calendars) { CollaborationToolsFactory collabFactory = CollaborationToolsFactory.getInstance(); - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); for (BusinessGroup bGroup:groups) { CollaborationTools collabTools = collabFactory.getOrCreateCollaborationTools(bGroup); if (!collabTools.isToolEnabled(CollaborationTools.TOOL_CALENDAR)) continue; KalendarRenderWrapper groupCalendarWrapper = calendarManager.getGroupCalendar(bGroup); + groupCalendarWrapper.setPrivateEventsVisible(true); // set calendar access int iCalAccess = CollaborationTools.CALENDAR_ACCESS_OWNERS; Long lCalAccess = collabTools.lookupCalendarAccess(); @@ -132,10 +133,9 @@ public class CourseCalendarController extends BasicController { } else { groupCalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); } - KalendarConfig config = calendarManager.findKalendarConfigForIdentity(groupCalendarWrapper.getKalendar(), ureq); + CalendarUserConfiguration config = calendarManager.findCalendarConfigForIdentity(groupCalendarWrapper.getKalendar(), getIdentity()); if (config != null) { - groupCalendarWrapper.getKalendarConfig().setCss(config.getCss()); - groupCalendarWrapper.getKalendarConfig().setVis(config.isVis()); + groupCalendarWrapper.setConfiguration(config); } groupCalendarWrapper.setLinkProvider(linkProvider); calendars.add(groupCalendarWrapper); @@ -148,7 +148,7 @@ public class CourseCalendarController extends BasicController { } public void event(UserRequest ureq, Controller source, Event event) { - if (event instanceof KalendarModifiedEvent) { + if (event instanceof CalendarGUIModifiedEvent) { List<KalendarRenderWrapper> calendars = getListOfCalendarWrappers(ureq); calendarController.setCalendars(calendars); } diff --git a/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java b/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java index 750c7d4d9bb..282eef913f5 100644 --- a/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java +++ b/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java @@ -31,7 +31,6 @@ import java.util.Iterator; import java.util.List; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.model.KalendarEventLink; import org.olat.commons.calendar.ui.LinkProvider; @@ -56,6 +55,7 @@ import org.olat.course.ICourse; import org.olat.course.nodes.CourseNode; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; +import org.springframework.beans.factory.annotation.Autowired; public class CourseLinkProviderController extends FormBasicController implements LinkProvider { @@ -67,6 +67,9 @@ public class CourseLinkProviderController extends FormBasicController implements private final List<ICourse> availableCourses; private MenuTreeItem multiSelectTree; private final CourseNodeSelectionTreeModel courseNodeTreeModel; + + @Autowired + private CalendarManager calendarManager; public CourseLinkProviderController(ICourse course, List<ICourse> courses, UserRequest ureq, WindowControl wControl) { super(ureq, wControl, "course_elements"); @@ -111,7 +114,7 @@ public class CourseLinkProviderController extends FormBasicController implements // otherwise, the modifications will be saver, when the user saves // the calendar event. if (kalendarEvent.getCalendar() != null) { - CalendarManagerFactory.getInstance().getCalendarManager().addEventTo(kalendarEvent.getCalendar(), kalendarEvent); + calendarManager.addEventTo(kalendarEvent.getCalendar(), kalendarEvent); } fireEvent(ureq, Event.DONE_EVENT); } diff --git a/src/main/java/org/olat/group/ui/homepage/GroupInfoDisplayController.java b/src/main/java/org/olat/group/ui/homepage/GroupInfoDisplayController.java index 939d2173b38..2888e1e3392 100644 --- a/src/main/java/org/olat/group/ui/homepage/GroupInfoDisplayController.java +++ b/src/main/java/org/olat/group/ui/homepage/GroupInfoDisplayController.java @@ -21,7 +21,6 @@ package org.olat.group.ui.homepage; import org.olat.basesecurity.GroupRoles; -import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.velocity.VelocityContainer; @@ -31,6 +30,7 @@ import org.olat.core.gui.control.controller.BasicController; import org.olat.core.util.StringHelper; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; +import org.springframework.beans.factory.annotation.Autowired; /** * @@ -42,13 +42,15 @@ public class GroupInfoDisplayController extends BasicController { private final VelocityContainer content; + @Autowired + private BusinessGroupService businessGroupService; + public GroupInfoDisplayController(UserRequest ureq, WindowControl wControl, BusinessGroup businessGroup) { super(ureq, wControl); content = createVelocityContainer("groupinfodisplay"); content.contextPut("description", businessGroup.getDescription()); content.contextPut("name", StringHelper.escapeHtml(businessGroup.getName())); - - BusinessGroupService businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class); + int numOfMembers = businessGroupService.countMembers(businessGroup, GroupRoles.participant.name(), GroupRoles.coach.name()); content.contextPut("numMembers", numOfMembers); diff --git a/src/main/java/org/olat/group/ui/homepage/GroupInfoMainController.java b/src/main/java/org/olat/group/ui/homepage/GroupInfoMainController.java index d9307f78340..43fb231e392 100644 --- a/src/main/java/org/olat/group/ui/homepage/GroupInfoMainController.java +++ b/src/main/java/org/olat/group/ui/homepage/GroupInfoMainController.java @@ -19,11 +19,15 @@ */ package org.olat.group.ui.homepage; +import java.util.ArrayList; import java.util.List; import org.olat.collaboration.CollaborationTools; import org.olat.collaboration.CollaborationToolsFactory; -import org.olat.core.CoreSpringFactory; +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.CalendarModule; +import org.olat.commons.calendar.ui.WeeklyCalendarController; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -45,6 +49,7 @@ import org.olat.core.util.resource.OresHelper; import org.olat.core.util.tree.TreeHelper; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupModule; +import org.springframework.beans.factory.annotation.Autowired; /** * @@ -58,10 +63,12 @@ public class GroupInfoMainController extends MainLayoutBasicController implement public final static String COMMAND_MENU_GROUPINFO = "COMMAND_MENU_GROUPINFO"; public final static String COMMAND_MENU_GROUPMEMBERS = "COMMAND_MENU_GROUPMEMBERS"; public final static String COMMAND_MENU_GROUPCONTACT = "COMMAND_MENU_GROUPCONTACT"; + public final static String COMMAND_MENU_GROUPCALENDAR = "COMMAND_MENU_GROUPCALENDAR"; /** The three columns layout controller */ private LayoutMain3ColsController layoutController; + private CollaborationTools tools; /** The business group we're dealing with */ private BusinessGroup businessGroup; @@ -71,14 +78,19 @@ public class GroupInfoMainController extends MainLayoutBasicController implement private final GroupInfoDisplayController groupInfoDisplayController; private GroupMembersDisplayController groupMembersDisplayController; private GroupContactController groupContactController; + private WeeklyCalendarController calendarController; - private final BusinessGroupModule module; + @Autowired + private BusinessGroupModule module; + @Autowired + private CalendarModule calendarModule; + @Autowired + private CalendarManager calendarManager; public GroupInfoMainController(UserRequest ureq, WindowControl wControl, BusinessGroup businessGroup) { // Initialize super(ureq, wControl); this.businessGroup = businessGroup; - module = CoreSpringFactory.getImpl(BusinessGroupModule.class); menuTree = new MenuTree("menuTree"); menuTree.setRootVisible(false); @@ -155,6 +167,8 @@ public class GroupInfoMainController extends MainLayoutBasicController implement getMembersController(ureq); } else if (command.equals(COMMAND_MENU_GROUPCONTACT)) { getContactController(ureq); + } else if (command.equals(COMMAND_MENU_GROUPCALENDAR)) { + getCalendarController(ureq); } } } @@ -192,6 +206,24 @@ public class GroupInfoMainController extends MainLayoutBasicController implement return groupContactController; } + private WeeklyCalendarController getCalendarController(UserRequest ureq) { + if(calendarController == null) { + OLATResourceable ores = OresHelper.createOLATResourceableInstance("Calendar", 0l); + WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ores, null, getWindowControl()); + + List<KalendarRenderWrapper> calendarWrappers = new ArrayList<>(2); + KalendarRenderWrapper groupCalendar = calendarManager.getGroupCalendar(businessGroup); + groupCalendar.setPrivateEventsVisible(false); + calendarWrappers.add(groupCalendar); + calendarController = new WeeklyCalendarController(ureq, bwControl, calendarWrappers, WeeklyCalendarController.CALLER_COLLAB, false); + listenTo(calendarController); + } + + layoutController.setCol3(calendarController.getInitialComponent()); + addToHistory(ureq, calendarController); + return calendarController; + } + private TreeModel buildTreeModel() { // Builds the model for the navigation tree GenericTreeModel treeModel = new GenericTreeModel(); @@ -208,6 +240,14 @@ public class GroupInfoMainController extends MainLayoutBasicController implement rootNode.addChild(childNode); rootNode.setDelegate(childNode); + if(calendarModule.isEnableGroupCalendar() && isCalendarEnabled()) { + childNode = new GenericTreeNode(); + childNode.setTitle(translate("main.menu.calendar")); + childNode.setUserObject(COMMAND_MENU_GROUPCALENDAR); + childNode.setCssClass("o_sel_groupcard_calendar"); + rootNode.addChild(childNode); + } + if(businessGroup.isOwnersVisiblePublic() || businessGroup.isParticipantsVisiblePublic() || businessGroup.isWaitingListVisiblePublic()) { childNode = new GenericTreeNode(); childNode.setTitle(translate("main.menu.members")); @@ -227,13 +267,22 @@ public class GroupInfoMainController extends MainLayoutBasicController implement return treeModel; } + private boolean isCalendarEnabled() { + if(tools == null) { + tools = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup); + } + return tools == null ? false : tools.isToolEnabled(CollaborationTools.TOOL_CALENDAR); + } + private boolean isContactEnabled() { String contactConfig = module.getContactBusinessCard(); if(BusinessGroupModule.CONTACT_BUSINESS_CARD_ALWAYS.equals(contactConfig)) { return true; } if(BusinessGroupModule.CONTACT_BUSINESS_CARD_GROUP_CONFIG.equals(contactConfig)) { - CollaborationTools tools = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup); + if(tools == null) { + tools = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup); + } return tools == null ? false : tools.isToolEnabled(CollaborationTools.TOOL_CONTACT); } return false; diff --git a/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_de.properties index 38c78b61288..6917e8d6470 100644 --- a/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_de.properties @@ -11,6 +11,7 @@ form.to.owners=Betreuer GroupInfoDisplayController.content.title=Gruppe GroupInfoDisplayController.content.numParticipants=Mitglieder GroupInfoDisplayController.content.numParticipant=Mitglied +main.menu.calendar=Kalendar main.menu.contact=Kontakt main.menu.members=Mitglieder main.menu.title=Gruppeninformationen diff --git a/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_en.properties index f09d9d3c6a0..b0a72a2909b 100644 --- a/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_en.properties @@ -12,6 +12,7 @@ form.fieldset.message=Message form.fieldset.to=Recipient form.to=To\: form.to.owners=Coaches +main.menu.calendar=Calendar main.menu.contact=Contact main.menu.members=Members main.menu.title=Group information diff --git a/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_fr.properties index be27f77c4e0..5e28fd0f634 100644 --- a/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/group/ui/homepage/_i18n/LocalStrings_fr.properties @@ -12,6 +12,7 @@ form.fieldset.message=Message form.fieldset.to=Destinataire form.to=\u00C0\: form.to.owners=Coaches +main.menu.calendar=Calendrier main.menu.contact=Contact main.menu.members=Membres main.menu.title=Informations diff --git a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java index d7b517fc047..06f7d85b53d 100644 --- a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java +++ b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java @@ -681,7 +681,7 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapBusinessPath(ce.getOLATResourceable())); bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, bwControl); - collabToolCtr = collabTools.createCalendarController(ureq, bwControl, this.businessGroup, isAdmin); + collabToolCtr = collabTools.createCalendarController(ureq, bwControl, businessGroup, isAdmin, true); listenTo(collabToolCtr); mainPanel.setContent(collabToolCtr.getInitialComponent()); } else if (ACTIVITY_MENUSELECT_INFORMATION.equals(cmd)) { diff --git a/src/main/java/org/olat/home/HomeCalendarController.java b/src/main/java/org/olat/home/HomeCalendarController.java index d14252aa559..9e605999f8d 100644 --- a/src/main/java/org/olat/home/HomeCalendarController.java +++ b/src/main/java/org/olat/home/HomeCalendarController.java @@ -30,7 +30,7 @@ import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.ui.CalendarController; import org.olat.commons.calendar.ui.WeeklyCalendarController; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; +import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.control.Controller; @@ -62,8 +62,8 @@ public class HomeCalendarController extends BasicController implements Activatea CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, ureq.getIdentity(), OresHelper.lookupType(CalendarManager.class)); List<KalendarRenderWrapper> calendars = homeCalendarManager.getListOfCalendarWrappers(ureq, windowControl); - List<KalendarRenderWrapper> importedCalendars = homeCalendarManager.getListOfImportedCalendarWrappers(ureq); - calendarController = new WeeklyCalendarController(ureq, windowControl, calendars, importedCalendars, WeeklyCalendarController.CALLER_HOME, false); + calendarController = new WeeklyCalendarController(ureq, windowControl, calendars, + WeeklyCalendarController.CALLER_HOME, true); listenTo(calendarController); putInitialPanel(calendarController.getInitialComponent()); @@ -83,10 +83,9 @@ public class HomeCalendarController extends BasicController implements Activatea @Override public void event(UserRequest ureq, Controller source, Event event) { - if (event instanceof KalendarModifiedEvent) { + if (event instanceof CalendarGUIModifiedEvent) { List<KalendarRenderWrapper> calendars = homeCalendarManager.getListOfCalendarWrappers(ureq, getWindowControl()); - List<KalendarRenderWrapper> importedCalendars = homeCalendarManager.getListOfImportedCalendarWrappers(ureq); - calendarController.setCalendars(calendars, importedCalendars); + calendarController.setCalendars(calendars); } super.event(ureq, source, event); } @@ -100,7 +99,7 @@ public class HomeCalendarController extends BasicController implements Activatea @Override public void event(Event event) { - if (event instanceof KalendarModifiedEvent) { + if (event instanceof CalendarGUIModifiedEvent) { if (calendarController != null) { // could theoretically be disposed calendarController.setDirty(); diff --git a/src/main/java/org/olat/home/HomeCalendarManager.java b/src/main/java/org/olat/home/HomeCalendarManager.java index 1c2fc452bf7..05b1b1f610c 100644 --- a/src/main/java/org/olat/home/HomeCalendarManager.java +++ b/src/main/java/org/olat/home/HomeCalendarManager.java @@ -19,6 +19,7 @@ */ package org.olat.home; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -31,15 +32,18 @@ import org.olat.basesecurity.IdentityRef; import org.olat.collaboration.CollaborationManager; import org.olat.collaboration.CollaborationTools; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.CalendarModule; -import org.olat.commons.calendar.ImportCalendarManager; -import org.olat.commons.calendar.model.KalendarConfig; +import org.olat.commons.calendar.PersonalCalendarManager; +import org.olat.commons.calendar.manager.ImportCalendarManager; +import org.olat.commons.calendar.model.CalendarFileInfos; +import org.olat.commons.calendar.model.CalendarKey; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DB; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.nodes.INode; @@ -67,7 +71,7 @@ import org.springframework.stereotype.Service; * */ @Service -public class HomeCalendarManager { +public class HomeCalendarManager implements PersonalCalendarManager { private static final OLog log = Tracing.createLoggerFor(HomeCalendarManager.class); @@ -76,55 +80,122 @@ public class HomeCalendarManager { @Autowired private CalendarModule calendarModule; @Autowired + private CalendarManager calendarManager; + @Autowired private BusinessGroupService businessGroupService; + @Autowired + private ImportCalendarManager importCalendarManager; - public List<KalendarRenderWrapper> getListOfCalendarWrappers(UserRequest ureq, WindowControl wControl) { + + public List<CalendarFileInfos> getListOfCalendarsFiles(Identity identity) { + List<CalendarFileInfos> aggragtedFiles = new ArrayList<>(); + + Map<CalendarKey,CalendarUserConfiguration> configMap = calendarManager.getCalendarUserConfigurationsMap(identity); + + //personal calendar + CalendarKey personalCalendarKey = new CalendarKey(CalendarManager.TYPE_USER, identity.getName()); + CalendarUserConfiguration personalCalendarConfig = configMap.get(personalCalendarKey); + if(calendarModule.isEnablePersonalCalendar() + && (personalCalendarConfig == null || personalCalendarConfig.isInAggregatedFeed())) { + File iCalFile = calendarManager.getCalendarICalFile(CalendarManager.TYPE_USER, identity.getName()); + if(iCalFile != null) { + aggragtedFiles.add(new CalendarFileInfos(identity.getName(), CalendarManager.TYPE_USER, iCalFile)); + } + } + + //group calendars + if(calendarModule.isEnableGroupCalendar()) { + SearchBusinessGroupParams groupParams = new SearchBusinessGroupParams(identity, true, true); + groupParams.addTools(CollaborationTools.TOOL_CALENDAR); + List<BusinessGroup> groups = businessGroupService.findBusinessGroups(groupParams, null, 0, -1); + for(BusinessGroup group:groups) { + String calendarId = group.getKey().toString(); + CalendarKey key = new CalendarKey(CalendarManager.TYPE_GROUP, calendarId); + CalendarUserConfiguration calendarConfig = configMap.get(key); + if(calendarConfig == null || calendarConfig.isInAggregatedFeed()) { + File iCalFile = calendarManager.getCalendarICalFile(CalendarManager.TYPE_GROUP, calendarId); + if(iCalFile != null) { + aggragtedFiles.add(new CalendarFileInfos(calendarId, CalendarManager.TYPE_GROUP, iCalFile)); + } + } + } + } + + if(calendarModule.isEnableCourseElementCalendar() || calendarModule.isEnableCourseToolCalendar()) { + List<Object[]> resources = getCourses(identity); + for(Object[] resource:resources) { + RepositoryEntry courseEntry = (RepositoryEntry)resource[0]; + String calendarId = courseEntry.getKey().toString(); + CalendarKey key = new CalendarKey(CalendarManager.TYPE_COURSE, calendarId); + CalendarUserConfiguration calendarConfig = configMap.get(key); + if(calendarConfig == null || calendarConfig.isInAggregatedFeed()) { + File iCalFile = calendarManager.getCalendarICalFile(CalendarManager.TYPE_COURSE, calendarId); + if(iCalFile != null) { + aggragtedFiles.add(new CalendarFileInfos(calendarId, CalendarManager.TYPE_COURSE, iCalFile)); + } + } + } + } + return aggragtedFiles; + } + + public List<KalendarRenderWrapper> getListOfCalendarWrappers(UserRequest ureq, WindowControl wControl) { if(!calendarModule.isEnabled()) { return new ArrayList<KalendarRenderWrapper>(); } + Identity identity = ureq.getIdentity(); + List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); - appendPersonalCalendar(ureq, calendars); - appendGroupCalendars(ureq, calendars); - appendCourseCalendars(ureq, wControl, calendars); + Map<CalendarKey,CalendarUserConfiguration> configMap = calendarManager + .getCalendarUserConfigurationsMap(ureq.getIdentity()); + appendPersonalCalendar(identity, calendars, configMap); + appendGroupCalendars(identity, calendars, configMap); + appendCourseCalendars(ureq, wControl, calendars, configMap); + + //reload + List<KalendarRenderWrapper> importedCalendars = importCalendarManager.getImportedCalendarsForIdentity(identity, true); + + calendars.addAll(importedCalendars); return calendars; } - private void appendPersonalCalendar(UserRequest ureq, List<KalendarRenderWrapper> calendars) { + private void appendPersonalCalendar(Identity identity, List<KalendarRenderWrapper> calendars, + Map<CalendarKey,CalendarUserConfiguration> configMap) { // get the personal calendar - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); if(calendarModule.isEnablePersonalCalendar()) { - KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(ureq.getIdentity()); + KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(identity); calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); - KalendarConfig personalKalendarConfig = calendarManager.findKalendarConfigForIdentity(calendarWrapper.getKalendar(), ureq); - if (personalKalendarConfig != null) { - calendarWrapper.getKalendarConfig().setCss(personalKalendarConfig.getCss()); - calendarWrapper.getKalendarConfig().setVis(personalKalendarConfig.isVis()); + calendarWrapper.setPrivateEventsVisible(true); + CalendarUserConfiguration config = configMap.get(calendarWrapper.getCalendarKey()); + if (config != null) { + calendarWrapper.setConfiguration(config); } calendars.add(calendarWrapper); } } - private void appendGroupCalendars(UserRequest ureq, List<KalendarRenderWrapper> calendars) { + private void appendGroupCalendars(Identity identity, List<KalendarRenderWrapper> calendars, + Map<CalendarKey,CalendarUserConfiguration> configMap) { // get group calendars if(calendarModule.isEnableGroupCalendar()) { - SearchBusinessGroupParams groupParams = new SearchBusinessGroupParams(ureq.getIdentity(), true, false); + SearchBusinessGroupParams groupParams = new SearchBusinessGroupParams(identity, true, false); groupParams.addTools(CollaborationTools.TOOL_CALENDAR); List<BusinessGroup> ownerGroups = businessGroupService.findBusinessGroups(groupParams, null, 0, -1); - addCalendars(ureq, ownerGroups, true, calendars); + addCalendars(ownerGroups, true, calendars, configMap); - SearchBusinessGroupParams groupParams2 = new SearchBusinessGroupParams(ureq.getIdentity(), false, true); + SearchBusinessGroupParams groupParams2 = new SearchBusinessGroupParams(identity, false, true); groupParams2.addTools(CollaborationTools.TOOL_CALENDAR); List<BusinessGroup> attendedGroups = businessGroupService.findBusinessGroups(groupParams2, null, 0, -1); attendedGroups.removeAll(ownerGroups); - addCalendars(ureq, attendedGroups, false, calendars); + addCalendars(attendedGroups, false, calendars, configMap); } } - private void appendCourseCalendars(UserRequest ureq, WindowControl wControl, List<KalendarRenderWrapper> calendars) { + private void appendCourseCalendars(UserRequest ureq, WindowControl wControl, List<KalendarRenderWrapper> calendars, + Map<CalendarKey,CalendarUserConfiguration> configMap) { if(calendarModule.isEnableCourseElementCalendar() || calendarModule.isEnableCourseToolCalendar()) { - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); // add course calendars List<Object[]> resources = getCourses(ureq.getIdentity()); @@ -155,18 +226,15 @@ public class HomeCalendarManager { courseCalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY); } - KalendarConfig courseKalendarConfig = calendarManager.findKalendarConfigForIdentity(courseCalendarWrapper.getKalendar(), ureq); - if (courseKalendarConfig != null) { - courseCalendarWrapper.getKalendarConfig().setCss(courseKalendarConfig.getCss()); - courseCalendarWrapper.getKalendarConfig().setVis(courseKalendarConfig.isVis()); + CalendarUserConfiguration config = configMap.get(courseCalendarWrapper.getCalendarKey()); + if (config != null) { + courseCalendarWrapper.setConfiguration(config); } courseCalendarWrapper.setLinkProvider(new CourseLinkProviderController(course, Collections.singletonList(course), ureq, wControl)); calendars.add(courseCalendarWrapper); } } catch (CorruptedCourseException e) { OLATResource olatResource = courseEntry.getOlatResource(); - - log.error("Corrupted course: " + olatResource.getResourceableTypeName() + " :: " + courseResourceableID, null); } } @@ -232,11 +300,13 @@ public class HomeCalendarManager { * @param isOwner * @param calendars */ - private void addCalendars(UserRequest ureq, List<BusinessGroup> groups, boolean isOwner, List<KalendarRenderWrapper> calendars) { - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); + private void addCalendars(List<BusinessGroup> groups, boolean isOwner, + List<KalendarRenderWrapper> calendars, Map<CalendarKey,CalendarUserConfiguration> configMap) { + Map<Long,Long> groupKeyToAccess = CoreSpringFactory.getImpl(CollaborationManager.class).lookupCalendarAccess(groups); for (BusinessGroup bGroup:groups) { KalendarRenderWrapper groupCalendarWrapper = calendarManager.getGroupCalendar(bGroup); + groupCalendarWrapper.setPrivateEventsVisible(true); // set calendar access int iCalAccess = CollaborationTools.CALENDAR_ACCESS_OWNERS; Long lCalAccess = groupKeyToAccess.get(bGroup.getKey()); @@ -248,20 +318,14 @@ public class HomeCalendarManager { } else { groupCalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); } - KalendarConfig groupKalendarConfig = calendarManager.findKalendarConfigForIdentity(groupCalendarWrapper.getKalendar(), ureq); - if (groupKalendarConfig != null) { - groupCalendarWrapper.getKalendarConfig().setCss(groupKalendarConfig.getCss()); - groupCalendarWrapper.getKalendarConfig().setVis(groupKalendarConfig.isVis()); + CalendarUserConfiguration config = configMap.get(groupCalendarWrapper.getCalendarKey()); + if (config != null) { + groupCalendarWrapper.setConfiguration(config); } calendars.add(groupCalendarWrapper); } } - public List<KalendarRenderWrapper> getListOfImportedCalendarWrappers(UserRequest ureq) { - ImportCalendarManager.reloadUrlImportedCalendars(ureq); - return ImportCalendarManager.getImportedCalendarsForIdentity(ureq); - } - private static class CalCourseNodeVisitor implements Visitor { private boolean found = false; @@ -276,5 +340,4 @@ public class HomeCalendarManager { } } } - } diff --git a/src/main/java/org/olat/portal/calendar/CalendarPortletRunController.java b/src/main/java/org/olat/portal/calendar/CalendarPortletRunController.java index cce3bf96784..26c5a95a038 100644 --- a/src/main/java/org/olat/portal/calendar/CalendarPortletRunController.java +++ b/src/main/java/org/olat/portal/calendar/CalendarPortletRunController.java @@ -36,6 +36,7 @@ import java.util.List; import org.olat.NewControllerFactory; import org.olat.commons.calendar.CalendarUtils; +import org.olat.commons.calendar.PersonalCalendarManager; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.gui.UserRequest; @@ -58,7 +59,6 @@ import org.olat.core.gui.control.controller.BasicController; import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.logging.OLATRuntimeException; -import org.olat.home.HomeCalendarManager; import org.springframework.beans.factory.annotation.Autowired; /** @@ -80,7 +80,7 @@ public class CalendarPortletRunController extends BasicController { private Link showAllLink; @Autowired - private HomeCalendarManager homeCalendarManager; + private PersonalCalendarManager personalCalendarManager; /** * Constructor @@ -131,8 +131,7 @@ public class CalendarPortletRunController extends BasicController { cal.set(Calendar.DAY_OF_YEAR, cal.get(Calendar.DAY_OF_YEAR) + 7); Date endDate = cal.getTime(); List<KalendarEvent> events = new ArrayList<>(); - List<KalendarRenderWrapper> calendars = homeCalendarManager.getListOfCalendarWrappers(ureq, wControl); - calendars.addAll( homeCalendarManager.getListOfImportedCalendarWrappers(ureq) ); + List<KalendarRenderWrapper> calendars = personalCalendarManager.getListOfCalendarWrappers(ureq, wControl); for (Iterator<KalendarRenderWrapper> iter = calendars.iterator(); iter.hasNext();) { KalendarRenderWrapper calendarWrapper = iter.next(); boolean readOnly = (calendarWrapper.getAccess() == KalendarRenderWrapper.ACCESS_READ_ONLY) && !calendarWrapper.isImported(); diff --git a/src/main/java/org/olat/restapi/repository/course/CourseWebService.java b/src/main/java/org/olat/restapi/repository/course/CourseWebService.java index 59339b4a503..c427210ccdd 100644 --- a/src/main/java/org/olat/restapi/repository/course/CourseWebService.java +++ b/src/main/java/org/olat/restapi/repository/course/CourseWebService.java @@ -61,6 +61,7 @@ import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.media.MediaResource; import org.olat.core.id.Identity; +import org.olat.core.id.IdentityEnvironment; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; @@ -72,6 +73,8 @@ import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.config.CourseConfig; import org.olat.course.nodes.cal.CourseCalendars; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.course.run.userview.UserCourseEnvironmentImpl; import org.olat.modules.vitero.restapi.ViteroBookingWebService; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; @@ -150,7 +153,12 @@ public class CourseWebService { && (calendarModule.isEnableCourseToolCalendar() || calendarModule.isEnableCourseElementCalendar()) && course.getCourseConfig().isCalendarEnabled()) { UserRequest ureq = getUserRequest(request); - KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, courseOres, null); + + IdentityEnvironment ienv = new IdentityEnvironment(); + ienv.setIdentity(ureq.getIdentity()); + ienv.setRoles(ureq.getUserSession().getRoles()); + UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment()); + KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, userCourseEnv, null); return new CalWebService(wrapper); } return null; diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_10_4_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_10_4_0.java new file mode 100644 index 00000000000..941f2a44ab5 --- /dev/null +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_10_4_0.java @@ -0,0 +1,384 @@ +/** + * <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.upgrade; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.manager.CalendarUserConfigurationDAO; +import org.olat.commons.calendar.manager.ImportedCalendarDAO; +import org.olat.commons.calendar.manager.ImportedToCalendarDAO; +import org.olat.commons.calendar.model.CalendarUserConfiguration; +import org.olat.commons.calendar.model.ImportedCalendar; +import org.olat.commons.calendar.model.ImportedToCalendar; +import org.olat.commons.calendar.model.Kalendar; +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; +import org.olat.core.util.xml.XStreamHelper; +import org.olat.properties.Property; +import org.olat.upgrade.model.KalendarConfig; +import org.olat.upgrade.model.UpgradePreferences; +import org.springframework.beans.factory.annotation.Autowired; + +import com.thoughtworks.xstream.XStream; + +/** + * + * Initial date: 27.03.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class OLATUpgrade_10_4_0 extends OLATUpgrade { + + private static final int BATCH_SIZE = 1000; + private static final String CALENDAR_TOKENS = "Calendar tokens"; + private static final String CALENDAR_USER_CONFIGS = "Calendar user configurations"; + private static final String IMPORTED_CALENDARS = "Imported calendars"; + private static final String IMPORTED_CALENDARS_URL = "Imported calendars url"; + private static final String VERSION = "OLAT_10.4.0"; + + private static final XStream xstream = XStreamHelper.createXStreamInstance(); + static { + xstream.alias("org.olat.core.util.prefs.db.DbPrefs", UpgradePreferences.class); + xstream.alias("org.olat.commons.calendar.model.KalendarConfig", KalendarConfig.class); + } + + private final static String KALENDAR_GUI_MARKER = "org.olat.commons.calendar.model.KalendarConfig::"; + + @Autowired + private DB dbInstance; + @Autowired + private CalendarManager calendarMgr; + @Autowired + private ImportedCalendarDAO importedCalendarDao; + @Autowired + private ImportedToCalendarDAO importedToCalendarDao; + @Autowired + private CalendarUserConfigurationDAO calendarUserConfigurationDao; + + + + public OLATUpgrade_10_4_0() { + super(); + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public boolean doPreSystemInitUpgrade(UpgradeManager upgradeManager) { + return false; + } + + @Override + public boolean doPostSystemInitUpgrade(UpgradeManager upgradeManager) { + UpgradeHistoryData uhd = upgradeManager.getUpgradesHistory(VERSION); + if (uhd == null) { + // has never been called, initialize + uhd = new UpgradeHistoryData(); + } else if (uhd.isInstallationComplete()) { + return false; + } + + boolean allOk = true; + allOk &= migrateImportedCalendars(upgradeManager, uhd); + allOk &= migrateImportedCalendarsTo(upgradeManager, uhd); + + allOk &= migrateCalendarTokens(upgradeManager, uhd); + allOk &= migrateCalendarConfigurations(upgradeManager, uhd); + + uhd.setInstallationComplete(allOk); + upgradeManager.setUpgradesHistory(uhd, VERSION); + if(allOk) { + log.audit("Finished OLATUpgrade_10_4_0 successfully!"); + } else { + log.audit("OLATUpgrade_10_4_0 not finished, try to restart OpenOLAT!"); + } + return allOk; + } + + private boolean migrateCalendarTokens(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + if (!uhd.getBooleanDataValue(CALENDAR_TOKENS)) { + int counter = 0; + List<Property> properties; + do { + properties = findAllUserProperties(counter, BATCH_SIZE); + for(Property property:properties) { + processCalendarProperty(property); + if(counter % 20 == 0) { + dbInstance.commit(); + } + } + dbInstance.commitAndCloseSession(); + counter += properties.size(); + } while(properties.size() == BATCH_SIZE); + uhd.setBooleanDataValue(CALENDAR_TOKENS, true); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return false; + } + + private void processCalendarProperty(Property property) { + String calendarId; + Identity identity = property.getIdentity(); + String resourceType = property.getResourceTypeName(); + if(StringHelper.containsNonWhitespace(resourceType) && property.getResourceTypeId() != null) { + calendarId = property.getResourceTypeId().toString(); + } else { + resourceType = CalendarManager.TYPE_USER; + calendarId = identity.getName(); + } + + CalendarUserConfiguration config = calendarUserConfigurationDao + .getCalendarUserConfiguration(identity, calendarId, resourceType); + + if(config == null) { + String token = property.getStringValue(); + Kalendar mockCal = new Kalendar(calendarId, resourceType); + calendarUserConfigurationDao.createCalendarUserConfiguration(mockCal, identity, token, true, true); + } + } + + private List<Property> findAllUserProperties(int firstResult, int maxResult) { + StringBuilder sb = new StringBuilder(); + sb.append("select v from ").append(Property.class.getName()).append(" as v ") + .append(" inner join v.identity identity ") + .append(" where v.category='icalAuthToken' and v.name='authToken'") + .append(" order by v.key"); + + return DBFactory.getInstance().getCurrentEntityManager() + .createQuery(sb.toString(), Property.class) + .setFirstResult(firstResult) + .setMaxResults(maxResult) + .getResultList(); + } + + private boolean migrateCalendarConfigurations(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + if (!uhd.getBooleanDataValue(CALENDAR_USER_CONFIGS)) { + int counter = 0; + + List<Property> properties; + do { + properties = getUserGUIProperties(counter, BATCH_SIZE); + for(Property property:properties) { + processCalendarGUIProperty(property); + if(counter % 20 == 0) { + dbInstance.commit(); + } + } + counter += properties.size(); + log.audit("Calendar GUI properties processed: " + properties.size() + ", total processed (" + counter + ")"); + dbInstance.commitAndCloseSession(); + } while(properties.size() == BATCH_SIZE); + uhd.setBooleanDataValue(CALENDAR_USER_CONFIGS, false); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return false; + } + + private List<Property> getUserGUIProperties(int firstResult, int maxResult) { + StringBuilder sb = new StringBuilder(); + sb.append("select v from ").append(Property.class.getName()).append(" as v ") + .append(" inner join v.identity identity ") + .append(" where v.name='v2guipreferences'") + .append(" order by v.key"); + + return DBFactory.getInstance().getCurrentEntityManager() + .createQuery(sb.toString(), Property.class) + .setFirstResult(firstResult) + .setMaxResults(maxResult) + .getResultList(); + + } + + private void processCalendarGUIProperty(Property property) { + String text = property.getTextValue(); + if(StringHelper.containsNonWhitespace(text)) { + UpgradePreferences prefs = (UpgradePreferences)xstream.fromXML(text); + Map<String,Object> preferenceMap = prefs.getPreferences(); + for(Map.Entry<String,Object> entry:preferenceMap.entrySet()) { + String key = entry.getKey(); + if(key.startsWith(KALENDAR_GUI_MARKER)) { + String calendarId = key.substring(KALENDAR_GUI_MARKER.length(), key.length()); + KalendarConfig config = (KalendarConfig)entry.getValue(); + processKalendarConfig(property.getIdentity(), calendarId, config); + } + } + } + } + + private void processKalendarConfig(Identity identity, String calendarId, KalendarConfig config) { + Kalendar cal; + CalendarUserConfiguration userConfig; + if(StringHelper.isLong(calendarId)) { + //guess if it's a course or a group calendar + if(calendarMgr.getCalendarFile(CalendarManager.TYPE_COURSE, calendarId) != null) { + cal = new Kalendar(calendarId, CalendarManager.TYPE_COURSE); + } else if(calendarMgr.getCalendarFile(CalendarManager.TYPE_GROUP, calendarId) != null) { + cal = new Kalendar(calendarId, CalendarManager.TYPE_GROUP); + } else { + return; + } + } else { + //personal calendar + cal = new Kalendar(calendarId, CalendarManager.TYPE_USER); + } + + userConfig = calendarUserConfigurationDao + .getCalendarUserConfiguration(identity, cal.getCalendarID(), cal.getType()); + if(userConfig == null) { + userConfig = calendarUserConfigurationDao.createCalendarUserConfiguration(cal, identity); + } + + userConfig.setCssClass(config.getCss()); + userConfig.setVisible(config.isVis()); + userConfig = calendarUserConfigurationDao.update(userConfig); + } + + private boolean migrateImportedCalendars(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + if (!uhd.getBooleanDataValue(IMPORTED_CALENDARS)) { + int counter = 0; + List<Property> properties; + do { + properties = getImportCalendarProperties(counter, BATCH_SIZE); + for(Property property:properties) { + processImportedCalendars(property); + if(counter % 20 == 0) { + dbInstance.commit(); + } + } + dbInstance.commitAndCloseSession(); + counter += properties.size(); + } while(properties.size() == BATCH_SIZE); + uhd.setBooleanDataValue(IMPORTED_CALENDARS, true); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return false; + } + + private void processImportedCalendars(Property property) { + Identity identity = property.getIdentity(); + String calendarPartialId = property.getName(); + String calendarId = identity.getName() + "_" + calendarPartialId; + String url = property.getStringValue(); + + List<ImportedCalendar> currentCalendars = importedCalendarDao + .getImportedCalendar(identity, calendarId, CalendarManager.TYPE_USER); + + if(currentCalendars.isEmpty()) { + Date importDate; + if(property.getLongValue() != null) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(property.getLongValue().longValue()); + importDate = calendar.getTime(); + } else { + importDate = new Date(); + } + importedCalendarDao.createImportedCalendar(identity, calendarPartialId, + calendarId, CalendarManager.TYPE_USER, url, importDate); + } + } + + private List<Property> getImportCalendarProperties(int firstResult, int maxResult) { + StringBuilder sb = new StringBuilder(); + sb.append("select v from ").append(Property.class.getName()).append(" as v ") + .append(" inner join v.identity identity ") + .append(" where v.category='Imported-Calendar'") + .append(" order by v.key"); + + return DBFactory.getInstance().getCurrentEntityManager() + .createQuery(sb.toString(), Property.class) + .setFirstResult(firstResult) + .setMaxResults(maxResult) + .getResultList(); + } + + private boolean migrateImportedCalendarsTo(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + if (!uhd.getBooleanDataValue(IMPORTED_CALENDARS_URL)) { + int counter = 0; + List<Property> properties; + do { + properties = getImportCalendarWithUrlProperties(counter, BATCH_SIZE); + for(Property property:properties) { + processImportedCalendarsTo(property); + if(counter % 20 == 0) { + dbInstance.commit(); + } + } + dbInstance.commitAndCloseSession(); + counter += properties.size(); + } while(properties.size() == BATCH_SIZE); + uhd.setBooleanDataValue(IMPORTED_CALENDARS_URL, true); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return false; + } + + private void processImportedCalendarsTo(Property property) { + //the calendar are imported in an existent calendar + //urls are | separated + String calendarId = property.getName(); + String type = property.getResourceTypeName(); + if("user".equals(type)) { + return;//don't convert this, they have there own synchronization mechanism + } + String importUrls = property.getTextValue(); + + if(StringHelper.containsNonWhitespace(importUrls) && StringHelper.containsNonWhitespace(calendarId) && StringHelper.containsNonWhitespace(type)) { + for(StringTokenizer tokenizer = new StringTokenizer(importUrls, "|"); tokenizer.hasMoreTokens(); ) { + String importUrl = tokenizer.nextToken(); + + List<ImportedToCalendar> currentCalendars = importedToCalendarDao.getImportedToCalendars(calendarId, type, importUrl); + if(currentCalendars.isEmpty()) { + Date importDate; + if(property.getLongValue() != null) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(property.getLongValue().longValue()); + importDate = calendar.getTime(); + } else { + importDate = new Date(); + } + importedToCalendarDao.createImportedToCalendar(calendarId, type, importUrl, importDate); + } + } + } + } + + private List<Property> getImportCalendarWithUrlProperties(int firstResult, int maxResult) { + StringBuilder sb = new StringBuilder(); + sb.append("select v from ").append(Property.class.getName()).append(" as v ") + .append(" where v.category='Imported-Calendar-To'") + .append(" order by v.key"); + + return DBFactory.getInstance().getCurrentEntityManager() + .createQuery(sb.toString(), Property.class) + .setFirstResult(firstResult) + .setMaxResults(maxResult) + .getResultList(); + } +} diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml index fedf67121a0..b64242d808a 100644 --- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml +++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml @@ -88,6 +88,10 @@ <constructor-arg index="0" value="OLAT_10.3.3" /> <property name="alterDbStatements" value="alter_10_3_2_to_10_3_3.sql" /> </bean> + <bean id="database_upgrade_10_4_0" class="org.olat.upgrade.DatabaseUpgrade"> + <constructor-arg index="0" value="OLAT_10.4.0" /> + <property name="alterDbStatements" value="alter_10_3_4_to_10_4_0.sql" /> + </bean> </list> </property> </bean> diff --git a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml index 52fdefa2e8b..9e1ee552f6c 100644 --- a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml +++ b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml @@ -44,6 +44,7 @@ <bean id="upgrade_10_0_3" class="org.olat.upgrade.OLATUpgrade_10_0_3"/> <bean id="upgrade_10_1_0" class="org.olat.upgrade.OLATUpgrade_10_1_0"/> <bean id="upgrade_10_3_0" class="org.olat.upgrade.OLATUpgrade_10_3_0"/> + <bean id="upgrade_10_4_0" class="org.olat.upgrade.OLATUpgrade_10_4_0"/> </list> </property> </bean> diff --git a/src/main/java/org/olat/upgrade/model/KalendarConfig.java b/src/main/java/org/olat/upgrade/model/KalendarConfig.java new file mode 100644 index 00000000000..fab3963784c --- /dev/null +++ b/src/main/java/org/olat/upgrade/model/KalendarConfig.java @@ -0,0 +1,71 @@ +/** + * <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.upgrade.model; + +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; + +/** + * Used to upgrade de GUi preferences of calendars + * + * Initial date: 25.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class KalendarConfig { + + private String css; + private boolean vis; + private Long resId; + + public KalendarConfig() { + this(KalendarRenderWrapper.CALENDAR_COLOR_BLUE, true); + } + + public KalendarConfig(String calendarCSS, boolean visible) { + + this.css = calendarCSS; + this.vis = visible; + this.resId = null; + } + + public Long getResId() { + return this.resId; + } + + public void setResId(Long resId) { + this.resId = resId; + } + + public String getCss() { + return css; + } + + public void setCss(String css) { + this.css = css; + } + + public boolean isVis() { + return vis; + } + + public void setVis(boolean vis) { + this.vis = vis; + } +} diff --git a/src/main/java/org/olat/upgrade/model/UpgradePreferences.java b/src/main/java/org/olat/upgrade/model/UpgradePreferences.java new file mode 100644 index 00000000000..971523eb019 --- /dev/null +++ b/src/main/java/org/olat/upgrade/model/UpgradePreferences.java @@ -0,0 +1,42 @@ +/** + * <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.upgrade.model; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * + * Initial date: 25.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class UpgradePreferences implements Serializable { + + private static final long serialVersionUID = 3828851618949061953L; + + // keys: prefs-keys; values: any Prefs-Objects + private Map<String,Object> prefstore = new HashMap<String,Object>(); + + public Map<String,Object> getPreferences() { + return prefstore; + } +} diff --git a/src/main/java/org/olat/user/UserInfoMainController.java b/src/main/java/org/olat/user/UserInfoMainController.java index 10f37c8e0e2..14e6ae3f9dc 100644 --- a/src/main/java/org/olat/user/UserInfoMainController.java +++ b/src/main/java/org/olat/user/UserInfoMainController.java @@ -32,8 +32,8 @@ import java.util.ArrayList; import java.util.List; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.model.KalendarConfig; +import org.olat.commons.calendar.CalendarModule; +import org.olat.commons.calendar.model.CalendarUserConfiguration; import org.olat.commons.calendar.ui.WeeklyCalendarController; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.core.CoreSpringFactory; @@ -116,6 +116,10 @@ public class UserInfoMainController extends MainLayoutBasicController implements private UserManager userManager; @Autowired private InvitationDAO invitationDao; + @Autowired + private CalendarModule calendarModule; + @Autowired + private CalendarManager calendarManager; /** * @param ureq @@ -251,12 +255,16 @@ public class UserInfoMainController extends MainLayoutBasicController implements boolean isInvitee = invitationDao.isInvitee(chosenIdentity); boolean isDeleted = chosenIdentity.getStatus().equals(Identity.STATUS_DELETED); + + if ( !isDeleted && ! isInvitee) { - gtn = new GenericTreeNode(); - gtn.setTitle(translate("menu.calendar")); - gtn.setUserObject(CMD_CALENDAR); - gtn.setAltText(translate("menu.calendar.alt")); - root.addChild(gtn); + if(calendarModule.isEnablePersonalCalendar()) { + gtn = new GenericTreeNode(); + gtn.setTitle(translate("menu.calendar")); + gtn.setUserObject(CMD_CALENDAR); + gtn.setAltText(translate("menu.calendar.alt")); + root.addChild(gtn); + } gtn = new GenericTreeNode(); gtn.setTitle(translate("menu.folder")); @@ -314,15 +322,14 @@ public class UserInfoMainController extends MainLayoutBasicController implements private WeeklyCalendarController doOpenCalendar(UserRequest ureq) { removeAsListenerAndDispose(calendarController); - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(chosenIdentity); - calendarWrapper.setKalendarConfig(new KalendarConfig(chosenIdentity.getName(), KalendarRenderWrapper.CALENDAR_COLOR_BLUE, true)); - KalendarConfig config = calendarManager.findKalendarConfigForIdentity(calendarWrapper.getKalendar(), ureq); + CalendarUserConfiguration config = calendarManager.findCalendarConfigForIdentity(calendarWrapper.getKalendar(), getIdentity()); if (config != null) { - calendarWrapper.getKalendarConfig().setCss(config.getCss()); - calendarWrapper.getKalendarConfig().setVis(config.isVis()); + calendarWrapper.setConfiguration(config); } - if (ureq.getUserSession().getRoles().isOLATAdmin() || chosenIdentity.getName().equals(ureq.getIdentity().getName())) { + + calendarWrapper.setPrivateEventsVisible(chosenIdentity.equals(ureq.getIdentity())); + if (ureq.getUserSession().getRoles().isOLATAdmin() || chosenIdentity.equals(ureq.getIdentity())) { calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE); } else { calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY); @@ -332,7 +339,8 @@ public class UserInfoMainController extends MainLayoutBasicController implements OLATResourceable ores = OresHelper.createOLATResourceableType(CMD_CALENDAR); WindowControl bwControl = addToHistory(ureq, ores, null); - calendarController = new WeeklyCalendarController(ureq, bwControl, calendars, WeeklyCalendarController.CALLER_PROFILE, true); + calendarController = new WeeklyCalendarController(ureq, bwControl, calendars, + WeeklyCalendarController.CALLER_PROFILE, false); listenTo(calendarController); return calendarController; } diff --git a/src/main/resources/database/mysql/alter_10_3_4_to_10_4_0.sql b/src/main/resources/database/mysql/alter_10_3_4_to_10_4_0.sql new file mode 100644 index 00000000000..128fabaf984 --- /dev/null +++ b/src/main/resources/database/mysql/alter_10_3_4_to_10_4_0.sql @@ -0,0 +1,54 @@ +create table o_cal_use_config ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_token varchar(36), + c_cssclass varchar(36), + c_visible bit not null default 1, + c_aggregated_feed bit not null default 1, + fk_identity bigint not null, + primary key (id), + unique (c_calendar_id, c_calendar_type, fk_identity) +); +alter table o_cal_use_config ENGINE = InnoDB; + +alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_u_conf_cal_id_idx on o_cal_use_config (c_calendar_id); +create index idx_cal_u_conf_cal_type_idx on o_cal_use_config (c_calendar_type); + + +create table o_cal_import ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_displayname varchar(256), + c_lastupdate datetime not null, + c_url varchar(1024), + fk_identity bigint, + primary key (id) +); +alter table o_cal_import ENGINE = InnoDB; + +alter table o_cal_import add constraint cal_imp_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_imp_cal_id_idx on o_cal_import (c_calendar_id); +create index idx_cal_imp_cal_type_idx on o_cal_import (c_calendar_type); + + +create table o_cal_import_to ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + c_to_calendar_id varchar(128) not null, + c_to_calendar_type varchar(16) not null, + c_lastupdate datetime not null, + c_url varchar(1024), + primary key (id) +); +alter table o_cal_import_to ENGINE = InnoDB; + +create index idx_cal_imp_to_cal_id_idx on o_cal_import_to (c_to_calendar_id); +create index idx_cal_imp_to_cal_type_idx on o_cal_import_to (c_to_calendar_type); \ No newline at end of file diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 2db70762bd9..50a16f6366a 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1134,6 +1134,46 @@ create table o_cer_certificate ( primary key (id) ); +-- calendar +create table o_cal_use_config ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_token varchar(36), + c_cssclass varchar(36), + c_visible bit not null default 1, + c_aggregated_feed bit not null default 1, + fk_identity bigint not null, + primary key (id), + unique (c_calendar_id, c_calendar_type, fk_identity) +); + +create table o_cal_import ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_displayname varchar(256), + c_lastupdate datetime not null, + c_url varchar(1024), + fk_identity bigint, + primary key (id) +); + +create table o_cal_import_to ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + c_to_calendar_id varchar(128) not null, + c_to_calendar_type varchar(16) not null, + c_lastupdate datetime not null, + c_url varchar(1024), + primary key (id) +); + -- instant messaging create table if not exists o_im_message ( id bigint not null, @@ -1736,6 +1776,9 @@ alter table o_as_user_course_infos ENGINE = InnoDB; alter table o_as_mode_course ENGINE = InnoDB; alter table o_as_mode_course ENGINE = InnoDB; alter table o_as_mode_course_to_area ENGINE = InnoDB; +alter table o_cal_use_config ENGINE = InnoDB; +alter table o_cal_import ENGINE = InnoDB; +alter table o_cal_import_to ENGINE = InnoDB; alter table o_mapper ENGINE = InnoDB; alter table o_qp_pool ENGINE = InnoDB; alter table o_qp_taxonomy_level ENGINE = InnoDB; @@ -2057,6 +2100,18 @@ alter table o_as_user_course_infos add index user_course_infos_id_cstr (fk_ident alter table o_as_user_course_infos add index user_course_infos_res_cstr (fk_resource_id), add constraint user_course_infos_res_cstr foreign key (fk_resource_id) references o_olatresource (resource_id); alter table o_as_user_course_infos add unique (fk_identity, fk_resource_id); +-- calendar +alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_u_conf_cal_id_idx on o_cal_use_config (c_calendar_id); +create index idx_cal_u_conf_cal_type_idx on o_cal_use_config (c_calendar_type); + +alter table o_cal_import add constraint cal_imp_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_imp_cal_id_idx on o_cal_import (c_calendar_id); +create index idx_cal_imp_cal_type_idx on o_cal_import (c_calendar_type); + +create index idx_cal_imp_to_cal_id_idx on o_cal_import_to (c_to_calendar_id); +create index idx_cal_imp_to_cal_type_idx on o_cal_import_to (c_to_calendar_type); + -- mapper create index o_mapper_uuid_idx on o_mapper (mapper_uuid); diff --git a/src/main/resources/database/oracle/alter_10_3_4_to_10_4_0.sql b/src/main/resources/database/oracle/alter_10_3_4_to_10_4_0.sql new file mode 100644 index 00000000000..657661f9253 --- /dev/null +++ b/src/main/resources/database/oracle/alter_10_3_4_to_10_4_0.sql @@ -0,0 +1,53 @@ +create table o_cal_use_config ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + c_calendar_id varchar2(128 char) not null, + c_calendar_type varchar2(16 char) not null, + c_token varchar2(36 char), + c_cssclass varchar2(36 char), + c_visible number default 1 not null, + c_aggregated_feed number default 1 not null, + fk_identity number(20) not null, + primary key (id), + unique (c_calendar_id, c_calendar_type, fk_identity) +); + +alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_u_conf_to_ident_idx on o_cal_use_config (fk_identity); +create index idx_cal_u_conf_cal_id_idx on o_cal_use_config (c_calendar_id); +create index idx_cal_u_conf_cal_type_idx on o_cal_use_config (c_calendar_type); + + +create table o_cal_import ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + c_calendar_id varchar2(128 char) not null, + c_calendar_type varchar2(16 char) not null, + c_displayname varchar2(256 char), + c_lastupdate date not null, + c_url varchar2(1024 char), + fk_identity number(20), + primary key (id) +); + +alter table o_cal_import add constraint cal_imp_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_imp_to_ident_idx on o_cal_import (fk_identity); +create index idx_cal_imp_cal_id_idx on o_cal_import (c_calendar_id); +create index idx_cal_imp_cal_type_idx on o_cal_import (c_calendar_type); + + +create table o_cal_import_to ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + c_to_calendar_id varchar2(128 char) not null, + c_to_calendar_type varchar2(16 char) not null, + c_lastupdate date not null, + c_url varchar2(1024 char), + primary key (id) +); + +create index idx_cal_imp_to_cal_id_idx on o_cal_import_to (c_to_calendar_id); +create index idx_cal_imp_to_cal_type_idx on o_cal_import_to (c_to_calendar_type); \ No newline at end of file diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index c08b55b49a8..ea2e0f54b71 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1168,6 +1168,47 @@ create table o_cer_certificate ( primary key (id) ); +-- calendar +create table o_cal_use_config ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + c_calendar_id varchar2(128 char) not null, + c_calendar_type varchar2(16 char) not null, + c_token varchar2(36 char), + c_cssclass varchar2(36 char), + c_visible number default 1 not null, + c_aggregated_feed number default 1 not null, + fk_identity number(20) not null, + primary key (id), + unique (c_calendar_id, c_calendar_type, fk_identity) +); + +create table o_cal_import ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + c_calendar_id varchar2(128 char) not null, + c_calendar_type varchar2(16 char) not null, + c_displayname varchar2(256 char), + c_lastupdate date not null, + c_url varchar2(1024 char), + fk_identity number(20), + primary key (id) +); + +create table o_cal_import_to ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + c_to_calendar_id varchar2(128 char) not null, + c_to_calendar_type varchar2(16 char) not null, + c_lastupdate date not null, + c_url varchar2(1024 char), + primary key (id) +); + +-- instant messaging create table o_im_message ( id number(20) not null, creationdate date, @@ -2204,6 +2245,20 @@ alter table o_as_eff_statement add constraint eff_statement_id_cstr foreign key create index idx_eff_statement_ident_idx on o_as_eff_statement (fk_identity); create index eff_statement_repo_key_idx on o_as_eff_statement (course_repo_key); +-- calendar +alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_u_conf_to_ident_idx on o_cal_use_config (fk_identity); +create index idx_cal_u_conf_cal_id_idx on o_cal_use_config (c_calendar_id); +create index idx_cal_u_conf_cal_type_idx on o_cal_use_config (c_calendar_type); + +alter table o_cal_import add constraint cal_imp_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_imp_to_ident_idx on o_cal_import (fk_identity); +create index idx_cal_imp_cal_id_idx on o_cal_import (c_calendar_id); +create index idx_cal_imp_cal_type_idx on o_cal_import (c_calendar_type); + +create index idx_cal_imp_to_cal_id_idx on o_cal_import_to (c_to_calendar_id); +create index idx_cal_imp_to_cal_type_idx on o_cal_import_to (c_to_calendar_type); + -- course infos alter table o_as_user_course_infos add constraint user_course_infos_id_cstr foreign key (fk_identity) references o_bs_identity (id); create index idx_ucourseinfos_ident_idx on o_as_user_course_infos (fk_identity); diff --git a/src/main/resources/database/postgresql/alter_10_3_4_to_10_4_0.sql b/src/main/resources/database/postgresql/alter_10_3_4_to_10_4_0.sql new file mode 100644 index 00000000000..a84581abca9 --- /dev/null +++ b/src/main/resources/database/postgresql/alter_10_3_4_to_10_4_0.sql @@ -0,0 +1,53 @@ +create table o_cal_use_config ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_token varchar(36), + c_cssclass varchar(36), + c_visible bool default true, + c_aggregated_feed bool default true, + fk_identity int8 not null, + primary key (id), + unique (c_calendar_id, c_calendar_type, fk_identity) +); + +alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_u_conf_to_ident_idx on o_cal_use_config (fk_identity); +create index idx_cal_u_conf_cal_id_idx on o_cal_use_config (c_calendar_id); +create index idx_cal_u_conf_cal_type_idx on o_cal_use_config (c_calendar_type); + + +create table o_cal_import ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_displayname varchar(256), + c_lastupdate timestamp not null, + c_url varchar(1024), + fk_identity int8, + primary key (id) +); + +alter table o_cal_import add constraint cal_imp_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_imp_to_ident_idx on o_cal_import (fk_identity); +create index idx_cal_imp_cal_id_idx on o_cal_import (c_calendar_id); +create index idx_cal_imp_cal_type_idx on o_cal_import (c_calendar_type); + + +create table o_cal_import_to ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + c_to_calendar_id varchar(128) not null, + c_to_calendar_type varchar(16) not null, + c_lastupdate timestamp not null, + c_url varchar(1024), + primary key (id) +); + +create index idx_cal_imp_to_cal_id_idx on o_cal_import_to (c_to_calendar_id); +create index idx_cal_imp_to_cal_type_idx on o_cal_import_to (c_to_calendar_type); \ No newline at end of file diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 4273252613f..f84636e4b99 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1135,6 +1135,46 @@ create table o_cer_certificate ( primary key (id) ); +-- calendar +create table o_cal_use_config ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_token varchar(36), + c_cssclass varchar(36), + c_visible bool default true, + c_aggregated_feed bool default true, + fk_identity int8 not null, + primary key (id), + unique (c_calendar_id, c_calendar_type, fk_identity) +); + +create table o_cal_import ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + c_calendar_id varchar(128) not null, + c_calendar_type varchar(16) not null, + c_displayname varchar(256), + c_lastupdate timestamp not null, + c_url varchar(1024), + fk_identity int8, + primary key (id) +); + +create table o_cal_import_to ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + c_to_calendar_id varchar(128) not null, + c_to_calendar_type varchar(16) not null, + c_lastupdate timestamp not null, + c_url varchar(1024), + primary key (id) +); + -- instant messaging create table o_im_message ( id int8 not null, @@ -2057,6 +2097,20 @@ alter table o_as_user_course_infos add constraint user_course_infos_res_cstr for create index idx_ucourseinfos_rsrc_idx on o_as_user_course_infos (fk_resource_id); alter table o_as_user_course_infos add unique (fk_identity, fk_resource_id); +-- calendar +alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_u_conf_to_ident_idx on o_cal_use_config (fk_identity); +create index idx_cal_u_conf_cal_id_idx on o_cal_use_config (c_calendar_id); +create index idx_cal_u_conf_cal_type_idx on o_cal_use_config (c_calendar_type); + +alter table o_cal_import add constraint cal_imp_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_cal_imp_to_ident_idx on o_cal_import (fk_identity); +create index idx_cal_imp_cal_id_idx on o_cal_import (c_calendar_id); +create index idx_cal_imp_cal_type_idx on o_cal_import (c_calendar_type); + +create index idx_cal_imp_to_cal_id_idx on o_cal_import_to (c_to_calendar_id); +create index idx_cal_imp_to_cal_type_idx on o_cal_import_to (c_to_calendar_type); + -- mapper create index o_mapper_uuid_idx on o_mapper (mapper_uuid); diff --git a/src/main/webapp-tomcat/WEB-INF/web.xml b/src/main/webapp-tomcat/WEB-INF/web.xml index a3acc13e1f8..5d71317fb3a 100644 --- a/src/main/webapp-tomcat/WEB-INF/web.xml +++ b/src/main/webapp-tomcat/WEB-INF/web.xml @@ -3,13 +3,14 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" - version="3.1"> + version="3.1" + metadata-complete="true"> <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> <!-- IMPORTANT : DO NOT CHANGE SEQUENCE OF XML TYPES BECAUSE VALIDATION MAY FAIL --> <!-- Please check before checkin http://www.xmlvalidation.com/index.php --> <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> - + <absolute-ordering /> <display-name>OLAT Online Learning and Training</display-name> <description> Online Learning and Training Application (OLAT) is a Learning Management Platform. diff --git a/src/main/webapp/static/themes/light/modules/_cal.scss b/src/main/webapp/static/themes/light/modules/_cal.scss index bf298002203..ce71e689143 100644 --- a/src/main/webapp/static/themes/light/modules/_cal.scss +++ b/src/main/webapp/static/themes/light/modules/_cal.scss @@ -42,6 +42,13 @@ display: inline; } +.o_cal_config_color { + display: block; + width: 16px; + height: 16px; + border-radius:8px; +} + .o_cal_colorchooser_selected:before { content: $fa-var-check; } diff --git a/src/main/webapp/static/themes/light/modules/_icons.scss b/src/main/webapp/static/themes/light/modules/_icons.scss index 14722d80b82..6892f0b6fc1 100644 --- a/src/main/webapp/static/themes/light/modules/_icons.scss +++ b/src/main/webapp/static/themes/light/modules/_icons.scss @@ -48,6 +48,7 @@ $fa-css-prefix: "o_icon" !default; .o_icon_calendar_enabled:before { content: $fa-var-check-circle-o;} .o_icon_calendar_disabled:before { content: $fa-var-circle-o;} .o_icon_calendar:before { content: $fa-var-calendar;} +.o_icon_calendar_sync:before { content: $fa-var-refresh; } .o_icon_caret:before { content: $fa-var-caret-down;} .o_icon_caret_right:before { content: $fa-var-caret-right;} .o_icon_catalog:before { content: $fa-var-sitemap;} diff --git a/src/main/webapp/static/themes/light/theme.css b/src/main/webapp/static/themes/light/theme.css index d7086b3d2fb..40fba4d0c5d 100644 --- a/src/main/webapp/static/themes/light/theme.css +++ b/src/main/webapp/static/themes/light/theme.css @@ -1,4 +1,4 @@ -/*! +/*! * ======================================================== * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> @@ -22,5 +22,51 @@ * @author gnaegi, www.frentix.com * @date April. 2014 * ======================================================== -**//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail,.o_form .o_filepreview img,.o_feed .o_media{padding:4px;line-height:1.42857;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,.o_cal .fc-header-title h2,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,.o_cal .fc-header-title h2 small,h4 .small,.o_cal .fc-header-title h2 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 small,.h6 .small{font-weight:normal;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,h1 .small,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.o_cal .fc-header-title h2,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.o_cal .fc-header-title h2 small,h4 .small,.o_cal .fc-header-title h2 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .small,.h6 small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.o_cal .fc-header-title h2,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width: 768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff}.bg-primary{background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857}dt{font-weight:bold}dd{margin-left:0}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-15px;margin-right:-15px}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-1{width:8.33333%}.col-xs-2{width:16.66667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333%}.col-xs-5{width:41.66667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333%}.col-xs-8{width:66.66667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333%}.col-xs-11{width:91.66667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333%}.col-xs-pull-2{right:16.66667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333%}.col-xs-pull-5{right:41.66667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333%}.col-xs-pull-8{right:66.66667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333%}.col-xs-pull-11{right:91.66667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333%}.col-xs-push-2{left:16.66667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333%}.col-xs-push-5{left:41.66667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333%}.col-xs-push-8{left:66.66667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333%}.col-xs-push-11{left:91.66667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333%}.col-xs-offset-2{margin-left:16.66667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333%}.col-xs-offset-5{margin-left:41.66667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333%}.col-xs-offset-8{margin-left:66.66667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333%}.col-xs-offset-11{margin-left:91.66667%}.col-xs-offset-12{margin-left:100%}@media (min-width: 768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-1{width:8.33333%}.col-sm-2{width:16.66667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333%}.col-sm-5{width:41.66667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333%}.col-sm-8{width:66.66667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333%}.col-sm-11{width:91.66667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333%}.col-sm-pull-2{right:16.66667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333%}.col-sm-pull-5{right:41.66667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333%}.col-sm-pull-8{right:66.66667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333%}.col-sm-pull-11{right:91.66667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333%}.col-sm-push-2{left:16.66667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333%}.col-sm-push-5{left:41.66667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333%}.col-sm-push-8{left:66.66667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333%}.col-sm-push-11{left:91.66667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333%}.col-sm-offset-2{margin-left:16.66667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333%}.col-sm-offset-5{margin-left:41.66667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333%}.col-sm-offset-8{margin-left:66.66667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333%}.col-sm-offset-11{margin-left:91.66667%}.col-sm-offset-12{margin-left:100%}}@media (min-width: 992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-1{width:8.33333%}.col-md-2{width:16.66667%}.col-md-3{width:25%}.col-md-4{width:33.33333%}.col-md-5{width:41.66667%}.col-md-6{width:50%}.col-md-7{width:58.33333%}.col-md-8{width:66.66667%}.col-md-9{width:75%}.col-md-10{width:83.33333%}.col-md-11{width:91.66667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333%}.col-md-pull-2{right:16.66667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333%}.col-md-pull-5{right:41.66667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333%}.col-md-pull-8{right:66.66667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333%}.col-md-pull-11{right:91.66667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333%}.col-md-push-2{left:16.66667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333%}.col-md-push-5{left:41.66667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333%}.col-md-push-8{left:66.66667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333%}.col-md-push-11{left:91.66667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333%}.col-md-offset-2{margin-left:16.66667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333%}.col-md-offset-5{margin-left:41.66667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333%}.col-md-offset-8{margin-left:66.66667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333%}.col-md-offset-11{margin-left:91.66667%}.col-md-offset-12{margin-left:100%}}@media (min-width: 1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-1{width:8.33333%}.col-lg-2{width:16.66667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333%}.col-lg-5{width:41.66667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333%}.col-lg-8{width:66.66667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333%}.col-lg-11{width:91.66667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333%}.col-lg-pull-2{right:16.66667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333%}.col-lg-pull-5{right:41.66667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333%}.col-lg-pull-8{right:66.66667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333%}.col-lg-pull-11{right:91.66667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333%}.col-lg-push-2{left:16.66667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333%}.col-lg-push-5{left:41.66667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333%}.col-lg-push-8{left:66.66667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333%}.col-lg-push-11{left:91.66667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333%}.col-lg-offset-2{margin-left:16.66667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333%}.col-lg-offset-5{margin-left:41.66667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333%}.col-lg-offset-8{margin-left:66.66667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333%}.col-lg-offset-11{margin-left:91.66667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.table>tfoot>tr>td{padding:8px;line-height:1.42857;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio: 0){input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{line-height:34px}input[type="date"].input-sm,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,input[type="time"].input-sm,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-sm,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-sm,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn{line-height:30px}input[type="date"].input-lg,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,input[type="time"].input-lg,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-lg,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-lg,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-left:0;padding-right:0}.input-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn,.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn,.form-group-sm .form-control{height:30px;line-height:30px}textarea.input-sm,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,.form-group-sm .form-control,select[multiple].input-sm,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn,.form-group-sm .form-control{height:auto}.input-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn,.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn,.form-group-lg .form-control{height:46px;line-height:46px}textarea.input-lg,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,.form-group-lg .form-control,select[multiple].input-lg,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn,.form-group-lg .form-control{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label ~ .form-control-feedback{top:25px}.has-feedback label.sr-only ~ .form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width: 768px){.form-inline .form-group,.o_navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control,.o_navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static,.o_navbar-form .form-control-static{display:inline-block}.form-inline .input-group,.o_navbar-form .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.o_navbar-form .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.o_navbar-form .input-group .input-group-btn,.form-inline .input-group .form-control,.o_navbar-form .input-group .form-control{width:auto}.form-inline .input-group>.form-control,.o_navbar-form .input-group>.form-control{width:100%}.form-inline .control-label,.o_navbar-form .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.o_navbar-form .radio,.form-inline .checkbox,.o_navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.o_navbar-form .radio label,.form-inline .checkbox label,.o_navbar-form .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.o_navbar-form .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"],.o_navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback,.o_navbar-form .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.focus,.btn.active:focus,.btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default.focus,.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled,.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled.active,.btn-default[disabled],.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled].active,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:hover,.btn-primary:focus,.btn-primary.focus,.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled.active,.btn-primary[disabled],.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary.active{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success.focus,.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled,.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled.active,.btn-success[disabled],.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled].active,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info.focus,.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled,.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled.active,.btn-info[disabled],.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled].active,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning.focus,.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled,.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled.active,.btn-warning[disabled],.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger.focus,.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled,.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled.active,.btn-danger[disabled],.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#337ab7;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#337ab7}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar:before,.btn-toolbar:after{content:" ";display:table}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle,.btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret,.btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret,.dropup .btn-group-lg>.btn .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{content:" ";display:table}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav:before,.nav:after{content:" ";display:table}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-justified>li,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{content:" ";display:table}.navbar:after{clear:both}@media (min-width: 768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{content:" ";display:table}.navbar-header:after{clear:both}@media (min-width: 768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{content:" ";display:table}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;visibility:visible !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width: 768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width: 768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width: 768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width: 768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width: 767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width: 768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-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 (min-width: 768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width: 767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 768px){.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}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm,.btn-group-sm>.navbar-btn.btn{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs,.btn-group-xs>.navbar-btn.btn{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width: 768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right ~ .navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width: 767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:hover,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#090909}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#090909}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#090909;color:#fff}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:hover,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/ ";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857;text-decoration:none;color:#337ab7;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager:before,.pager:after{content:" ";display:table}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.jumbotron,.o_repo_details .o_lead{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.o_repo_details .o_lead h1,.jumbotron .h1,.o_repo_details .o_lead .h1{color:inherit}.jumbotron p,.o_repo_details .o_lead p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr,.o_repo_details .o_lead>hr{border-top-color:#d5d5d5}.container .jumbotron,.container .o_repo_details .o_lead,.o_repo_details .container .o_lead,.container-fluid .jumbotron,.container-fluid .o_repo_details .o_lead,.o_repo_details .container-fluid .o_lead{border-radius:6px}.jumbotron .container,.o_repo_details .o_lead .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron,.o_repo_details .o_lead{padding:48px 0}.container .jumbotron,.container .o_repo_details .o_lead,.o_repo_details .container .o_lead,.container-fluid .jumbotron,.container-fluid .o_repo_details .o_lead,.o_repo_details .container-fluid .o_lead{padding-left:60px;padding-right:60px}.jumbotron h1,.o_repo_details .o_lead h1,.jumbotron .h1,.o_repo_details .o_lead .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#333}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#337ab7}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4,.alert .o_cal .fc-header-title h2,.o_cal .fc-header-title .alert h2{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;color:#555;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#777;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table,.panel-collapse>.table,.panel-collapse>.table-responsive>.table,.panel-collapse>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption,.panel-collapse>.table caption,.panel-collapse>.table-responsive>.table caption,.panel-collapse>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child,.panel-collapse>.table:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child,.panel-collapse>.table:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body,.panel-collapse>.panel-body+.table,.panel-collapse>.panel-body+.table-responsive,.panel-collapse>.table+.panel-body,.panel-collapse>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td,.panel-collapse>.table>tbody:first-child>tr:first-child th,.panel-collapse>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered,.panel-collapse>.table-bordered,.panel-collapse>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel-collapse>.table-bordered>thead>tr>th:first-child,.panel-collapse>.table-bordered>thead>tr>td:first-child,.panel-collapse>.table-bordered>tbody>tr>th:first-child,.panel-collapse>.table-bordered>tbody>tr>td:first-child,.panel-collapse>.table-bordered>tfoot>tr>th:first-child,.panel-collapse>.table-bordered>tfoot>tr>td:first-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel-collapse>.table-bordered>thead>tr>th:last-child,.panel-collapse>.table-bordered>thead>tr>td:last-child,.panel-collapse>.table-bordered>tbody>tr>th:last-child,.panel-collapse>.table-bordered>tbody>tr>td:last-child,.panel-collapse>.table-bordered>tfoot>tr>th:last-child,.panel-collapse>.table-bordered>tfoot>tr>td:last-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel-collapse>.table-bordered>thead>tr:first-child>td,.panel-collapse>.table-bordered>thead>tr:first-child>th,.panel-collapse>.table-bordered>tbody>tr:first-child>td,.panel-collapse>.table-bordered>tbody>tr:first-child>th,.panel-collapse>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel-collapse>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel-collapse>.table-bordered>tbody>tr:last-child>td,.panel-collapse>.table-bordered>tbody>tr:last-child>th,.panel-collapse>.table-bordered>tfoot>tr:last-child>td,.panel-collapse>.table-bordered>tfoot>tr:last-child>th,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive,.panel-collapse>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{content:" ";display:table}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width: 992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;visibility:visible;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:normal;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:normal;line-height:1.42857;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width: 767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width: 767px){.visible-xs-block{display:block !important}}@media (max-width: 767px){.visible-xs-inline{display:inline !important}}@media (max-width: 767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-block{display:block !important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline{display:inline !important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-block{display:block !important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline{display:inline !important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width: 1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width: 1200px){.visible-lg-block{display:block !important}}@media (min-width: 1200px){.visible-lg-inline{display:inline !important}}@media (min-width: 1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width: 767px){.hidden-xs{display:none !important}}@media (min-width: 768px) and (max-width: 991px){.hidden-sm{display:none !important}}@media (min-width: 992px) and (max-width: 1199px){.hidden-md{display:none !important}}@media (min-width: 1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}body .modal{position:absolute;overflow:visible}body div.tooltip-inner{max-width:400px}body div.popover{max-width:450px}body .modal-body.alert{border-radius:0}body .progress{margin-bottom:0}.panel-body:nth-child(n+2){border-top:1px solid #ddd}.panel .panel-heading[data-toggle="collapse"]{cursor:pointer}#o_ajax_busy_backdrop{bottom:0;z-index:1020}.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}@-moz-document url-prefix(){fieldset{display:table-cell}}@font-face{font-family:'openolat';src:url("../light/fonts/openolat/openolat.eot?4yacgg");src:url("../light/fonts/openolat/openolat.eot?#iefix4yacgg") format("embedded-opentype"),url("../light/fonts/openolat/openolat.woff?4yacgg") format("woff"),url("../light/fonts/openolat/openolat.ttf?4yacgg") format("truetype"),url("../light/fonts/openolat/openolat.svg?4yacgg#openolat") format("svg");font-weight:normal;font-style:normal}@font-face{font-family:'FontAwesome';src:url("../../font-awesome/fonts/fontawesome-webfont.eot?v=4.3.0");src:url("../../font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.3.0") format("embedded-opentype"),url("../../font-awesome/fonts/fontawesome-webfont.woff2?v=4.3.0") format("woff2"),url("../../font-awesome/fonts/fontawesome-webfont.woff?v=4.3.0") format("woff"),url("../../font-awesome/fonts/fontawesome-webfont.ttf?v=4.3.0") format("truetype"),url("../../font-awesome/fonts/fontawesome-webfont.svg?v=4.3.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;transform:translate(0, 0)}.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}.o_icon-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-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_accepted:before{content:"ï…¤"}.o_icon_accessibility:before{content:""}.o_icon_actions:before{content:"ï‚…"}.o_icon_archive_tool:before{content:""}.o_icon_assessment_mode:before{content:"ï„"}.o_icon_assessment_tool:before{content:"ï‚‘"}.o_icon_attempt_limit:before{content:""}.o_icon_accept:before{content:"";color:#5cb85c}.o_icon_add:before{content:"ï•"}.o_icon_add_member:before{content:""}.o_icon_add_search:before{content:""}.o_icon_audio:before{content:""}.o_icon_back:before{content:"ï“"}.o_icon_back_history:before{content:""}.o_icon_banned:before{content:"";color:#d9534f}.o_icon_bold:before{content:""}.o_icon_booking:before{content:"ïº"}.o_icon_bookmark:before{content:"";color:#bc2d0c}.o_icon_bookmark_add:before{content:"ï‚—"}.o_icon_bookmark_header:before{content:""}.o_icon_browse:before{content:""}.o_icon_browsercheck:before{content:"ï…¤"}.o_icon_busy:before{content:"ï„"}.o_icon_calendar:before{content:"ï³"}.o_icon_calendar_enabled:before{content:"ï"}.o_icon_calendar_disabled:before{content:"ï„Œ"}.o_icon_calendar:before{content:"ï³"}.o_icon_caret:before{content:""}.o_icon_caret_right:before{content:""}.o_icon_catalog:before{content:""}.o_icon_catalog_sub:before{content:"ï»"}.o_icon_certificate:before{content:"ï‚£"}.o_icon_chat:before{content:""}.o_icon_check:before{content:""}.o_icon_check_off:before{content:"ï‚–"}.o_icon_check_on:before{content:"ï†"}.o_icon_checkbox:before{content:"ï‚–"}.o_icon_checkbox_checked:before{content:"ï…Š"}.o_icon_cleanup:before{content:""}.o_icon_close:before{content:"ï€"}.o_icon_close_resource:before{content:""}.o_icon_close_tab:before{content:"ï€"}.o_icon_close_tool:before{content:"ï€"}.o_icon_close_tree:before{content:""}.o_icon_close_togglebox:before,.o_togglebox_wrapper .o_opener.o_in i:before{content:""}.o_icon_code:before{content:"ï„¡"}.o_icon_color_picker:before{content:"ïƒ"}.o_icon_copy:before{content:""}.o_icon_courseareas:before{content:""}.o_icon_coursedb:before{content:""}.o_icon_courseeditor:before{content:"ï„"}.o_icon_coursefolder:before{content:"ï„”"}.o_icon_courserun:before{content:""}.o_icon_comments:before{content:""}.o_icon_comments_none:before{content:""}.o_icon_content_popup:before{content:"ï‚Ž"}.o_icon_correct_answer:before{content:"";color:#5cb85c}.o_icon_customize:before{content:""}.o_icon_delete_item:before{content:""}.o_icon_delete:before{content:"ï–";color:#A87E7E}.o_icon_details:before{content:""}.o_icon_description:before{content:"ïš"}.o_icon_dev:before{content:""}.o_icon_disabled:before{content:"ï„Œ"}.o_icon_download:before{content:""}.o_icon_edit:before{content:"ï„"}.o_icon_edit_file:before{content:"ï„"}.o_icon_edit_metadata:before{content:""}.o_icon_enabled:before{content:"ï„‘"}.o_icon_enlarge:before{content:""}.o_icon_eportfolio_add:before{content:"ï„®"}.o_icon_eportfolio_link:before{content:"ï„®"}.o_icon_error:before{content:"ïª";color:#d9534f}.o_icon_expenditure:before{content:""}.o_icon_export:before{content:"ï…"}.o_icon_external_link:before{content:"ï‚Ž"}.o_icon_failed:before{content:"ï—"}.o_icon_filter:before{content:"ï‚°"}.o_icon_graduate:before{content:"ï†"}.o_icon_group:before,.o_BusinessGroup_icon:before{content:""}.o_icon_header:before{content:""}.o_icon_help:before{content:"ï™";cursor:help}.o_icon_home:before{content:""}.o_icon_impress:before{content:"ïš"}.o_icon_important:before{content:"ï±";color:#f0ad4e}.o_icon_import:before{content:"ï‚“"}.o_icon_info:before{content:"ïš";color:#5bc0de}.o_icon_info_msg:before{content:"ïª";color:#d9534f}.o_icon_info_resource:before{content:"ïš"}.o_icon_inline_editable:before{content:"ï„"}.o_icon_institution:before{content:""}.o_icon_italic:before{content:""}.o_icon_landingpage:before{content:"ï…€"}.o_icon_language:before{content:""}.o_icon_layout:before{content:""}.o_icon_link:before{content:"ïƒ"}.o_icon_link_extern:before{content:"ï‚Ž"}.o_icon_list:before{content:""}.o_icon_list_num :before{content:""}.o_icon_lifecycle:before{content:"ï³"}.o_icon_locked:before{content:""}.o_icon_login:before{content:"ï‚"}.o_icon_logout:before{content:"ï‚‹"}.o_icon_mandatory:before{content:"ï©";color:#f0ad4e}.o_icon_managed:before{content:"ï¹";color:#777}.o_icon_manual:before{content:"ï€";cursor:help}.o_icon_mail:before{content:""}.o_icon_math:before{content:""}.o_icon_membersmanagement:before{content:""}.o_icon_menuhandel:before{content:""}.o_icon_message:before{content:"ïƒ "}.o_icon_mobile:before{content:"ï„‹"}.o_icon_move:before{content:"ï‡"}.o_icon_move_down:before{content:""}.o_icon_move_left:before{content:"ï„€"}.o_icon_move_right:before{content:"ï„"}.o_icon_move_up:before{content:"ï„‚"}.o_icon_new:before{content:"ï©";color:#5cb85c}.o_icon_new_document:before{content:"ï…œ"}.o_icon_new_folder:before{content:"ï»"}.o_icon_news:before{content:"ïš"}.o_icon_next:before{content:""}.o_icon_next_page:before{content:"ï„"}.o_icon_next_toolbar:before{content:""}.o_icon_node_after:before{content:"ï…µ"}.o_icon_node_before:before{content:"ï…¶"}.o_icon_node_under:before{content:"ï„’"}.o_icon_notes:before{content:"ï€"}.o_icon_notification:before{content:"ï‚ž"}.o_icon_ok:before{content:"";color:#5cb85c}.o_icon_open_tree:before{content:""}.o_icon_open_togglebox:before,.o_togglebox_wrapper .o_opener i:before{content:""}.o_icon_openolat:before,.o_icon_provider_olat:before{content:"\E600";font-family:openolat;font-size:10px}.o_icon_options:before{content:""}.o_icon_pageing:before{content:"ï…"}.o_icon_passed:before{content:"ï˜"}.o_icon_password:before{content:""}.o_icon_pending:before{content:"ï„"}.o_icon_phone:before{content:"ï‚•"}.o_icon_post:before{content:""}.o_icon_preview:before{content:"ï®"}.o_icon_previous:before{content:"ï„·"}.o_icon_previous_page:before{content:"ï„€"}.o_icon_previous_toolbar:before{content:""}.o_icon_print:before{content:""}.o_icon_private:before{content:""}.o_icon_provider_adfs:before{content:"ï…º"}.o_icon_provider_facebook:before{content:"ï‚š"}.o_icon_provider_google:before{content:""}.o_icon_provider_guest:before{content:""}.o_icon_provider_ldap:before{content:""}.o_icon_provider_linkedin:before{content:""}.o_icon_provider_oauth:before{content:""}.o_icon_provider_performx:before{content:""}.o_icon_provider_shibboleth:before{content:""}.o_icon_provider_twitter:before{content:"ï‚™"}.o_icon_publish:before{content:"ï¤"}.o_icon_qrcode:before{content:""}.o_icon_quickview:before{content:"ï®"}.o_icon_radio_off:before{content:"ï„Œ"}.o_icon_radio_on:before{content:"ï"}.o_icon_rating_on:before,.o_rating .o_rating_items.o_enabled .o_icon:hover:before{content:""}.o_icon_rating_off:before{content:""}.o_icon_read:before{content:"ï„Œ"}.o_icon_readonly:before{content:"ï„";color:red}.o_icon_readwrite:before{content:"ï„"}.o_icon_recycle:before{content:""}.o_icon_refresh:before{content:""}.o_icon_reject:before{content:"ï€";color:#d9534f}.o_icon_rejected:before{content:"ï…¥"}.o_icon_reminder:before{content:""}.o_icon_remove:before{content:"ï€"}.o_icon_replace:before{content:""}.o_icon_reply:before{content:"ï„’"}.o_icon_reply_with_quote:before{content:"ï„¢"}.o_icon_response_feedback:before{content:""}.o_icon_review:before{content:"ï®"}.o_icon_rss:before{content:"ï‚ž"}.o_icon_rss_unsubscribe:before{content:"ï‚ž";color:#996633}.o_icon_search:before{content:""}.o_icon_select:before{content:""}.o_icon_send:before{content:"ïƒ "}.o_icon_settings:before{content:"ï‚…"}.o_icon_share:before{content:"ï¤"}.o_icon_show_more:before{content:"ï…"}.o_icon_show_less:before{content:"ï…‘"}.o_icon_show_send:before{content:""}.o_icon_sign_out:before{content:"ï‚‹"}.o_icon_spacer:before{content:"ï¾"}.o_icon_split:before{content:""}.o_icon_sort:before{content:""}.o_icon_sort_asc:before{content:""}.o_icon_sort_desc:before{content:"ïƒ"}.o_icon_sort_menu:before{content:"ï… "}.o_icon_start:before{content:"ï”"}.o_icon_status_available:before{content:"ï„‘";color:#063}.o_icon_status_chat:before{content:"ïµ"}.o_icon_status_dnd:before{content:"";color:#cc3}.o_icon_status_unavailable:before{content:"ïœ";color:#963}.o_icon_statistics_tool:before{content:"ï‚€"}.o_icon_submit:before{content:""}.o_icon_table:before{content:""}.o_icon_table_large:before{content:""}.o_icon_tags:before{content:""}.o_icon_timelimit:before{content:""}.o_icon_toggle:before{content:"ï„‘"}.o_icon_to_read:before{content:"ï„‘"}.o_icon_tool:before{content:""}.o_icon_tools:before{content:"ï‚"}.o_icon_top:before{content:"ï·"}.o_icon_translation_item:before{content:""}.o_icon_translation_package:before{content:"ï„•"}.o_icon_user:before{content:""}.o_icon_user_vip:before{content:"ï†"}.o_icon_user_anonymous:before{content:""}.o_icon_upload:before{content:"ï‚“"}.o_icon_version:before{content:""}.o_icon_video:before{content:""}.o_icon_waiting:before{content:""}.o_icon_warn:before{content:"ï±";color:#f0ad4e}.o_icon_wizard:before{content:"ïƒ"}.o_CourseModule_icon:before,.o_course_icon:before{content:""}.o_EPStructuredMapTemplate_icon:before{content:"ï„®"}.o_FileResource-BLOG_icon:before{content:"ï‚¡"}.o_FileResource-IMSCP_icon:before{content:""}.o_FileResource-PODCAST_icon:before{content:""}.o_FileResource-SHAREDFOLDER:before{content:"ï‚Ž"}.o_FileResource-SCORMCP_icon:before{content:""}.o_FileResource-SURVEY_icon:before{content:"ï„š"}.o_FileResource-TEST_icon:before{content:"ï„"}.o_FileResource-WIKI_icon:before{content:""}.o_FileResource-SHAREDFOLDER_icon:before{content:"ï„•"}.o_FileResource-GLOSSARY_icon:before{content:"ï†"}.o_FileResource-PDF_icon:before{content:"ï‡"}.o_FileResource-XLS_icon:before{content:""}.o_FileResource-PPT_icon:before{content:""}.o_FileResource-DOC_icon:before{content:""}.o_FileResource-ANIM_icon:before{content:""}.o_FileResource-IMAGE_icon:before{content:""}.o_FileResource-SOUND_icon:before{content:""}.o_FileResource-MOVIE_icon:before{content:""}.o_FileResource-FILE_icon:before{content:""}.o_CourseModule_icon_closed:before{content:"ïž"}.o_sp_icon:before{content:""}.o_st_icon:before{content:""}.o_tu_icon:before{content:"ï‚Ž"}.o_bc_icon:before{content:"ï„•"}.o_lti_icon:before{content:"ï‚Ž"}.o_cp_icon:before{content:""}.o_cp_item:before{content:""}.o_scorm_icon:before{content:""}.o_en_icon:before{content:"ï‚"}.o_fo_icon:before{content:""}.o_co_icon:before{content:""}.o_infomsg_icon:before{content:"ïš"}.o_cal_icon:before{content:"ï³"}.o_wiki_icon:before{content:""}.o_podcast_icon:before{content:""}.o_blog_icon:before{content:"ï‚¡"}.o_ep_icon:before{content:"ï„®"}.o_iqtest_icon:before{content:"ï„"}.o_iqself_icon:before{content:"ï„"}.o_iqsurv_icon:before{content:"ï„š"}.o_ta_icon:before{content:"ï‚®"}.o_gta_icon:before{content:"ï‚®"}.o_ms_icon:before{content:""}.o_dialog_icon:before{content:""}.o_projectbroker_icon:before{content:"ï„Œ"}.o_ll_icon:before{content:"ïƒ"}.o_den_icon:before{content:""}.o_cmembers_icon:before{content:""}.o_cl_icon:before{content:"ï†"}.o_vc_icon:before{content:""}.o_vitero_icon:before{content:""}.o_openmeetings_icon:before{content:""}.o_portlet_infomsg_icon:before{content:"ïš"}.o_portlet_quickstart_icon:before{content:""}.o_portlet_bookmark_icon:before{content:""}.o_portlet_groups_icon:before{content:""}.o_portlet_notes_icon:before{content:""}.o_portlet_noti_icon:before{content:"ï‚ž"}.o_portlet_eff_icon:before{content:"ï‚£"}.o_portlet_repository_student_icon:before{content:""}.o_portlet_repository_teacher_icon:before{content:"ï†"}.o_portlet_iframe_icon:before{content:""}.o_portlet_sysinfo_icon:before{content:""}.o_portlet_dyk_icon:before{content:""}.o_portlet_infomessages_icon:before{content:""}.o_portlet_cal_icon:before{content:"ï³"}.o_portlet_institutions_icon:before{content:""}.o_portlet_links_icon:before{content:"ïƒ"}.o_portlet_shibboleth_icon:before{content:"ï‚"}.o_icon_qpool:before{content:""}.o_icon_pool_private:before{content:"ï‚–"}.o_icon_pool_public:before{content:"ï†"}.o_icon_pool_my_items:before{content:""}.o_icon_pool_favorits:before{content:""}.o_icon_pool_collection:before{content:""}.o_icon_pool_pool:before{content:""}.o_icon_pool_share:before{content:""}.o_forum_message_icon:before{content:""}.o_calendar_icon:before{content:"ï³"}.o_forum_status_thread_icon:before{content:""}.o_forum_status_sticky_closed_icon:before{content:"ïž"}.o_forum_status_sticky_icon:before{content:""}.o_forum_status_closed_icon:before{content:"ïž";color:#a94442}.o_forum_status_opened_icon:before{content:"ïž";color:#3c763d}.o_forum_status_hidden_icon:before{content:"ï°";color:#a94442}.o_forum_status_visible_icon:before{content:"ï®";color:#3c763d}.o_mi_qpool_import:before{content:""}.o_mi_qtisection:before{content:""}.o_mi_qtisc:before{content:""}.o_mi_qtimc:before{content:"ï†"}.o_mi_qtikprim:before{content:"ï…Š"}.o_mi_qtifib:before{content:"ï…"}.o_mi_qtiessay:before{content:""}.o_as_mode_leadtime:before{content:""}.o_as_mode_assessment:before{content:"ï‹"}.o_as_mode_followup:before{content:"ïž"}.o_as_mode_closed:before{content:'-'}.o_black_led:before{content:"ï„‘";color:#337ab7}.o_green_led:before{content:"ï„‘";color:#5cb85c}.o_yellow_led:before{content:"ï„‘";color:#f0ad4e}.o_red_led:before{content:"ï„‘";color:#d9534f}.o_ac_token_icon:before{content:"ï‚„"}.o_ac_free_icon:before{content:"ï«"}.o_ac_group_icon:before{content:""}.o_ac_membersonly_icon:before{content:""}.o_ac_paypal_icon:before{content:""}.o_ac_status_canceled_icon:before{content:"ï¨";color:#f0ad4e}.o_ac_status_error_icon:before{content:"ï€";color:#d9534f}.o_ac_status_new_icon:before{content:"ï©";color:#337ab7}.o_ac_status_succes_icon:before{content:"";color:#5cb85c}.o_ac_status_waiting_icon:before{content:"";color:#337ab7}.o_ac_order_status_new_icon:before{content:"ï©";color:#337ab7}.o_ac_order_status_prepayment_icon:before{content:"";color:#5bc0de}.o_ac_order_status_payed_icon:before{content:"";color:#5cb85c}.o_ac_order_status_canceled_icon:before{content:"ï¨";color:#f0ad4e}.o_ac_order_status_error_icon:before{content:"ï€";color:#d9534f}.o_ac_order_status_warning_icon:before{content:"";color:#f0ad4e}.o_scorm_org:before{content:""}.o_scorm_item:before{content:""}.o_scorm_completed:before,.o_scorm_passed:before{content:"ï˜"}.o_scorm_failed:before{content:"ï±"}.o_scorm_incomplete:before{content:"ï±"}.o_scorm_not_attempted:before{background:none}.o_midpub:before{content:"ï˜"}.o_midwarn:before{content:"ï±"}.o_midlock:before{content:""}.o_miderr:before{content:"ïª"}.o_middel:before{content:"ï„"}.o_filetype_file:before,.o_filetype_ico:before{content:""}.o_filetype_folder:before{content:"ï„”"}.o_filetype_folder_open:before{content:"ï„•"}.o_filetype_zip:before,.o_filetype_gz:before,.o_filetype_tar:before,.o_filetype_tgz:before{content:""}.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:""}.o_filetype_bat_icon:before,.o_filetype_bat:before,.o_filetype_exe:before,.o_filetype_app:before,.o_filetype_sh:before{content:""}.o_filetype_xls:before,.o_filetype_xlsx:before{content:""}.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:""}.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:""}.o_filetype_midi:before,.o_filetype_audio:before,.o_filetype_mp3:before,.o_filetype_m3u:before,.o_filetype_wav:before{content:""}.o_filetype_ps:before,.o_filetype_pdf:before{content:"ï‡"}.o_filetype_key:before,.o_filetype_odp:before,.o_filetype_ppt:before,.o_filetype_pptx:before{content:""}.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:""}.o_filetype_odt:before,.o_filetype_pages:before,.o_filetype_doc:before,.o_filetype_docx:before{content:""}.o_icon_share_social:before{content:"ï…"}.o_icon_apple:before{content:"ï…¹"}.o_icon_facebook:before{content:"ï‚‚"}.o_icon_twitter:before{content:"ï‚"}.o_icon_google:before{content:""}.o_icon_delicious:before{content:""}.o_icon_digg:before{content:""}.o_icon_mailto:before{content:""}.o_icon_link:before{content:"ïƒ"}.o_icon_yahoo:before{content:""}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_block_inline,.o_block_inline_left,.o_block_inline_both,.o_block_inline_right{display:inline-block}.o_block_inline_left,.o_block_inline_both{margin-left:0.5em}.o_block_inline_right,.o_block_inline_both{margin-right:0.5em}.o_scrollblock,div.b_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,.b_xsmall,p.b_xsmall,div.b_xsmall{font-size:12px}.o_small,.b_small,p.b_small,div.b_small,.o_comments .o_comment_wrapper h5,.o_comments .o_comment_wrapper .o_comment,.o_bc_meta,.tooltip,.o_htmleditor .o_metadata .o_lastmodified,.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,.b_large,p.b_large,div.b_large{font-size:18px}.o_xlarge,.b_xlarge,p.b_xlarge,div.b_xlarge{font-size:18px}.o_disabled,.b_disabled,p.b_disabled,div.b_disabled{color:#777 !important;cursor:default}.o_disabled:hover,.b_disabled:hover{color:#777 !important}.o_dimmed,.b_dimmed,p.b_dimmed,div.b_dimmed{opacity:0.4;filter:alpha(opacity=40)}.o_selected,.b_selected,p.b_selected,div.b_selected{font-weight:bold}.o_deleted,.b_deleted,p.b_deleted,div.b_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,.b_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,.b_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,.b_copy_code,p.b_copy_code,div.b_copy_code,code,pre{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;border-radius:4px}.o_nowrap,.b_copy_code,p.b_copy_code,div.b_copy_code,code{white-space:nowrap}.o_titled_wrapper .o_content{margin-top:20px}.o_video,.b_video{display:inline-block;max-width:100%;height:auto}.o_image,img,.b_image{display:inline-block;max-width:100%;height:auto}.o_with_hyphens{-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}h1{color:#337ab7}h2{color:#337ab7}h3{color:#337ab7}h4,.o_cal .fc-header-title h2{color:#337ab7}h5{color:#337ab7}h5{color:#337ab7}fieldset legend{color:#333}.b_border_box,p.b_border_box,div.b_border_box{border:1px solid #777;padding:1em;border-top-right-radius:3px;border-top-left-radius:3px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}table td{vertical-align:top}table.b_grid{width:99.5%;background:transparent;border-collapse:separate}table.b_grid td,table.b_grid th{padding:1px 5px;border:1px solid #777}table.b_grid th{background:#eee}table.b_border{width:99.5%;background:transparent;border-collapse:collapse}table.b_border td,table.b_border th{padding:1px 5px;border:1px solid #777}table.b_border th{background:#eee}table.b_borderless{width:99.5%;background:transparent;border-collapse:separate}table.b_borderless td,table.b_borderless th{padding:1px 5px;border:0}table.b_full{width:99.5%}table.b_middle{background:transparent}table.b_middle td{vertical-align:middle}.b_align_normal{text-align:left}.b_align_center{text-align:center}.b_align_inverse{text-align:right}.b_align_justified{text-align:justify}a.b_link_extern{color:#337ab7}a.b_link_extern:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding-right:0.5em;content:"ï‚Ž"}a.b_link_mailto{color:#337ab7}a.b_link_mailto:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding-right:0.5em;content:""}a.b_link_forward{color:#337ab7}a.b_link_forward:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding-right:0.5em;content:"ï¤"}img.b_float_left{float:left;margin:0 2em 2em 0}img.b_float_left_clear{clear:both;margin:0 2em 2em 0;display:block}img.b_float_right{float:right;margin:0 0 2em 2em}img.b_float_right_clear{clear:both;display:block;margin:0 0 2em auto}img.b_centered{clear:both;display:block;margin:0 auto 2em auto}img.b_circle{border-radius:50%}img.b_with_border{border:1px solid #ddd;padding:3px;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px}span.olatFlashMovieViewer{max-width:100%;height:auto !important}.mejs-container,.mejs-mediaelement video,.mejs-layers div{max-width:100%}.b_clear_float,p.b_clear_float,div.b_clear_float{clear:both}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:#337ab7}#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}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>#o_navbar_tools_permanent>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 .dropdown-menu a,.o_navbar #o_navbar_tools_permanent #o_navbar_my_menu .dropdown-menu a{padding-left:15px}.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:#9d9d9d}.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;vertical-align:baseline}.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:#9d9d9d;font-size:14px;border:none}.o_navbar.o_navbar-offcanvas .o_navbar-right .dropdown-menu .divider{background:none}.o_navbar.o_navbar-offcanvas .o_navbar-nav a{color:#9d9d9d;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:#9d9d9d}.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_impress 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_body_popup #o_topnav_printview{display:inline-block}.o_body_popup #o_topnav_close{float:right}.o_body_popup #o_topnav_close span{display:block}.o_body_popup #o_navbar_tools_permanent li>a{background-color:transparent}.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:#337ab7;background-color:transparent}.o_toolbar .o_tools_container .dropdown-menu a{display:block}.o_toolbar .o_tools_container .dropdown-menu a.active{color:#337ab7;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;margin-right: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{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{position:relative}.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{padding:0 10px 0 5px}.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:20px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span{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_center{float:left}.o_toolbar .o_tool,.o_toolbar .o_text,.o_toolbar .o_tool_dropdown{margin:0 0;position:static}.o_toolbar .o_tool_dropdown .dropdown-menu{left:0px;right:0px}.o_toolbar .o_tool_dropdown .dropdown-menu a,.o_toolbar .o_tool_dropdown .dropdown-menu i{line-height:30px}.o_toolbar .o_tool_dropdown .dropdown-menu .o_navbar_tab_close{line-height:inherit}}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}#o_offcanvas_right{position:absolute;top:0;right:-250px;width:250px;padding:15px 15px;background-color:#222;color:#9d9d9d;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,.b_info,p.b_info,div.b_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,.b_info h2,.o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback h2,.b_info h3,.o_form .o_info h3,.o_togglebox_wrapper div.o_togglebox_content h3,div.o_qti_item_itemfeedback h3,.b_info h4,.o_form .o_info h4,.o_togglebox_wrapper div.o_togglebox_content h4,div.o_qti_item_itemfeedback h4,.b_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_info h2,.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,.b_info h5,.o_form .o_info h5,.o_togglebox_wrapper div.o_togglebox_content h5,div.o_qti_item_itemfeedback h5{color:#777}.o_note,.b_note,p.b_note,div.b_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,.b_note h2,.o_form .o_desc h2,.o_course_run .o_statusinfo h2,.o_course_stats .o_desc h2,.b_note h3,.o_form .o_desc h3,.o_course_run .o_statusinfo h3,.o_course_stats .o_desc h3,.b_note h4,.o_form .o_desc h4,.o_course_run .o_statusinfo h4,.o_course_stats .o_desc h4,.b_note .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_note h2,.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,.b_note h5,.o_form .o_desc h5,.o_course_run .o_statusinfo h5,.o_course_stats .o_desc h5{color:#31708f}.o_important,.b_important,p.b_important,div.b_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,.b_important h2,.o_bc_empty h2,.o_course_run .o_no_scoreinfo h2,.b_important h3,.o_bc_empty h3,.o_course_run .o_no_scoreinfo h3,.b_important h4,.o_bc_empty h4,.o_course_run .o_no_scoreinfo h4,.b_important .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_important h2,.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,.b_important h5,.o_bc_empty h5,.o_course_run .o_no_scoreinfo h5{color:#F4D000}.o_success,.b_success,p.b_success,div.b_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,.b_success h2,.b_success h3,.b_success h4,.b_success .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_success h2,.b_success h5{color:#3c763d}.o_warning,.b_warning,p.b_warning,div.b_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,.b_warning h2,.o_form .o_warning h2,.b_warning h3,.o_form .o_warning h3,.b_warning h4,.o_form .o_warning h4,.b_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_warning h2,.o_form .o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_warning h2,.b_warning h5,.o_form .o_warning h5{color:#8a6d3b}.o_error,.b_error,p.b_error,div.b_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,.b_error h2,.b_error h3,.b_error h4,.b_error .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_error h2,.b_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}#o_msg_sticky.o_msg_sticky_fullscreen,#o_msg_sticky_preview.o_msg_sticky_fullscreen{margin-top:0}@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:#337ab7;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}.o_tree .o_tree_link{background-color:none}.o_tree .o_tree_link:hover,.o_tree .o_tree_link:focus{background-color:#f8f8f8}.o_tree .o_tree_link:first-child{background-color:transparent}.o_tree .o_tree_link:last-child:hover,.o_tree .o_tree_link:last-child:focus{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 .active.o_tree_link{background-color:none;font-weight:bold}.o_tree ul li .active.o_tree_link a{color:#337ab7}.o_tree ul li .active.o_tree_link:hover,.o_tree ul li .active.o_tree_link:focus{background-color:#eee}.o_tree ul li .active.o_tree_link:hover a,.o_tree ul li .active.o_tree_link:focus a{color:#23527c}.o_tree ul li .active_parent.o_tree_link{font-weight:bold}.o_tree ul li .active_parent.o_tree_link a{color:#777}.o_tree ul li .active_parent.o_tree_link a:hover,.o_tree ul li .active_parent.o_tree_link 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:#f0ad4e;padding:5px 10px 5px 10px;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_dnd_item.o_dnd_over{background-color:#ffff60}.o_tree .o_dnd_sibling{height:7px;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_tree.o_tree_insert_tool span.o_tree_link a{display:block}.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{margin-right:0.25em}.o_form .o_form_chelp{padding-left:0.25em;margin-right:-1.25em}.o_form .o_form_example{font-size:90%}.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 hr.o_spacer.form,.o_form hr.o_spacer_noline.form{margin-top:0px;margin-bottom:0px}.o_form .form-group.o_omit_margin{margin-bottom:0}.o_form .o_date{position:relative;padding-right:34px}.o_form .o_date.form-inline .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.o_navbar-form .o_date_ms.form-group{margin-left:25px}.o_form .has-feedback .o_date.form-inline .form-control.o_date_ms,.o_form .has-feedback .o_date.o_navbar-form .form-control.o_date_ms{padding-right:0}.o_form .o_form_element.form-inline .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_centered_form{text-align:center}.o_centered_form fieldset.o_form{display:inline-block;text-align:left}.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.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.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].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.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:#337ab7;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_table_rows_infos{float:left;padding-left:0;padding-right:20px;margin:20px 0}.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}@media (max-width: 767px){.o_table_wrapper .o_table_rows_infos{clear:both}}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_messages{float:left}#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,#o_navbar_imclient #o_im_message{padding-left:15px}#o_navbar_imclient #o_im_summary .badge{color:#fff;background-color:#777}#o_navbar_imclient #o_im_status li>a>span{display:inline}#o_navbar_imclient #o_im_status div.o_chelp_wrapper{right:0.5em}#o_navbar_imclient #o_im_message a:hover,#o_navbar_imclient #o_im_message a:focus{text-decoration:none}#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:#337ab7}.o_im_buddieslist ul a:hover{color:#23527c}.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:#337ab7}.o_rating .o_rating_items .o_legend{margin-left:1em;font-size:12px;line-height:1em}.o_rating .o_rating_explanation{font-size:12px;color:#777}@media (max-width: 991px){.o_rating .o_rating_title,.o_rating .o_rating_explanation{display:none}}.o_comments .o_comment_wrapper .o_avatar{float:left;margin:0 1em 0 0}.o_comments .o_comment_wrapper .o_reply,.o_comments .o_comment_wrapper .o_delete{float:right}.o_comments .o_comment_wrapper .o_comment_wrapper{margin-left:16px}.o_ratings_and_comments .o_rating_wrapper{vertical-align:middle;display:inline-block}.o_ratings_and_comments a.o_comments{margin-left:10px;position:relative;top:0.1em}.d3chart .bar{shape-rendering:crispEdges}.d3chart .bar_default_light{fill:#64a0d3}.d3chart .bar_default{fill:#337ab7}.d3chart .bar_default_dark{fill:#23527c}.d3chart .axis{font:12px sans-serif}.d3chart .axis path,.d3chart .axis line{fill:none;stroke:#000;shape-rendering:crispEdges}.o_forum_peekview .o_quote_wrapper,.o_forum_peekview .b_quote_wrapper{display:none}.o_forum_thread_sticky{font-weight:bold}.o_forum_switch{font-size:12px}.o_forum_toolbar{margin-bottom:6px;float:left}.o_forum_fulltextsearch{float:right}@media (max-width: 767px){.o_forum_fulltextsearch{float:left}}.o_forum .o_mark,.o_forum .o_ep_collect{float:right;position:relative;width:2em;margin-left:12px}.o_forum .o_portrait{float:left;margin-right:16px}.o_forum .o_portrait_avatar{width:70px;height:70px}.o_forum .o_newindicator{font-size:10px;color:#5cb85c;text-transform:uppercase;padding-left:1em;vertical-align:text-top;white-space:nowrap}.o_forum .o_author,.o_forum .o_date{display:inline-block;color:#777}.o_forum .o_date{font-size:12px}.o_forum .o_modified{color:#8a6d3b;font-size:12px;font-style:italic}.o_forum .o_forum_message{margin-bottom:20px;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1)}.o_forum .o_forum_message_new{-webkit-box-shadow:0 1px 10px rgba(92,184,92,0.3);box-shadow:0 1px 10px rgba(92,184,92,0.3)}.o_forum .o_forum_message_highlight{-webkit-box-shadow:0 1px 10px rgba(240,173,78,0.5);box-shadow:0 1px 10px rgba(240,173,78,0.5)}.o_forum .o_forum_message_header{padding:10px 15px;border-bottom:1px solid #ddd;background-color:#f5f5f5;border-top-right-radius:3px;border-top-left-radius:3px}.o_forum .o_forum_message_title{margin-top:0}.o_forum .o_forum_message_body{padding:10px 15px}.o_forum .o_forum_message_attachments{border-top:1px solid #ddd;padding:10px 15px;font-size:12px;background-color:#f7f7f9}.o_forum .o_attachment{position:relative;max-width:250px;vertical-align:top;margin:6px 12px 10px 0}.o_forum .o_attachment img{margin-top:6px}.o_forum .o_filename{max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_forum .o_icon_enlarge{position:absolute;left:1em;bottom:1em;text-shadow:1px 1px 2px #fff, -1px 1px 2px #fff, 1px -1px 2px #fff, -1px -1px 2px #fff}@media (min-width: 768px) and (max-width: 991px){.o_forum .o_attachments{font-size:10px}.o_forum .o_attachment{max-width:200px}.o_forum .o_attachment img{max-width:150px}.o_forum .o_filename{max-width:200px}}@media (max-width: 767px){.o_forum .o_attachments{font-size:9px}.o_forum .o_attachment{max-width:150px}.o_forum .o_attachment img{max-width:100px}.o_forum .o_filename{max-width:150px}}.o_quote_wrapper,.b_quote_wrapper{position:relative;margin:10px 0}.o_quote_author,.b_quote_author{color:#777;font-size:12px}.o_quote_author:before,.b_quote_author:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"ï„Ž";font-size:21px;padding-right:0.5em}blockquote.o_quote,blockquote.b_quote{color:#555;font-size:12px;margin-top:6px;padding:0 12px}a.o_chelp{display:inline-block;padding:1px 3px;text-align:center;vertical-align:middle;white-space:nowrap;font-size:10px;font-weight:normal;line-height:15px;color:#fff;background-color:#337ab7;border:1px solid #2e6da4;border-radius:2px;cursor:help;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}a.o_chelp:active,a.o_chelp:hover,a.o_chelp:focus{text-decoration:none;color:#fff;background-color:#2a6598;border-color:#204d74}a.o_chelp i{font-size:10px !important}.o_chelp_wrapper{position:relative;float:right;display:inline-block;line-height:1em;margin-bottom:10px;margin-left:10px}.o_iframedisplay iframe{width:100%}.o_singlepage .o_edit{position:absolute;top:10px;right:37px}.o_content_popup{position:absolute;top:10px;right:12px}.o_module_cp_wrapper .o_tools{position:absolute;top:10px;right:12px;text-align:right;vertical-align:middle}.o_module_cp_wrapper .o_tools .o_search_wrapper{display:inline-block;position:relative;top:-2px}ul.o_dropdown{margin:-5px -14px}ul.o_dropdown .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}ul.o_dropdown>li>a{display:block;padding:5px 14px;clear:both;font-weight:normal;line-height:1.42857;color:#333;background:#fff;white-space:nowrap}ul.o_dropdown>li>a:hover,ul.o_dropdown>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.badge.o_scorm_completed{background-color:#3c763d}.badge.o_scorm_failed{background-color:#a94442}.badge.o_scorm_incomplete{background-color:#8a6d3b}.badge.o_scorm_not_attempted{background:none}.o_bc_meta h5,.o_bc_meta .o_author,.o_bc_meta .o_comment,.tooltip h5,.tooltip .o_author,.tooltip .o_comment{color:#fff;margin:5px 0}.o_bc_meta .o_thumbnail,.tooltip .o_thumbnail{width:200px;height:200px;background-color:#fff;margin:0 -5px}.o_htmleditor .o_metadata{border:1px solid #999;border-top-left-radius:3px;border-top-right-radius:3px;border-bottom:0;background:#eee;position:relative;top:1px;padding:5px}.o_htmleditor .o_metadata #o_filename{float:left}.o_htmleditor .o_metadata .o_lastmodified{float:right;color:#777;line-height:1.42857}.o_htmleditor #o_save{margin-top:10px;text-align:center}.o_htmleditor #o_save input{margin-right:1em}.o_htmleditor #o_save input:last-child{margin-right:0}.o_notifications_news_wrapper .o_notifications_news_subscription{margin:10px 0}.o_notifications_news_wrapper .o_notifications_news_subscription h4 i,.o_notifications_news_wrapper .o_notifications_news_subscription .o_cal .fc-header-title h2 i,.o_cal .fc-header-title .o_notifications_news_wrapper .o_notifications_news_subscription h2 i{display:none}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_context{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content{margin-left:1.5em;position:relative}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_icon{position:absolute;left:-1.5em;line-height:1.5em;top:0}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_date{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_url{margin-left:1.5em}.o_noti{margin:6px 0 6px 12px;float:right;color:#777}.o_noti .o_label{color:#777;cursor:help}@media (max-width: 767px){.o_noti .o_label span{display:none}}.panel-body .o_noti{margin:0}.o_portrait{display:inline-block}.o_portrait img{border-radius:50%;border:none;background-color:#eee;background-position:50% 50%;background-repeat:no-repeat;background-size:cover}.o_portrait_name{margin-top:6px}.o_block_inline .o_portait,.o_block_inline .o_portrait_name,.o_block_inline .o_portrait_image,.o_block_inline_right .o_portait,.o_block_inline_right .o_portrait_name,.o_block_inline_right .o_portrait_image,.o_block_inline_left .o_portait,.o_block_inline_left .o_portrait_name,.o_block_inline_left .o_portrait_image,.o_block_inline_both .o_portait,.o_block_inline_both .o_portrait_name,.o_block_inline_both .o_portrait_image{display:inline-block}.o_portrait_avatar,.o_portrait_dummy,.o_portrait_dummy_female_big,.o_portrait_dummy_male_big,.o_portrait_anonymous{width:100px;height:100px}.o_portrait_dummy{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_dummy_female_big{background-image:url("../light/images/portrait/dummy_female_big.png")}.o_portrait_dummy_male_big{background-image:url("../light/images/portrait/dummy_male_big.png")}.o_portrait_anonymous{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_avatar_small,.o_portrait_dummy_small,.o_portrait_dummy_female_small,.o_portrait_dummy_male_small,.o_portrait_anonymous_small{width:30px;height:30px}.o_portrait_dummy_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_portrait_dummy_female_small{background-image:url("../light/images/portrait/dummy_female_small.png")}.o_portrait_dummy_male_small{background-image:url("../light/images/portrait/dummy_male_small.png")}.o_portrait_anonymous_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_datecomp{position:relative;width:40px;height:52px;border:1px solid #555;margin-right:12px;text-align:center;vertical-align:middle}.o_datecomp div.o_year{position:absolute;left:0;width:100%;top:-20px;height:20px;line-height:20px;font-size:10px}.o_datecomp div.o_month{height:20px;line-height:20px;font-size:12px;background-color:#337ab7;color:#fff}.o_datecomp div.o_day{height:30px;line-height:30px;font-size:18px;border-top:1px solid #555;background-color:#fff;color:#333}.o_block_with_datecomp .o_head{position:relative;padding-left:52px}.o_block_with_datecomp .o_datecomp{position:absolute;top:0.2em;left:0}.o_block_with_datecomp .o_title{margin-top:0}.o_block_with_datecomp .o_meta{color:#777}.o_block_with_datecomp .o_content{border-left:5px solid #eee;padding:0 20px}.o_block_with_datecomp .o_block_footer{padding-left:25px}ul.o_certificates li{padding:5px 0}ul.o_certificates li a.o_sel_certificate_delete{padding-left:2em}.o_cal_toptoolbar{margin-bottom:6px}.o_cal_toptoolbar .o_cal_toptoolbar_help{float:left;margin-right:12px}.o_feed .o_date,.o_feed .o_author{color:#777}.o_feed .o_subscription a{margin-right:1.5em}.o_feed .o_subscription .form-group{margin-bottom:5px}.o_feed .o_subscription .form-control{border:0;background:none;padding:0;height:auto;-webkit-box-shadow:none;box-shadow:none}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper{float:left}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_title,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_explanation,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_legend{display:none}.o_feed .o_blog_posts .o_ratings_and_comments a.o_comments span{display:none}.o_feed .o_content:before,.o_feed .o_content:after{content:" ";display:table}.o_feed .o_content:after{clear:both}.o_glossary .o_register{text-align:center}.o_glossary .o_meta{font-size:90%;color:#777;font-style:italic}.o_glossary dl dt:first-letter{font-size:21px}.o_glossary dl dt small{color:#777}.o_tm_glossary{border-bottom:1px dotted #666699 !important}.o_tm_yellow{background-color:#FFFF66}.o_tm_blue{background-color:#33FFFF}.o_tm_red{background-color:#FF3333}.o_tm_green{background-color:#99FF00}.vitero_iframe{width:100%;height:100%;border:none;min-height:60em}.o_reminder_rule{padding:5px 0}.o_segments.btn-group a span{overflow:hidden;display:block;text-overflow:ellipsis}.o_segments_content{margin-top:20px}.o_tabbed_pane .o_tabbed_pane_content{padding:20px 0 6px 0}.o_togglebox_wrapper .o_opener{position:relative;left:-0.5em}.o_togglebox_wrapper div.o_togglebox_content{position:relative;margin:0}.o_togglebox_wrapper div.o_togglebox_content .o_hide{position:absolute;bottom:0.5em;right:1em}.o_toolboxes ul{margin:0 0 1.5em 0;padding:0 0 0 1.5em}.o_qrcode{width:256px;height:256px}#o_ajax_busy{position:absolute;left:50%;top:20em;margin-left:-2.5em;height:5em;width:5em;color:#fff;z-index:1201;display:none}#o_body.o_ajax_busy{cursor:busy}.o_exception .o_visual{position:relative;background-image:url("../light/images/lion-500x333.jpg");filter:grayscale(50%);-webkit-filter:grayscale(50%);-moz-filter:grayscale(50%);-ms-filter:grayscale(50%);-o-filter:grayscale(50%);width:500px;height:333px;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;margin:0 0 10px 16px}@media (min-width: 768px) and (max-width: 991px){.o_exception .o_visual{width:375px;height:249px}}@media (min-width: 500px) and (max-width: 767px){.o_exception .o_visual{width:250px;height:166px}}@media (max-width: 500px){.o_exception .o_visual{background-size:cover}}.o_exception .jumbotron h1,.o_exception .o_repo_details .o_lead h1,.o_repo_details .o_exception .o_lead h1{color:#d9534f}.tt-input{width:400px}.tt-dropdown-menu{width:400px;margin-top:6px;padding:0 0 0;color:#555;background-color:#fff;border:1px solid #66afe9;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;-webkit-box-shadow:0 0 8px rgba(102,175,233,0.6);box-shadow:0 0 8px rgba(102,175,233,0.6)}.tt-suggestion{padding:6px 12px;font-size:14px;line-height:1.42857}.tt-suggestion.tt-cursor{color:#fff;background-color:#337ab7}.tt-suggestion p{margin:0}.o_search_link_extended,.o_search_link_simple{margin-top:12px;display:inline-block}.o_search_results_stats{color:#777;padding-left:1.5em}.o_search_highlight{margin-left:12px;font-size:12px}.o_search_result_title h4,.o_search_result_title .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_search_result_title h2{display:inline-block;margin-right:12px;margin-bottom:6px}.o_search_result_highlight{font-weight:bold}.o_search_result_context{color:#3c763d}.o_search_result_excerpt{color:#555}.o_search_result_details .o_togglebox_wrapper.o_block{margin-top:0;margin-bottom:0}.o_search_result_details .o_togglebox_wrapper .o_togglebox_content{color:#777;font-size:12px;background:#fff;padding:6px 12px}@media (max-width: 767px){.o_search_result_details{display:none}}.wizard{border:1px solid #d4d4d4;border-radius:2px;background-color:#f9f9f9;position:relative;overflow:hidden;margin-bottom:15px}.wizard ul{list-style:none outside none;padding:0;margin:0;width:4000px}.wizard ul li{float:left;margin:0;padding:0 20px 0 30px;height:46px;line-height:46px;position:relative;background:#ededed;color:#333;font-size:16px;cursor:default}.wizard ul li .chevron{border:24px solid transparent;border-left:14px solid #d4d4d4;border-right:0;display:block;position:absolute;right:-14px;top:0;z-index:1}.wizard ul li .chevron:before{border:24px solid transparent;border-left:14px solid #ededed;border-right:0;content:"";display:block;position:absolute;right:1px;top:-24px}.wizard ul li.active{background:#f1f6fc;color:#333}.wizard ul li.active .chevron:before{border-left:14px solid #f1f6fc}.wizard ul li .badge{margin-right:8px}.wizard ul li:first-child{border-radius:4px 0 0 4px;padding-left:20px}.o_process{position:relative;padding-left:25px}.o_process .o_step{position:relative;height:auto;padding-top:10px;padding-left:30px;padding-bottom:10px}.o_process .o_bar{position:absolute;top:10px;left:8px;height:100%;border-left:4px solid #777}.o_process .o_bar:after{position:absolute;top:0;left:-10px;height:16px;width:16px;border:4px solid #777;border-radius:16px;background:#fff;content:" "}.o_process .o_title{margin-top:-1px;color:#777 !important}.o_process .o_step.o_active .o_bar,.o_process .o_step.o_active .o_bar:after{border-color:#337ab7}.o_process .o_step.o_active .o_title{color:#337ab7 !important}.o_process .o_step.o_active .o_title:before{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;transform:translate(0, 0);content:""}.o_process .o_step.o_done .o_bar,.o_process .o_step.o_done .o_bar:after{border-color:#5094ce}.o_process .o_step.o_done .o_title{color:#5094ce !important}.o_process .o_step.o_done .o_title:before{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;transform:translate(0, 0);content:""}.o_process .o_meta{color:#777;font-size:12px;margin-top:-0.5em}.o_cal_orange{background:#ffc266;border-color:#ff9900;color:#5D5D5D}.o_cal_orange .o_cal_wv_event_header{background:#ff9900}.o_cal_orange a{color:#5D5D5D !important}.o_cal_green{background:#66c266;border-color:#009900;color:#FFF}.o_cal_green .o_cal_wv_event_header{background:#009900}.o_cal_green a{color:#FFF !important}.o_cal_blue{background:#4d6e9f;border-color:#2e5894;color:#FFF}.o_cal_blue .o_cal_wv_event_header{background:#2e5894}.o_cal_blue a{color:#FFF !important}.o_cal_yellow{background:#ffe066;border-color:#ffcc00;color:#5D5D5D}.o_cal_yellow .o_cal_wv_event_header{background:#ffcc00}.o_cal_yellow a{color:#5D5D5D !important}.o_cal_red{background:#c26666;border-color:#990000;color:#FFF}.o_cal_red .o_cal_wv_event_header{background:#990000}.o_cal_red a{color:#FFF !important}.o_cal_rebeccapurple{background:#663399;border-color:#663399;color:#FFF}.o_cal_rebeccapurple .o_cal_wv_event_header{background:#663399}.o_cal_rebeccapurple a{color:#FFF !important}.o_cal_grey{background:#DDDAAA;border-color:#5D5D5D;color:#FFF}.o_cal_grey .o_cal_wv_event_header{background:#5D5D5D}.o_cal_grey a{color:#FFF !important}.o_cal_config_enabled,.o_cal_config_disabled{position:relative;float:left;display:inline}.o_cal_config_calendar{margin:0 5px;padding:1px 6px 1px 4px;position:relative;width:200px;overflow:hidden;float:left;display:inline}.o_cal_colorchooser_selected:before{content:""}#o_cal_colorchooser div{border:1px solid #337ab7;margin:5px;display:inline-block}#o_cal_colorchooser div:hover{border:1px solid #333}#o_cal_colorchooser a{width:20px;height:20px;display:inline-block}.fc-button{color:#333;background-color:#fff;border-color:#ccc}.fc-button:hover,.fc-button:focus,.fc-button.focus,.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{background-image:none}.fc-button.disabled,.fc-button.disabled:hover,.fc-button.disabled:focus,.fc-button.disabled.focus,.fc-button.disabled:active,.fc-button.disabled.active,.fc-button[disabled],.fc-button[disabled]:hover,.fc-button[disabled]:focus,.fc-button[disabled].focus,.fc-button[disabled]:active,.fc-button[disabled].active,fieldset[disabled] .fc-button,fieldset[disabled] .fc-button:hover,fieldset[disabled] .fc-button:focus,fieldset[disabled] .fc-button.focus,fieldset[disabled] .fc-button:active,fieldset[disabled] .fc-button.active{background-color:#fff;border-color:#ccc}.fc-button .badge{color:#fff;background-color:#333}.fc-button.fc-state-default{text-shadow:none}.fc-button.fc-state-active{color:#fff;background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active:hover,.fc-button.fc-state-active:focus,.fc-button.fc-state-active.focus,.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{background-image:none}.fc-button.fc-state-active.disabled,.fc-button.fc-state-active.disabled:hover,.fc-button.fc-state-active.disabled:focus,.fc-button.fc-state-active.disabled.focus,.fc-button.fc-state-active.disabled:active,.fc-button.fc-state-active.disabled.active,.fc-button.fc-state-active[disabled],.fc-button.fc-state-active[disabled]:hover,.fc-button.fc-state-active[disabled]:focus,.fc-button.fc-state-active[disabled].focus,.fc-button.fc-state-active[disabled]:active,.fc-button.fc-state-active[disabled].active,fieldset[disabled] .fc-button.fc-state-active,fieldset[disabled] .fc-button.fc-state-active:hover,fieldset[disabled] .fc-button.fc-state-active:focus,fieldset[disabled] .fc-button.fc-state-active.focus,fieldset[disabled] .fc-button.fc-state-active:active,fieldset[disabled] .fc-button.fc-state-active.active{background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active .badge{color:#337ab7;background-color:#fff}.o_visual{position:absolute;top:0;left:0;overflow:hidden;height:120px;width:180px;vertical-align:middle}@media (min-width: 768px) and (max-width: 991px){.o_visual{height:80px;width:120px}}@media (max-width: 767px){.o_visual{height:50px;width:75px}}.o_visual img{width:100%;height:auto}.o_visual .o_visual_not_available{width:100%;height:100%;background-image:url("../light/images/no_preview.png");background-repeat:no-repeat;background-position:50% 50%;background-size:contain}.o_coursetable.o_rendertype_custom .o_table_row{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_coursetable.o_rendertype_custom .o_table_row .o_visual{border-right:1px solid #337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_access{position:absolute;top:0;right:0;height:120px;width:180px;overflow:hidden;border-left:1px solid #337ab7;padding-top:0.25em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{padding:0 1em;height:20px;line-height:20px;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{position:relative;left:2px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score .o_label{color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social{position:absolute;width:100%;bottom:32px;height:20px;padding-left:1em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_title,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating o_rating_legend,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_explanation{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings{padding:0 0 0 1em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label{margin-bottom:1em;color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_methods{color:#5bc0de}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{position:absolute;display:block;bottom:0;width:90px;height:30px;line-height:30px;text-align:center}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{right:0}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start{color:#fff;background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start: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_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active{background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start .badge{color:#337ab7;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book: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_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active{background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book .badge{color:#f0ad4e;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:90px;color:#fff;background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active{background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details .badge{color:#5cb85c;background-color:#fff}@media (min-width: 768px) and (max-width: 991px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{height:80px;width:120px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_comments,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_label{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{width:60px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:60px}}@media (max-width: 767px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{display:none}}.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:120px;margin:0 180px 0 180px;position:relative;padding:1em 0.5em 0.25em 1em;overflow:hidden}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title{margin:0;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a{display:block;color:#337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover{color:#286090}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_author{margin-top:0.5em;line-height:1em;font-size:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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:#337ab7}.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 #337ab7;border-bottom:1px solid #337ab7}.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:#337ab7}.o_catalog .o_level .o_meta .o_title a:hover{color:#286090}.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_list .o_sublevel{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_catalog .o_sublevels_list .o_sublevel .o_visual{height:75px;width:75px}.o_catalog .o_sublevels_list .o_sublevel .o_title{margin:0}.o_catalog .o_sublevels_list .o_sublevel .o_meta{border-left:1px solid #337ab7;min-height:75px;height:75px;margin:0 0 0 75px;padding:0 0 0 1em;overflow:hidden}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_title{line-height:75px}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_desc{display:none}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a{font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a>i,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a>i,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a>i{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 #337ab7;position:relative;height:180px}.o_catalog .o_sublevels .o_sublevel .o_meta{position:absolute;left:0;bottom:0;width:100%;border:1px solid #337ab7;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:#337ab7;font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover{color:#286090}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a>i{display:none}@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%}}@media (min-width: 768px){.o_catalog .o_sublevels_list,.o_catalog .o_sublevels_compact{-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2;columns:2}}.o_repo_details{position:relative}.o_repo_details .o_lead{margin-bottom:10px}.o_repo_details .o_lead .o_author{margin-top:0.5em;margin-bottom:1em;font-size:120%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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-left:0;float:none;text-align:center}}@media (max-width: 613px){.o_repo_details .o_subcolumn{width:100%}}.o_meta .o_closed{padding:2px 5px;margin:5px 0}.o_overview .o_closed{padding:12px 15px;margin:15px 0}.o_ac_configuration span.o_ac_infos{font-weight:normal;color:grey}.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_tree.o_course_menu div.o_tree_l0>a:first-child{background-color: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_ll_container h5{margin-bottom:5px}.o_ll_container h5 a.o_desc{color:#337ab7}.o_ll_container h5 a.o_desc small{display:none}.o_ll_container h5 a.o_desc:hover{color:#286090;text-decoration:none}.o_ll_container h5 a.o_desc:hover small{color:#5e5e5e;display:inline}.o_ll_container div.o_comment{color:#777}.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,div.o_qti_menu_section_clickable,div.o_qti_menu_section_active{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:"ï„"}.o_qti_menu_item_attempts_marked:after{content:"";color:#337ab7}.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:""}.o_ep_icon_collection:before{content:""}.o_ep_icon_page:before{content:""}.o_ep_icon_struct:before{content:""}.o_ep_icon_liveblog:before{content:"ï‚¡"}.o_artefact_closed:before{content:""}.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.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%;border-top:50px solid transparent;border-bottom:70px solid transparent;background:url("../light/images/learn-bg.jpg");background-size:cover;background-position:center center;background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=1 )}body.o_dmz #o_bg:after{content:" ";position:absolute;top:0;left:0;width:100%;height:100%;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%);background-size:cover;background-position:center center;background-repeat:no-repeat}body.o_dmz #o_toplink{display:none}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:#337ab7}.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:#337ab7}.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_providers .o_icon_provider_olat{font-size:1em}.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.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.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].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.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 .btn-default.o_sel_auth_facebook{color:#fff;background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{color:#fff;background-color:#37538d;border-color:#2d4374}.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled],.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.active{background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook .badge{color:#4568b2;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_twitter{color:#fff;background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{color:#fff;background-color:#00b4f8;border-color:#009ad4}.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled],.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.active{background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter .badge{color:#2cc5ff;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_google{color:#fff;background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google:hover,.o_login .o_login_social .btn-default.o_sel_auth_google:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.focus,.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{color:#fff;background-color:#d83825;border-color:#ba3120}.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_google.disabled,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled],.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.active{background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google .badge{color:#e15f4f;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_linkedin{color:#fff;background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{color:#fff;background-color:#015e8a;border-color:#014667}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled],.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.active{background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin .badge{color:#0181bd;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_adfs{color:#fff;background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{color:#fff;background-color:#000;border-color:#000}.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled],.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.active{background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs .badge{color:#1a1a1a;background-color:#fff}@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:""}.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 #337ab7 !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:#337ab7;font-weight:500;font-family:inherit;line-height:1.1}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close:before{content:"ï€" !important}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;font-size:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close span{display:none}.ui-dialog .ui-widget-header .ui-button.ui-corner-all{border:none !important;background:#fff !important;float:right}.ui-dialog .ui-widget-content{border-color:#fff;padding:5px;overflow:auto;background:white !important}.ui-dialog .ui-dialog-titlebar{padding:4px 7px 4px 7px;background-color:#eee !important}.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:"ï¡";font-weight:normal;color:black}.ui-datepicker .ui-widget-header .ui-icon.ui-icon-circle-triangle-w:before{content:"ï ";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 #2e6da4;background:#337ab7;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}.progress{-webkit-print-color-adjust:exact;background-color:rgba(0,0,0,0.1) !important;border:1px solid rgba(0,0,0,0.5)}.progress-bar{-webkit-print-color-adjust:exact;background-color:#000 !important;border:10px solid #000}}body.o_browser_ie7 #o_offcanvas_right,body.o_browser_ie8 #o_offcanvas_right{right:0px} -/*# sourceMappingURL=theme.css.map */ +**//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail,.o_form .o_filepreview img,.o_feed .o_media{padding:4px;line-height:1.42857;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,.o_cal .fc-header-title h2,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,.o_cal .fc-header-title h2 small,h4 .small,.o_cal .fc-header-title h2 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 small,.h6 .small{font-weight:normal;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,h1 .small,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.o_cal .fc-header-title h2,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.o_cal .fc-header-title h2 small,h4 .small,.o_cal .fc-header-title h2 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .small,.h6 small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.o_cal .fc-header-title h2,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width: 768px){.lead{font-size:21px}} +small,.small{font-size:85%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff}.bg-primary{background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857}dt{font-weight:bold}dd{margin-left:0}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}} +abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}} +.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-15px;margin-right:-15px}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-1{width:8.33333%}.col-xs-2{width:16.66667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333%}.col-xs-5{width:41.66667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333%}.col-xs-8{width:66.66667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333%}.col-xs-11{width:91.66667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333%}.col-xs-pull-2{right:16.66667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333%}.col-xs-pull-5{right:41.66667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333%}.col-xs-pull-8{right:66.66667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333%}.col-xs-pull-11{right:91.66667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333%}.col-xs-push-2{left:16.66667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333%}.col-xs-push-5{left:41.66667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333%}.col-xs-push-8{left:66.66667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333%}.col-xs-push-11{left:91.66667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333%}.col-xs-offset-2{margin-left:16.66667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333%}.col-xs-offset-5{margin-left:41.66667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333%}.col-xs-offset-8{margin-left:66.66667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333%}.col-xs-offset-11{margin-left:91.66667%}.col-xs-offset-12{margin-left:100%}@media (min-width: 768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-1{width:8.33333%}.col-sm-2{width:16.66667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333%}.col-sm-5{width:41.66667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333%}.col-sm-8{width:66.66667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333%}.col-sm-11{width:91.66667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333%}.col-sm-pull-2{right:16.66667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333%}.col-sm-pull-5{right:41.66667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333%}.col-sm-pull-8{right:66.66667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333%}.col-sm-pull-11{right:91.66667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333%}.col-sm-push-2{left:16.66667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333%}.col-sm-push-5{left:41.66667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333%}.col-sm-push-8{left:66.66667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333%}.col-sm-push-11{left:91.66667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333%}.col-sm-offset-2{margin-left:16.66667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333%}.col-sm-offset-5{margin-left:41.66667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333%}.col-sm-offset-8{margin-left:66.66667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333%}.col-sm-offset-11{margin-left:91.66667%}.col-sm-offset-12{margin-left:100%}}@media (min-width: 992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-1{width:8.33333%}.col-md-2{width:16.66667%}.col-md-3{width:25%}.col-md-4{width:33.33333%}.col-md-5{width:41.66667%}.col-md-6{width:50%}.col-md-7{width:58.33333%}.col-md-8{width:66.66667%}.col-md-9{width:75%}.col-md-10{width:83.33333%}.col-md-11{width:91.66667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333%}.col-md-pull-2{right:16.66667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333%}.col-md-pull-5{right:41.66667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333%}.col-md-pull-8{right:66.66667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333%}.col-md-pull-11{right:91.66667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333%}.col-md-push-2{left:16.66667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333%}.col-md-push-5{left:41.66667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333%}.col-md-push-8{left:66.66667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333%}.col-md-push-11{left:91.66667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333%}.col-md-offset-2{margin-left:16.66667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333%}.col-md-offset-5{margin-left:41.66667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333%}.col-md-offset-8{margin-left:66.66667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333%}.col-md-offset-11{margin-left:91.66667%}.col-md-offset-12{margin-left:100%}}@media (min-width: 1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-1{width:8.33333%}.col-lg-2{width:16.66667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333%}.col-lg-5{width:41.66667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333%}.col-lg-8{width:66.66667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333%}.col-lg-11{width:91.66667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333%}.col-lg-pull-2{right:16.66667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333%}.col-lg-pull-5{right:41.66667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333%}.col-lg-pull-8{right:66.66667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333%}.col-lg-pull-11{right:91.66667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333%}.col-lg-push-2{left:16.66667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333%}.col-lg-push-5{left:41.66667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333%}.col-lg-push-8{left:66.66667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333%}.col-lg-push-11{left:91.66667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333%}.col-lg-offset-2{margin-left:16.66667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333%}.col-lg-offset-5{margin-left:41.66667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333%}.col-lg-offset-8{margin-left:66.66667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333%}.col-lg-offset-11{margin-left:91.66667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.table>tfoot>tr>td{padding:8px;line-height:1.42857;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}} +fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio: 0){input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{line-height:34px}input[type="date"].input-sm,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,input[type="time"].input-sm,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-sm,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-sm,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn{line-height:30px}input[type="date"].input-lg,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,input[type="time"].input-lg,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-lg,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-lg,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-left:0;padding-right:0}.input-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn,.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn,.form-group-sm .form-control{height:30px;line-height:30px}textarea.input-sm,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,.form-group-sm .form-control,select[multiple].input-sm,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn,.form-group-sm .form-control{height:auto}.input-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn,.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn,.form-group-lg .form-control{height:46px;line-height:46px}textarea.input-lg,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,.form-group-lg .form-control,select[multiple].input-lg,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn,.form-group-lg .form-control{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label ~ .form-control-feedback{top:25px}.has-feedback label.sr-only ~ .form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width: 768px){.form-inline .form-group,.o_navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control,.o_navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static,.o_navbar-form .form-control-static{display:inline-block}.form-inline .input-group,.o_navbar-form .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.o_navbar-form .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.o_navbar-form .input-group .input-group-btn,.form-inline .input-group .form-control,.o_navbar-form .input-group .form-control{width:auto}.form-inline .input-group>.form-control,.o_navbar-form .input-group>.form-control{width:100%}.form-inline .control-label,.o_navbar-form .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.o_navbar-form .radio,.form-inline .checkbox,.o_navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.o_navbar-form .radio label,.form-inline .checkbox label,.o_navbar-form .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.o_navbar-form .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"],.o_navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback,.o_navbar-form .has-feedback .form-control-feedback{top:0}} +.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}} +.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.focus,.btn.active:focus,.btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default.focus,.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled,.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled.active,.btn-default[disabled],.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled].active,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:hover,.btn-primary:focus,.btn-primary.focus,.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled.active,.btn-primary[disabled],.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary.active{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success.focus,.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled,.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled.active,.btn-success[disabled],.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled].active,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info.focus,.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled,.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled.active,.btn-info[disabled],.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled].active,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning.focus,.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled,.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled.active,.btn-warning[disabled],.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger.focus,.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled,.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled.active,.btn-danger[disabled],.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#337ab7;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#337ab7}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar:before,.btn-toolbar:after{content:" ";display:table}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle,.btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret,.btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret,.dropup .btn-group-lg>.btn .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{content:" ";display:table}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav:before,.nav:after{content:" ";display:table}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-justified>li,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}} +.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}} +.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{content:" ";display:table}.navbar:after{clear:both}@media (min-width: 768px){.navbar{border-radius:4px}} +.navbar-header:before,.navbar-header:after{content:" ";display:table}.navbar-header:after{clear:both}@media (min-width: 768px){.navbar-header{float:left}} +.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{content:" ";display:table}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;visibility:visible !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}} +.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}} +.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}} +.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width: 768px){.navbar-static-top{border-radius:0}} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width: 768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}} +.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width: 768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}} +.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width: 768px){.navbar-toggle{display:none}} +.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width: 767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width: 768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}} +.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-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 (min-width: 768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width: 767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 768px){.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}} +.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm,.btn-group-sm>.navbar-btn.btn{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs,.btn-group-xs>.navbar-btn.btn{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}} +@media (min-width: 768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right ~ .navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width: 767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:hover,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#090909}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#090909}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#090909;color:#fff}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:hover,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857;text-decoration:none;color:#337ab7;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager:before,.pager:after{content:" ";display:table}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.jumbotron,.o_repo_details .o_lead{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.o_repo_details .o_lead h1,.jumbotron .h1,.o_repo_details .o_lead .h1{color:inherit}.jumbotron p,.o_repo_details .o_lead p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr,.o_repo_details .o_lead>hr{border-top-color:#d5d5d5}.container .jumbotron,.container .o_repo_details .o_lead,.o_repo_details .container .o_lead,.container-fluid .jumbotron,.container-fluid .o_repo_details .o_lead,.o_repo_details .container-fluid .o_lead{border-radius:6px}.jumbotron .container,.o_repo_details .o_lead .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron,.o_repo_details .o_lead{padding:48px 0}.container .jumbotron,.container .o_repo_details .o_lead,.o_repo_details .container .o_lead,.container-fluid .jumbotron,.container-fluid .o_repo_details .o_lead,.o_repo_details .container-fluid .o_lead{padding-left:60px;padding-right:60px}.jumbotron h1,.o_repo_details .o_lead h1,.jumbotron .h1,.o_repo_details .o_lead .h1{font-size:63px}} +.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#333}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#337ab7}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4,.alert .o_cal .fc-header-title h2,.o_cal .fc-header-title .alert h2{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;color:#555;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#777;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table,.panel-collapse>.table,.panel-collapse>.table-responsive>.table,.panel-collapse>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption,.panel-collapse>.table caption,.panel-collapse>.table-responsive>.table caption,.panel-collapse>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child,.panel-collapse>.table:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel-collapse>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel-collapse>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel-collapse>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child,.panel-collapse>.table:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel-collapse>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel-collapse>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel-collapse>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body,.panel-collapse>.panel-body+.table,.panel-collapse>.panel-body+.table-responsive,.panel-collapse>.table+.panel-body,.panel-collapse>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td,.panel-collapse>.table>tbody:first-child>tr:first-child th,.panel-collapse>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered,.panel-collapse>.table-bordered,.panel-collapse>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel-collapse>.table-bordered>thead>tr>th:first-child,.panel-collapse>.table-bordered>thead>tr>td:first-child,.panel-collapse>.table-bordered>tbody>tr>th:first-child,.panel-collapse>.table-bordered>tbody>tr>td:first-child,.panel-collapse>.table-bordered>tfoot>tr>th:first-child,.panel-collapse>.table-bordered>tfoot>tr>td:first-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel-collapse>.table-bordered>thead>tr>th:last-child,.panel-collapse>.table-bordered>thead>tr>td:last-child,.panel-collapse>.table-bordered>tbody>tr>th:last-child,.panel-collapse>.table-bordered>tbody>tr>td:last-child,.panel-collapse>.table-bordered>tfoot>tr>th:last-child,.panel-collapse>.table-bordered>tfoot>tr>td:last-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel-collapse>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel-collapse>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel-collapse>.table-bordered>thead>tr:first-child>td,.panel-collapse>.table-bordered>thead>tr:first-child>th,.panel-collapse>.table-bordered>tbody>tr:first-child>td,.panel-collapse>.table-bordered>tbody>tr:first-child>th,.panel-collapse>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel-collapse>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel-collapse>.table-bordered>tbody>tr:last-child>td,.panel-collapse>.table-bordered>tbody>tr:last-child>th,.panel-collapse>.table-bordered>tfoot>tr:last-child>td,.panel-collapse>.table-bordered>tfoot>tr:last-child>th,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel-collapse>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel-collapse>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive,.panel-collapse>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{content:" ";display:table}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width: 992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;visibility:visible;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:normal;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:normal;line-height:1.42857;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width: 767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width: 767px){.visible-xs-block{display:block !important}} +@media (max-width: 767px){.visible-xs-inline{display:inline !important}} +@media (max-width: 767px){.visible-xs-inline-block{display:inline-block !important}} +@media (min-width: 768px) and (max-width: 991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width: 768px) and (max-width: 991px){.visible-sm-block{display:block !important}} +@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline{display:inline !important}} +@media (min-width: 768px) and (max-width: 991px){.visible-sm-inline-block{display:inline-block !important}} +@media (min-width: 992px) and (max-width: 1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width: 992px) and (max-width: 1199px){.visible-md-block{display:block !important}} +@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline{display:inline !important}} +@media (min-width: 992px) and (max-width: 1199px){.visible-md-inline-block{display:inline-block !important}} +@media (min-width: 1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width: 1200px){.visible-lg-block{display:block !important}} +@media (min-width: 1200px){.visible-lg-inline{display:inline !important}} +@media (min-width: 1200px){.visible-lg-inline-block{display:inline-block !important}} +@media (max-width: 767px){.hidden-xs{display:none !important}}@media (min-width: 768px) and (max-width: 991px){.hidden-sm{display:none !important}}@media (min-width: 992px) and (max-width: 1199px){.hidden-md{display:none !important}}@media (min-width: 1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}} +.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}} +.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}} +@media print{.hidden-print{display:none !important}}body .modal{position:absolute;overflow:visible}body div.tooltip-inner{max-width:400px}body div.popover{max-width:450px}body .modal-body.alert{border-radius:0}body .progress{margin-bottom:0}.panel-body:nth-child(n+2){border-top:1px solid #ddd}.panel .panel-heading[data-toggle="collapse"]{cursor:pointer}#o_ajax_busy_backdrop{bottom:0;z-index:1020}.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}@-moz-document url-prefix(){fieldset{display:table-cell}}@font-face{font-family:'openolat';src:url("../light/fonts/openolat/openolat.eot?4yacgg");src:url("../light/fonts/openolat/openolat.eot?#iefix4yacgg") format("embedded-opentype"),url("../light/fonts/openolat/openolat.woff?4yacgg") format("woff"),url("../light/fonts/openolat/openolat.ttf?4yacgg") format("truetype"),url("../light/fonts/openolat/openolat.svg?4yacgg#openolat") format("svg");font-weight:normal;font-style:normal}@font-face{font-family:'FontAwesome';src:url("../../font-awesome/fonts/fontawesome-webfont.eot?v=4.3.0");src:url("../../font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.3.0") format("embedded-opentype"),url("../../font-awesome/fonts/fontawesome-webfont.woff2?v=4.3.0") format("woff2"),url("../../font-awesome/fonts/fontawesome-webfont.woff?v=4.3.0") format("woff"),url("../../font-awesome/fonts/fontawesome-webfont.ttf?v=4.3.0") format("truetype"),url("../../font-awesome/fonts/fontawesome-webfont.svg?v=4.3.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;transform:translate(0, 0)}.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}.o_icon-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-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_accepted:before{content:"\f164"}.o_icon_accessibility:before{content:"\f193"}.o_icon_actions:before{content:"\f085"}.o_icon_archive_tool:before{content:"\f019"}.o_icon_assessment_mode:before{content:"\f044"}.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_member:before{content:"\f234"}.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_banned:before{content:"\f0c3";color:#d9534f}.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_calendar_sync:before{content:"\f021"}.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_description:before{content:"\f05a"}.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_graduate:before{content:"\f19d"}.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_info_resource:before{content:"\f05a"}.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_manual:before{content:"\f02d";cursor:help}.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_mobile:before{content:"\f10b"}.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:"\f040"}.o_icon_notification:before{content:"\f09e"}.o_icon_ok:before{content:"\f00c";color:#5cb85c}.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:"\E600";font-family:openolat;font-size:10px}.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_performx:before{content:"\f0d7"}.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_rejected:before{content:"\f165"}.o_icon_reminder:before{content:"\f0a6"}.o_icon_remove:before{content:"\f00d"}.o_icon_replace:before{content:"\f0c5"}.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_select:before{content:"\f00c"}.o_icon_send:before{content:"\f0e0"}.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_show_send:before{content:"\f1d9"}.o_icon_sign_out:before{content:"\f08b"}.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_submit:before{content:"\f00c"}.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_waiting:before{content:"\f017"}.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_gta_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_as_mode_leadtime:before{content:"\f017"}.o_as_mode_assessment:before{content:"\f04b"}.o_as_mode_followup:before{content:"\f05e"}.o_as_mode_closed:before{content:'-'}.o_black_led:before{content:"\f111";color:#337ab7}.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:#337ab7}.o_ac_status_succes_icon:before{content:"\f00c";color:#5cb85c}.o_ac_status_waiting_icon:before{content:"\f017";color:#337ab7}.o_ac_order_status_new_icon:before{content:"\f069";color:#337ab7}.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:"\f06a"}.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_block_inline,.o_block_inline_left,.o_block_inline_both,.o_block_inline_right{display:inline-block}.o_block_inline_left,.o_block_inline_both{margin-left:0.5em}.o_block_inline_right,.o_block_inline_both{margin-right:0.5em}.o_scrollblock,div.b_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,.b_xsmall,p.b_xsmall,div.b_xsmall{font-size:12px}.o_small,.b_small,p.b_small,div.b_small,.o_comments .o_comment_wrapper h5,.o_comments .o_comment_wrapper .o_comment,.o_bc_meta,.tooltip,.o_htmleditor .o_metadata .o_lastmodified,.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,.b_large,p.b_large,div.b_large{font-size:18px}.o_xlarge,.b_xlarge,p.b_xlarge,div.b_xlarge{font-size:18px}.o_disabled,.b_disabled,p.b_disabled,div.b_disabled{color:#777 !important;cursor:default}.o_disabled:hover,.b_disabled:hover{color:#777 !important}.o_dimmed,.b_dimmed,p.b_dimmed,div.b_dimmed{opacity:0.4;filter:alpha(opacity=40)}.o_selected,.b_selected,p.b_selected,div.b_selected{font-weight:bold}.o_deleted,.b_deleted,p.b_deleted,div.b_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,.b_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,.b_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,.b_copy_code,p.b_copy_code,div.b_copy_code,code,pre{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;border-radius:4px}.o_nowrap,.b_copy_code,p.b_copy_code,div.b_copy_code,code{white-space:nowrap}.o_titled_wrapper .o_content{margin-top:20px}.o_video,.b_video{display:inline-block;max-width:100%;height:auto}.o_image,img,.b_image{display:inline-block;max-width:100%;height:auto}.o_with_hyphens{-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}h1{color:#337ab7}h2{color:#337ab7}h3{color:#337ab7}h4,.o_cal .fc-header-title h2{color:#337ab7}h5{color:#337ab7}h5{color:#337ab7}fieldset legend{color:#333}.b_border_box,p.b_border_box,div.b_border_box{border:1px solid #777;padding:1em;border-top-right-radius:3px;border-top-left-radius:3px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}table td{vertical-align:top}table.b_grid{width:99.5%;background:transparent;border-collapse:separate}table.b_grid td,table.b_grid th{padding:1px 5px;border:1px solid #777}table.b_grid th{background:#eee}table.b_border{width:99.5%;background:transparent;border-collapse:collapse}table.b_border td,table.b_border th{padding:1px 5px;border:1px solid #777}table.b_border th{background:#eee}table.b_borderless{width:99.5%;background:transparent;border-collapse:separate}table.b_borderless td,table.b_borderless th{padding:1px 5px;border:0}table.b_full{width:99.5%}table.b_middle{background:transparent}table.b_middle td{vertical-align:middle}.b_align_normal{text-align:left}.b_align_center{text-align:center}.b_align_inverse{text-align:right}.b_align_justified{text-align:justify}a.b_link_extern{color:#337ab7}a.b_link_extern:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding-right:0.5em;content:"\f08e"}a.b_link_mailto{color:#337ab7}a.b_link_mailto:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding-right:0.5em;content:"\f003"}a.b_link_forward{color:#337ab7}a.b_link_forward:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding-right:0.5em;content:"\f064"}img.b_float_left{float:left;margin:0 2em 2em 0}img.b_float_left_clear{clear:both;margin:0 2em 2em 0;display:block}img.b_float_right{float:right;margin:0 0 2em 2em}img.b_float_right_clear{clear:both;display:block;margin:0 0 2em auto}img.b_centered{clear:both;display:block;margin:0 auto 2em auto}img.b_circle{border-radius:50%}img.b_with_border{border:1px solid #ddd;padding:3px;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px}span.olatFlashMovieViewer{max-width:100%;height:auto !important}.mejs-container,.mejs-mediaelement video,.mejs-layers div{max-width:100%}.b_clear_float,p.b_clear_float,div.b_clear_float{clear:both}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:#337ab7}#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}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>#o_navbar_tools_permanent>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 .dropdown-menu a,.o_navbar #o_navbar_tools_permanent #o_navbar_my_menu .dropdown-menu a{padding-left:15px}.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:#9d9d9d}.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;vertical-align:baseline}.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:#9d9d9d;font-size:14px;border:none}.o_navbar.o_navbar-offcanvas .o_navbar-right .dropdown-menu .divider{background:none}.o_navbar.o_navbar-offcanvas .o_navbar-nav a{color:#9d9d9d;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:#9d9d9d}.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_impress 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_body_popup #o_topnav_printview{display:inline-block}.o_body_popup #o_topnav_close{float:right}.o_body_popup #o_topnav_close span{display:block}.o_body_popup #o_navbar_tools_permanent li>a{background-color:transparent}.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:#337ab7;background-color:transparent}.o_toolbar .o_tools_container .dropdown-menu a{display:block}.o_toolbar .o_tools_container .dropdown-menu a.active{color:#337ab7;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;margin-right: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{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{position:relative}.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{padding:0 10px 0 5px}.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:20px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span{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_center{float:left}.o_toolbar .o_tool,.o_toolbar .o_text,.o_toolbar .o_tool_dropdown{margin:0 0;position:static}.o_toolbar .o_tool_dropdown .dropdown-menu{left:0px;right:0px}.o_toolbar .o_tool_dropdown .dropdown-menu a,.o_toolbar .o_tool_dropdown .dropdown-menu i{line-height:30px}.o_toolbar .o_tool_dropdown .dropdown-menu .o_navbar_tab_close{line-height:inherit}} +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}#o_offcanvas_right{position:absolute;top:0;right:-250px;width:250px;padding:15px 15px;background-color:#222;color:#9d9d9d;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,.b_info,p.b_info,div.b_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,.b_info h2,.o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback h2,.b_info h3,.o_form .o_info h3,.o_togglebox_wrapper div.o_togglebox_content h3,div.o_qti_item_itemfeedback h3,.b_info h4,.o_form .o_info h4,.o_togglebox_wrapper div.o_togglebox_content h4,div.o_qti_item_itemfeedback h4,.b_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_info h2,.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,.b_info h5,.o_form .o_info h5,.o_togglebox_wrapper div.o_togglebox_content h5,div.o_qti_item_itemfeedback h5{color:#777}.o_note,.b_note,p.b_note,div.b_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,.b_note h2,.o_form .o_desc h2,.o_course_run .o_statusinfo h2,.o_course_stats .o_desc h2,.b_note h3,.o_form .o_desc h3,.o_course_run .o_statusinfo h3,.o_course_stats .o_desc h3,.b_note h4,.o_form .o_desc h4,.o_course_run .o_statusinfo h4,.o_course_stats .o_desc h4,.b_note .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_note h2,.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,.b_note h5,.o_form .o_desc h5,.o_course_run .o_statusinfo h5,.o_course_stats .o_desc h5{color:#31708f}.o_important,.b_important,p.b_important,div.b_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,.b_important h2,.o_bc_empty h2,.o_course_run .o_no_scoreinfo h2,.b_important h3,.o_bc_empty h3,.o_course_run .o_no_scoreinfo h3,.b_important h4,.o_bc_empty h4,.o_course_run .o_no_scoreinfo h4,.b_important .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_important h2,.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,.b_important h5,.o_bc_empty h5,.o_course_run .o_no_scoreinfo h5{color:#f4d000}.o_success,.b_success,p.b_success,div.b_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,.b_success h2,.b_success h3,.b_success h4,.b_success .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_success h2,.b_success h5{color:#3c763d}.o_warning,.b_warning,p.b_warning,div.b_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,.b_warning h2,.o_form .o_warning h2,.b_warning h3,.o_form .o_warning h3,.b_warning h4,.o_form .o_warning h4,.b_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_warning h2,.o_form .o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_warning h2,.b_warning h5,.o_form .o_warning h5{color:#8a6d3b}.o_error,.b_error,p.b_error,div.b_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,.b_error h2,.b_error h3,.b_error h4,.b_error .o_cal .fc-header-title h2,.o_cal .fc-header-title .b_error h2,.b_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}#o_msg_sticky.o_msg_sticky_fullscreen,#o_msg_sticky_preview.o_msg_sticky_fullscreen{margin-top:0}@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:#337ab7;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}.o_tree .o_tree_link{background-color:none}.o_tree .o_tree_link:hover,.o_tree .o_tree_link:focus{background-color:#f8f8f8}.o_tree .o_tree_link:first-child{background-color:transparent}.o_tree .o_tree_link:last-child:hover,.o_tree .o_tree_link:last-child:focus{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 .active.o_tree_link{background-color:none;font-weight:bold}.o_tree ul li .active.o_tree_link a{color:#337ab7}.o_tree ul li .active.o_tree_link:hover,.o_tree ul li .active.o_tree_link:focus{background-color:#eee}.o_tree ul li .active.o_tree_link:hover a,.o_tree ul li .active.o_tree_link:focus a{color:#23527c}.o_tree ul li .active_parent.o_tree_link{font-weight:bold}.o_tree ul li .active_parent.o_tree_link a{color:#777}.o_tree ul li .active_parent.o_tree_link a:hover,.o_tree ul li .active_parent.o_tree_link 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:#f0ad4e;padding:5px 10px 5px 10px;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_dnd_item.o_dnd_over{background-color:#ffff60}.o_tree .o_dnd_sibling{height:7px;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_tree.o_tree_insert_tool span.o_tree_link a{display:block}.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{margin-right:0.25em}.o_form .o_form_chelp{padding-left:0.25em;margin-right:-1.25em}.o_form .o_form_example{font-size:90%}.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 hr.o_spacer.form,.o_form hr.o_spacer_noline.form{margin-top:0px;margin-bottom:0px}.o_form .form-group.o_omit_margin{margin-bottom:0}.o_form .o_date{position:relative;padding-right:34px}.o_form .o_date.form-inline .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.o_navbar-form .o_date_ms.form-group{margin-left:25px}.o_form .has-feedback .o_date.form-inline .form-control.o_date_ms,.o_form .has-feedback .o_date.o_navbar-form .form-control.o_date_ms{padding-right:0}.o_form .o_form_element.form-inline .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_centered_form{text-align:center}.o_centered_form fieldset.o_form{display:inline-block;text-align:left}.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.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.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].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.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:#337ab7;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_table_rows_infos{float:left;padding-left:0;padding-right:20px;margin:20px 0}.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}@media (max-width: 767px){.o_table_wrapper .o_table_rows_infos{clear:both}}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_messages{float:left}#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,#o_navbar_imclient #o_im_message{padding-left:15px}#o_navbar_imclient #o_im_summary .badge{color:#fff;background-color:#777}#o_navbar_imclient #o_im_status li>a>span{display:inline}#o_navbar_imclient #o_im_status div.o_chelp_wrapper{right:0.5em}#o_navbar_imclient #o_im_message a:hover,#o_navbar_imclient #o_im_message a:focus{text-decoration:none}#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:#337ab7}.o_im_buddieslist ul a:hover{color:#23527c}.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:#337ab7}.o_rating .o_rating_items .o_legend{margin-left:1em;font-size:12px;line-height:1em}.o_rating .o_rating_explanation{font-size:12px;color:#777}@media (max-width: 991px){.o_rating .o_rating_title,.o_rating .o_rating_explanation{display:none}} +.o_comments .o_comment_wrapper .o_avatar{float:left;margin:0 1em 0 0}.o_comments .o_comment_wrapper .o_reply,.o_comments .o_comment_wrapper .o_delete{float:right}.o_comments .o_comment_wrapper .o_comment_wrapper{margin-left:16px}.o_ratings_and_comments .o_rating_wrapper{vertical-align:middle;display:inline-block}.o_ratings_and_comments a.o_comments{margin-left:10px;position:relative;top:0.1em}.d3chart .bar{shape-rendering:crispEdges}.d3chart .bar_default_light{fill:#64a0d3}.d3chart .bar_default{fill:#337ab7}.d3chart .bar_default_dark{fill:#23527c}.d3chart .axis{font:12px sans-serif}.d3chart .axis path,.d3chart .axis line{fill:none;stroke:#000;shape-rendering:crispEdges}.o_forum_peekview .o_quote_wrapper,.o_forum_peekview .b_quote_wrapper{display:none}.o_forum_thread_sticky{font-weight:bold}.o_forum_switch{font-size:12px}.o_forum_toolbar{margin-bottom:6px;float:left}.o_forum_fulltextsearch{float:right}@media (max-width: 767px){.o_forum_fulltextsearch{float:left}}.o_forum .o_mark,.o_forum .o_ep_collect{float:right;position:relative;width:2em;margin-left:12px}.o_forum .o_portrait{float:left;margin-right:16px}.o_forum .o_portrait_avatar{width:70px;height:70px}.o_forum .o_newindicator{font-size:10px;color:#5cb85c;text-transform:uppercase;padding-left:1em;vertical-align:text-top;white-space:nowrap}.o_forum .o_author,.o_forum .o_date{display:inline-block;color:#777}.o_forum .o_date{font-size:12px}.o_forum .o_modified{color:#8a6d3b;font-size:12px;font-style:italic}.o_forum .o_forum_message{margin-bottom:20px;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1)}.o_forum .o_forum_message_new{-webkit-box-shadow:0 1px 10px rgba(92,184,92,0.3);box-shadow:0 1px 10px rgba(92,184,92,0.3)}.o_forum .o_forum_message_highlight{-webkit-box-shadow:0 1px 10px rgba(240,173,78,0.5);box-shadow:0 1px 10px rgba(240,173,78,0.5)}.o_forum .o_forum_message_header{padding:10px 15px;border-bottom:1px solid #ddd;background-color:#f5f5f5;border-top-right-radius:3px;border-top-left-radius:3px}.o_forum .o_forum_message_title{margin-top:0}.o_forum .o_forum_message_body{padding:10px 15px}.o_forum .o_forum_message_attachments{border-top:1px solid #ddd;padding:10px 15px;font-size:12px;background-color:#f7f7f9}.o_forum .o_attachment{position:relative;max-width:250px;vertical-align:top;margin:6px 12px 10px 0}.o_forum .o_attachment img{margin-top:6px}.o_forum .o_filename{max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_forum .o_icon_enlarge{position:absolute;left:1em;bottom:1em;text-shadow:1px 1px 2px #fff, -1px 1px 2px #fff, 1px -1px 2px #fff, -1px -1px 2px #fff}@media (min-width: 768px) and (max-width: 991px){.o_forum .o_attachments{font-size:10px}.o_forum .o_attachment{max-width:200px}.o_forum .o_attachment img{max-width:150px}.o_forum .o_filename{max-width:200px}}@media (max-width: 767px){.o_forum .o_attachments{font-size:9px}.o_forum .o_attachment{max-width:150px}.o_forum .o_attachment img{max-width:100px}.o_forum .o_filename{max-width:150px}} +.o_quote_wrapper,.b_quote_wrapper{position:relative;margin:10px 0}.o_quote_author,.b_quote_author{color:#777;font-size:12px}.o_quote_author:before,.b_quote_author:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f10e";font-size:21px;padding-right:0.5em}blockquote.o_quote,blockquote.b_quote{color:#555;font-size:12px;margin-top:6px;padding:0 12px}a.o_chelp{display:inline-block;padding:1px 3px;text-align:center;vertical-align:middle;white-space:nowrap;font-size:10px;font-weight:normal;line-height:15px;color:#fff;background-color:#337ab7;border:1px solid #2e6da4;border-radius:2px;cursor:help;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}a.o_chelp:active,a.o_chelp:hover,a.o_chelp:focus{text-decoration:none;color:#fff;background-color:#2a6598;border-color:#204d74}a.o_chelp i{font-size:10px !important}.o_chelp_wrapper{position:relative;float:right;display:inline-block;line-height:1em;margin-bottom:10px;margin-left:10px}.o_iframedisplay iframe{width:100%}.o_singlepage .o_edit{position:absolute;top:10px;right:37px}.o_content_popup{position:absolute;top:10px;right:12px}.o_module_cp_wrapper .o_tools{position:absolute;top:10px;right:12px;text-align:right;vertical-align:middle}.o_module_cp_wrapper .o_tools .o_search_wrapper{display:inline-block;position:relative;top:-2px}ul.o_dropdown{margin:-5px -14px}ul.o_dropdown .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}ul.o_dropdown>li>a{display:block;padding:5px 14px;clear:both;font-weight:normal;line-height:1.42857;color:#333;background:#fff;white-space:nowrap}ul.o_dropdown>li>a:hover,ul.o_dropdown>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.badge.o_scorm_completed{background-color:#3c763d}.badge.o_scorm_failed{background-color:#a94442}.badge.o_scorm_incomplete{background-color:#8a6d3b}.badge.o_scorm_not_attempted{background:none}.o_bc_meta h5,.o_bc_meta .o_author,.o_bc_meta .o_comment,.tooltip h5,.tooltip .o_author,.tooltip .o_comment{color:#fff;margin:5px 0}.o_bc_meta .o_thumbnail,.tooltip .o_thumbnail{width:200px;height:200px;background-color:#fff;margin:0 -5px}.o_htmleditor .o_metadata{border:1px solid #999;border-top-left-radius:3px;border-top-right-radius:3px;border-bottom:0;background:#eee;position:relative;top:1px;padding:5px}.o_htmleditor .o_metadata #o_filename{float:left}.o_htmleditor .o_metadata .o_lastmodified{float:right;color:#777;line-height:1.42857}.o_htmleditor #o_save{margin-top:10px;text-align:center}.o_htmleditor #o_save input{margin-right:1em}.o_htmleditor #o_save input:last-child{margin-right:0}.o_notifications_news_wrapper .o_notifications_news_subscription{margin:10px 0}.o_notifications_news_wrapper .o_notifications_news_subscription h4 i,.o_notifications_news_wrapper .o_notifications_news_subscription .o_cal .fc-header-title h2 i,.o_cal .fc-header-title .o_notifications_news_wrapper .o_notifications_news_subscription h2 i{display:none}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_context{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content{margin-left:1.5em;position:relative}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_icon{position:absolute;left:-1.5em;line-height:1.5em;top:0}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_date{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_url{margin-left:1.5em}.o_noti{margin:6px 0 6px 12px;float:right;color:#777}.o_noti .o_label{color:#777;cursor:help}@media (max-width: 767px){.o_noti .o_label span{display:none}} +.panel-body .o_noti{margin:0}.o_portrait{display:inline-block}.o_portrait img{border-radius:50%;border:none;background-color:#eee;background-position:50% 50%;background-repeat:no-repeat;background-size:cover}.o_portrait_name{margin-top:6px}.o_block_inline .o_portait,.o_block_inline .o_portrait_name,.o_block_inline .o_portrait_image,.o_block_inline_right .o_portait,.o_block_inline_right .o_portrait_name,.o_block_inline_right .o_portrait_image,.o_block_inline_left .o_portait,.o_block_inline_left .o_portrait_name,.o_block_inline_left .o_portrait_image,.o_block_inline_both .o_portait,.o_block_inline_both .o_portrait_name,.o_block_inline_both .o_portrait_image{display:inline-block}.o_portrait_avatar,.o_portrait_dummy,.o_portrait_dummy_female_big,.o_portrait_dummy_male_big,.o_portrait_anonymous{width:100px;height:100px}.o_portrait_dummy{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_dummy_female_big{background-image:url("../light/images/portrait/dummy_female_big.png")}.o_portrait_dummy_male_big{background-image:url("../light/images/portrait/dummy_male_big.png")}.o_portrait_anonymous{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_avatar_small,.o_portrait_dummy_small,.o_portrait_dummy_female_small,.o_portrait_dummy_male_small,.o_portrait_anonymous_small{width:30px;height:30px}.o_portrait_dummy_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_portrait_dummy_female_small{background-image:url("../light/images/portrait/dummy_female_small.png")}.o_portrait_dummy_male_small{background-image:url("../light/images/portrait/dummy_male_small.png")}.o_portrait_anonymous_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_datecomp{position:relative;width:40px;height:52px;border:1px solid #555;margin-right:12px;text-align:center;vertical-align:middle}.o_datecomp div.o_year{position:absolute;left:0;width:100%;top:-20px;height:20px;line-height:20px;font-size:10px}.o_datecomp div.o_month{height:20px;line-height:20px;font-size:12px;background-color:#337ab7;color:#fff}.o_datecomp div.o_day{height:30px;line-height:30px;font-size:18px;border-top:1px solid #555;background-color:#fff;color:#333}.o_block_with_datecomp .o_head{position:relative;padding-left:52px}.o_block_with_datecomp .o_datecomp{position:absolute;top:0.2em;left:0}.o_block_with_datecomp .o_title{margin-top:0}.o_block_with_datecomp .o_meta{color:#777}.o_block_with_datecomp .o_content{border-left:5px solid #eee;padding:0 20px}.o_block_with_datecomp .o_block_footer{padding-left:25px}ul.o_certificates li{padding:5px 0}ul.o_certificates li a.o_sel_certificate_delete{padding-left:2em}.o_cal_toptoolbar{margin-bottom:6px}.o_cal_toptoolbar .o_cal_toptoolbar_help{float:left;margin-right:12px}.o_feed .o_date,.o_feed .o_author{color:#777}.o_feed .o_subscription a{margin-right:1.5em}.o_feed .o_subscription .form-group{margin-bottom:5px}.o_feed .o_subscription .form-control{border:0;background:none;padding:0;height:auto;-webkit-box-shadow:none;box-shadow:none}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper{float:left}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_title,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_explanation,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_legend{display:none}.o_feed .o_blog_posts .o_ratings_and_comments a.o_comments span{display:none}.o_feed .o_content:before,.o_feed .o_content:after{content:" ";display:table}.o_feed .o_content:after{clear:both}.o_glossary .o_register{text-align:center}.o_glossary .o_meta{font-size:90%;color:#777;font-style:italic}.o_glossary dl dt:first-letter{font-size:21px}.o_glossary dl dt small{color:#777}.o_tm_glossary{border-bottom:1px dotted #666699 !important}.o_tm_yellow{background-color:#FFFF66}.o_tm_blue{background-color:#33FFFF}.o_tm_red{background-color:#FF3333}.o_tm_green{background-color:#99FF00}.vitero_iframe{width:100%;height:100%;border:none;min-height:60em}.o_reminder_rule{padding:5px 0}.o_segments.btn-group a span{overflow:hidden;display:block;text-overflow:ellipsis}.o_segments_content{margin-top:20px}.o_tabbed_pane .o_tabbed_pane_content{padding:20px 0 6px 0}.o_togglebox_wrapper .o_opener{position:relative;left:-0.5em}.o_togglebox_wrapper div.o_togglebox_content{position:relative;margin:0}.o_togglebox_wrapper div.o_togglebox_content .o_hide{position:absolute;bottom:0.5em;right:1em}.o_toolboxes ul{margin:0 0 1.5em 0;padding:0 0 0 1.5em}.o_qrcode{width:256px;height:256px}#o_ajax_busy{position:absolute;left:50%;top:20em;margin-left:-2.5em;height:5em;width:5em;color:#fff;z-index:1201;display:none}#o_body.o_ajax_busy{cursor:busy}.o_exception .o_visual{position:relative;background-image:url("../light/images/lion-500x333.jpg");filter:grayscale(50%);-webkit-filter:grayscale(50%);-moz-filter:grayscale(50%);-ms-filter:grayscale(50%);-o-filter:grayscale(50%);width:500px;height:333px;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;margin:0 0 10px 16px}@media (min-width: 768px) and (max-width: 991px){.o_exception .o_visual{width:375px;height:249px}}@media (min-width: 500px) and (max-width: 767px){.o_exception .o_visual{width:250px;height:166px}}@media (max-width: 500px){.o_exception .o_visual{background-size:cover}}.o_exception .jumbotron h1,.o_exception .o_repo_details .o_lead h1,.o_repo_details .o_exception .o_lead h1{color:#d9534f}.tt-input{width:400px}.tt-dropdown-menu{width:400px;margin-top:6px;padding:0 0 0;color:#555;background-color:#fff;border:1px solid #66afe9;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;-webkit-box-shadow:0 0 8px rgba(102,175,233,0.6);box-shadow:0 0 8px rgba(102,175,233,0.6)}.tt-suggestion{padding:6px 12px;font-size:14px;line-height:1.42857}.tt-suggestion.tt-cursor{color:#fff;background-color:#337ab7}.tt-suggestion p{margin:0}.o_search_link_extended,.o_search_link_simple{margin-top:12px;display:inline-block}.o_search_results_stats{color:#777;padding-left:1.5em}.o_search_highlight{margin-left:12px;font-size:12px}.o_search_result_title h4,.o_search_result_title .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_search_result_title h2{display:inline-block;margin-right:12px;margin-bottom:6px}.o_search_result_highlight{font-weight:bold}.o_search_result_context{color:#3c763d}.o_search_result_excerpt{color:#555}.o_search_result_details .o_togglebox_wrapper.o_block{margin-top:0;margin-bottom:0}.o_search_result_details .o_togglebox_wrapper .o_togglebox_content{color:#777;font-size:12px;background:#fff;padding:6px 12px}@media (max-width: 767px){.o_search_result_details{display:none}} +.wizard{border:1px solid #d4d4d4;border-radius:2px;background-color:#f9f9f9;position:relative;overflow:hidden;margin-bottom:15px}.wizard ul{list-style:none outside none;padding:0;margin:0;width:4000px}.wizard ul li{float:left;margin:0;padding:0 20px 0 30px;height:46px;line-height:46px;position:relative;background:#ededed;color:#333;font-size:16px;cursor:default}.wizard ul li .chevron{border:24px solid transparent;border-left:14px solid #d4d4d4;border-right:0;display:block;position:absolute;right:-14px;top:0;z-index:1}.wizard ul li .chevron:before{border:24px solid transparent;border-left:14px solid #ededed;border-right:0;content:"";display:block;position:absolute;right:1px;top:-24px}.wizard ul li.active{background:#f1f6fc;color:#333}.wizard ul li.active .chevron:before{border-left:14px solid #f1f6fc}.wizard ul li .badge{margin-right:8px}.wizard ul li:first-child{border-radius:4px 0 0 4px;padding-left:20px}.o_process{position:relative;padding-left:25px}.o_process .o_step{position:relative;height:auto;padding-top:10px;padding-left:30px;padding-bottom:10px}.o_process .o_bar{position:absolute;top:10px;left:8px;height:100%;border-left:4px solid #777}.o_process .o_bar:after{position:absolute;top:0;left:-10px;height:16px;width:16px;border:4px solid #777;border-radius:16px;background:#fff;content:" "}.o_process .o_title{margin-top:-1px;color:#777 !important}.o_process .o_step.o_active .o_bar,.o_process .o_step.o_active .o_bar:after{border-color:#337ab7}.o_process .o_step.o_active .o_title{color:#337ab7 !important}.o_process .o_step.o_active .o_title:before{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;transform:translate(0, 0);content:"\f0a4"}.o_process .o_step.o_done .o_bar,.o_process .o_step.o_done .o_bar:after{border-color:#5094ce}.o_process .o_step.o_done .o_title{color:#5094ce !important}.o_process .o_step.o_done .o_title:before{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;transform:translate(0, 0);content:"\f00c"}.o_process .o_meta{color:#777;font-size:12px;margin-top:-0.5em}.o_cal_orange{background:#ffc266;border-color:#ff9900;color:#5D5D5D}.o_cal_orange .o_cal_wv_event_header{background:#ff9900}.o_cal_orange a{color:#5d5d5d !important}.o_cal_green{background:#66c266;border-color:#009900;color:#FFF}.o_cal_green .o_cal_wv_event_header{background:#009900}.o_cal_green a{color:#fff !important}.o_cal_blue{background:#4d6e9f;border-color:#2e5894;color:#FFF}.o_cal_blue .o_cal_wv_event_header{background:#2e5894}.o_cal_blue a{color:#fff !important}.o_cal_yellow{background:#ffe066;border-color:#ffcc00;color:#5D5D5D}.o_cal_yellow .o_cal_wv_event_header{background:#ffcc00}.o_cal_yellow a{color:#5d5d5d !important}.o_cal_red{background:#c26666;border-color:#990000;color:#FFF}.o_cal_red .o_cal_wv_event_header{background:#990000}.o_cal_red a{color:#fff !important}.o_cal_rebeccapurple{background:#663399;border-color:#663399;color:#FFF}.o_cal_rebeccapurple .o_cal_wv_event_header{background:#663399}.o_cal_rebeccapurple a{color:#fff !important}.o_cal_grey{background:#DDDAAA;border-color:#5D5D5D;color:#FFF}.o_cal_grey .o_cal_wv_event_header{background:#5D5D5D}.o_cal_grey a{color:#fff !important}.o_cal_config_enabled,.o_cal_config_disabled{position:relative;float:left;display:inline}.o_cal_config_calendar{margin:0 5px;padding:1px 6px 1px 4px;position:relative;width:200px;overflow:hidden;float:left;display:inline}.o_cal_config_color{display:block;width:16px;height:16px;border-radius:8px}.o_cal_colorchooser_selected:before{content:"\f00c"}#o_cal_colorchooser div{border:1px solid #337ab7;margin:5px;display:inline-block}#o_cal_colorchooser div:hover{border:1px solid #333}#o_cal_colorchooser a{width:20px;height:20px;display:inline-block}.fc-button{color:#333;background-color:#fff;border-color:#ccc}.fc-button:hover,.fc-button:focus,.fc-button.focus,.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{background-image:none}.fc-button.disabled,.fc-button.disabled:hover,.fc-button.disabled:focus,.fc-button.disabled.focus,.fc-button.disabled:active,.fc-button.disabled.active,.fc-button[disabled],.fc-button[disabled]:hover,.fc-button[disabled]:focus,.fc-button[disabled].focus,.fc-button[disabled]:active,.fc-button[disabled].active,fieldset[disabled] .fc-button,fieldset[disabled] .fc-button:hover,fieldset[disabled] .fc-button:focus,fieldset[disabled] .fc-button.focus,fieldset[disabled] .fc-button:active,fieldset[disabled] .fc-button.active{background-color:#fff;border-color:#ccc}.fc-button .badge{color:#fff;background-color:#333}.fc-button.fc-state-default{text-shadow:none}.fc-button.fc-state-active{color:#fff;background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active:hover,.fc-button.fc-state-active:focus,.fc-button.fc-state-active.focus,.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{background-image:none}.fc-button.fc-state-active.disabled,.fc-button.fc-state-active.disabled:hover,.fc-button.fc-state-active.disabled:focus,.fc-button.fc-state-active.disabled.focus,.fc-button.fc-state-active.disabled:active,.fc-button.fc-state-active.disabled.active,.fc-button.fc-state-active[disabled],.fc-button.fc-state-active[disabled]:hover,.fc-button.fc-state-active[disabled]:focus,.fc-button.fc-state-active[disabled].focus,.fc-button.fc-state-active[disabled]:active,.fc-button.fc-state-active[disabled].active,fieldset[disabled] .fc-button.fc-state-active,fieldset[disabled] .fc-button.fc-state-active:hover,fieldset[disabled] .fc-button.fc-state-active:focus,fieldset[disabled] .fc-button.fc-state-active.focus,fieldset[disabled] .fc-button.fc-state-active:active,fieldset[disabled] .fc-button.fc-state-active.active{background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active .badge{color:#337ab7;background-color:#fff}.o_visual{position:absolute;top:0;left:0;overflow:hidden;height:120px;width:180px;vertical-align:middle}@media (min-width: 768px) and (max-width: 991px){.o_visual{height:80px;width:120px}}@media (max-width: 767px){.o_visual{height:50px;width:75px}}.o_visual img{width:100%;height:auto}.o_visual .o_visual_not_available{width:100%;height:100%;background-image:url("../light/images/no_preview.png");background-repeat:no-repeat;background-position:50% 50%;background-size:contain}.o_coursetable.o_rendertype_custom .o_table_row{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_coursetable.o_rendertype_custom .o_table_row .o_visual{border-right:1px solid #337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_access{position:absolute;top:0;right:0;height:120px;width:180px;overflow:hidden;border-left:1px solid #337ab7;padding-top:0.25em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{padding:0 1em;height:20px;line-height:20px;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{position:relative;left:2px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score .o_label{color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social{position:absolute;width:100%;bottom:32px;height:20px;padding-left:1em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_title,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating o_rating_legend,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_explanation{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings{padding:0 0 0 1em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label{margin-bottom:1em;color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_methods{color:#5bc0de}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{position:absolute;display:block;bottom:0;width:90px;height:30px;line-height:30px;text-align:center}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{right:0}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start{color:#fff;background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start: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_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active{background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start .badge{color:#337ab7;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book: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_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active{background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book .badge{color:#f0ad4e;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:90px;color:#fff;background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active{background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details .badge{color:#5cb85c;background-color:#fff}@media (min-width: 768px) and (max-width: 991px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{height:80px;width:120px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_comments,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_label{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{width:60px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:60px}}@media (max-width: 767px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{display:none}}.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:120px;margin:0 180px 0 180px;position:relative;padding:1em 0.5em 0.25em 1em;overflow:hidden}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title{margin:0;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a{display:block;color:#337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover{color:#286090}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_author{margin-top:0.5em;line-height:1em;font-size:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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:#337ab7}.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 #337ab7;border-bottom:1px solid #337ab7}.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:#337ab7}.o_catalog .o_level .o_meta .o_title a:hover{color:#286090}.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_list .o_sublevel{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_catalog .o_sublevels_list .o_sublevel .o_visual{height:75px;width:75px}.o_catalog .o_sublevels_list .o_sublevel .o_title{margin:0}.o_catalog .o_sublevels_list .o_sublevel .o_meta{border-left:1px solid #337ab7;min-height:75px;height:75px;margin:0 0 0 75px;padding:0 0 0 1em;overflow:hidden}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_title{line-height:75px}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_desc{display:none}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a{font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a>i,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a>i,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a>i{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 #337ab7;position:relative;height:180px}.o_catalog .o_sublevels .o_sublevel .o_meta{position:absolute;left:0;bottom:0;width:100%;border:1px solid #337ab7;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:#337ab7;font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover{color:#286090}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a>i{display:none}@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%}}@media (min-width: 768px){.o_catalog .o_sublevels_list,.o_catalog .o_sublevels_compact{-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2;columns:2}} +.o_repo_details{position:relative}.o_repo_details .o_lead{margin-bottom:10px}.o_repo_details .o_lead .o_author{margin-top:0.5em;margin-bottom:1em;font-size:120%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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-left:0;float:none;text-align:center}}@media (max-width: 613px){.o_repo_details .o_subcolumn{width:100%}} +.o_meta .o_closed{padding:2px 5px;margin:5px 0}.o_overview .o_closed{padding:12px 15px;margin:15px 0}.o_ac_configuration span.o_ac_infos{font-weight:normal;color:grey}.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_tree.o_course_menu div.o_tree_l0>a:first-child{background-color: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_ll_container h5{margin-bottom:5px}.o_ll_container h5 a.o_desc{color:#337ab7}.o_ll_container h5 a.o_desc small{display:none}.o_ll_container h5 a.o_desc:hover{color:#286090;text-decoration:none}.o_ll_container h5 a.o_desc:hover small{color:#5e5e5e;display:inline}.o_ll_container div.o_comment{color:#777}.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,div.o_qti_menu_section_clickable,div.o_qti_menu_section_active{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:#337ab7}.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.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%;border-top:50px solid transparent;border-bottom:70px solid transparent;background:url("../light/images/learn-bg.jpg");background-size:cover;background-position:center center;background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=1 )}body.o_dmz #o_bg:after{content:" ";position:absolute;top:0;left:0;width:100%;height:100%;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%);background-size:cover;background-position:center center;background-repeat:no-repeat}body.o_dmz #o_toplink{display:none}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:#337ab7}.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:#337ab7}.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_providers .o_icon_provider_olat{font-size:1em}.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.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.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].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.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 .btn-default.o_sel_auth_facebook{color:#fff;background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{color:#fff;background-color:#37538d;border-color:#2d4374}.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled],.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.active{background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook .badge{color:#4568b2;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_twitter{color:#fff;background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{color:#fff;background-color:#00b4f8;border-color:#009ad4}.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled],.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.active{background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter .badge{color:#2cc5ff;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_google{color:#fff;background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google:hover,.o_login .o_login_social .btn-default.o_sel_auth_google:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.focus,.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{color:#fff;background-color:#d83825;border-color:#ba3120}.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_google.disabled,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled],.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.active{background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google .badge{color:#e15f4f;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_linkedin{color:#fff;background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{color:#fff;background-color:#015e8a;border-color:#014667}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled],.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.active{background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin .badge{color:#0181bd;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_adfs{color:#fff;background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{color:#fff;background-color:#000;border-color:#000}.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled],.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.active{background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs .badge{color:#1a1a1a;background-color:#fff}@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 #337ab7 !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:#337ab7;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:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close span{display:none}.ui-dialog .ui-widget-header .ui-button.ui-corner-all{border:none !important;background:#fff !important;float:right}.ui-dialog .ui-widget-content{border-color:#fff;padding:5px;overflow:auto;background:white !important}.ui-dialog .ui-dialog-titlebar{padding:4px 7px 4px 7px;background-color:#eee !important}.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 #2e6da4;background:#337ab7;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}.progress{-webkit-print-color-adjust:exact;background-color:rgba(0,0,0,0.1) !important;border:1px solid rgba(0,0,0,0.5)}.progress-bar{-webkit-print-color-adjust:exact;background-color:#000 !important;border:10px solid #000}}body.o_browser_ie7 #o_offcanvas_right,body.o_browser_ie8 #o_offcanvas_right{right:0px} 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 3e9c771d653..9ad0b984b5e 100644 --- a/src/main/webapp/static/themes/light/theme_ie_completions.css +++ b/src/main/webapp/static/themes/light/theme_ie_completions.css @@ -1 +1,4 @@ -.o_block_inline .o_portait,.o_block_inline .o_portrait_name,.o_block_inline .o_portrait_image,.o_block_inline_right .o_portait,.o_block_inline_right .o_portrait_name,.o_block_inline_right .o_portrait_image,.o_block_inline_left .o_portait,.o_block_inline_left .o_portrait_name,.o_block_inline_left .o_portrait_image,.o_block_inline_both .o_portait,.o_block_inline_both .o_portrait_name,.o_block_inline_both .o_portrait_image{display:inline-block}.o_portrait_avatar,.o_portrait_dummy,.o_portrait_dummy_female_big,.o_portrait_dummy_male_big,.o_portrait_anonymous{width:100px;height:100px}.o_portrait_dummy{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_dummy_female_big{background-image:url("../light/images/portrait/dummy_female_big.png")}.o_portrait_dummy_male_big{background-image:url("../light/images/portrait/dummy_male_big.png")}.o_portrait_anonymous{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_avatar_small,.o_portrait_dummy_small,.o_portrait_dummy_female_small,.o_portrait_dummy_male_small,.o_portrait_anonymous_small{width:30px;height:30px}.o_portrait_dummy_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_portrait_dummy_female_small{background-image:url("../light/images/portrait/dummy_female_small.png")}.o_portrait_dummy_male_small{background-image:url("../light/images/portrait/dummy_male_small.png")}.o_portrait_anonymous_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_datecomp{position:relative;width:40px;height:52px;border:1px solid #555;margin-right:12px;text-align:center;vertical-align:middle}.o_datecomp div.o_year{position:absolute;left:0;width:100%;top:-20px;height:20px;line-height:20px;font-size:10px}.o_datecomp div.o_month{height:20px;line-height:20px;font-size:12px;background-color:#337ab7;color:#fff}.o_datecomp div.o_day{height:30px;line-height:30px;font-size:18px;border-top:1px solid #555;background-color:#fff;color:#333}.o_block_with_datecomp .o_head{position:relative;padding-left:52px}.o_block_with_datecomp .o_datecomp{position:absolute;top:0.2em;left:0}.o_block_with_datecomp .o_title{margin-top:0}.o_block_with_datecomp .o_meta{color:#777}.o_block_with_datecomp .o_content{border-left:5px solid #eee;padding:0 20px}.o_block_with_datecomp .o_block_footer{padding-left:25px}ul.o_certificates li{padding:5px 0}ul.o_certificates li a.o_sel_certificate_delete{padding-left:2em}.o_cal_toptoolbar{margin-bottom:6px}.o_cal_toptoolbar .o_cal_toptoolbar_help{float:left;margin-right:12px}.o_feed .o_date,.o_feed .o_author{color:#777}.o_feed .o_subscription a{margin-right:1.5em}.o_feed .o_subscription .form-group{margin-bottom:5px}.o_feed .o_subscription .form-control{border:0;background:none;padding:0;height:auto;-webkit-box-shadow:none;box-shadow:none}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper{float:left}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_title,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_explanation,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_legend{display:none}.o_feed .o_blog_posts .o_ratings_and_comments a.o_comments span{display:none}.o_feed .o_content:before,.o_feed .o_content:after{content:" ";display:table}.o_feed .o_content:after{clear:both}.o_glossary .o_register{text-align:center}.o_glossary .o_meta{font-size:90%;color:#777;font-style:italic}.o_glossary dl dt:first-letter{font-size:21px}.o_glossary dl dt small{color:#777}.o_tm_glossary{border-bottom:1px dotted #666699 !important}.o_tm_yellow{background-color:#FFFF66}.o_tm_blue{background-color:#33FFFF}.o_tm_red{background-color:#FF3333}.o_tm_green{background-color:#99FF00}.vitero_iframe{width:100%;height:100%;border:none;min-height:60em}.o_reminder_rule{padding:5px 0}.o_segments.btn-group a span{overflow:hidden;display:block;text-overflow:ellipsis}.o_segments_content{margin-top:20px}.o_tabbed_pane .o_tabbed_pane_content{padding:20px 0 6px 0}.o_togglebox_wrapper .o_opener{position:relative;left:-0.5em}.o_togglebox_wrapper div.o_togglebox_content{position:relative;margin:0}.o_togglebox_wrapper div.o_togglebox_content .o_hide{position:absolute;bottom:0.5em;right:1em}.o_toolboxes ul{margin:0 0 1.5em 0;padding:0 0 0 1.5em}.o_qrcode{width:256px;height:256px}#o_ajax_busy{position:absolute;left:50%;top:20em;margin-left:-2.5em;height:5em;width:5em;color:#fff;z-index:1201;display:none}#o_body.o_ajax_busy{cursor:busy}.o_exception .o_visual{position:relative;background-image:url("../light/images/lion-500x333.jpg");filter:grayscale(50%);-webkit-filter:grayscale(50%);-moz-filter:grayscale(50%);-ms-filter:grayscale(50%);-o-filter:grayscale(50%);width:500px;height:333px;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;margin:0 0 10px 16px}@media (min-width: 768px) and (max-width: 991px){.o_exception .o_visual{width:375px;height:249px}}@media (min-width: 500px) and (max-width: 767px){.o_exception .o_visual{width:250px;height:166px}}@media (max-width: 500px){.o_exception .o_visual{background-size:cover}}.o_exception .jumbotron h1,.o_exception .o_repo_details .o_lead h1,.o_repo_details .o_exception .o_lead h1{color:#d9534f}.tt-input{width:400px}.tt-dropdown-menu{width:400px;margin-top:6px;padding:0 0 0;color:#555;background-color:#fff;border:1px solid #66afe9;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;-webkit-box-shadow:0 0 8px rgba(102,175,233,0.6);box-shadow:0 0 8px rgba(102,175,233,0.6)}.tt-suggestion{padding:6px 12px;font-size:14px;line-height:1.42857}.tt-suggestion.tt-cursor{color:#fff;background-color:#337ab7}.tt-suggestion p{margin:0}.o_search_link_extended,.o_search_link_simple{margin-top:12px;display:inline-block}.o_search_results_stats{color:#777;padding-left:1.5em}.o_search_highlight{margin-left:12px;font-size:12px}.o_search_result_title h4,.o_search_result_title .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_search_result_title h2{display:inline-block;margin-right:12px;margin-bottom:6px}.o_search_result_highlight{font-weight:bold}.o_search_result_context{color:#3c763d}.o_search_result_excerpt{color:#555}.o_search_result_details .o_togglebox_wrapper.o_block{margin-top:0;margin-bottom:0}.o_search_result_details .o_togglebox_wrapper .o_togglebox_content{color:#777;font-size:12px;background:#fff;padding:6px 12px}@media (max-width: 767px){.o_search_result_details{display:none}}.wizard{border:1px solid #d4d4d4;border-radius:2px;background-color:#f9f9f9;position:relative;overflow:hidden;margin-bottom:15px}.wizard ul{list-style:none outside none;padding:0;margin:0;width:4000px}.wizard ul li{float:left;margin:0;padding:0 20px 0 30px;height:46px;line-height:46px;position:relative;background:#ededed;color:#333;font-size:16px;cursor:default}.wizard ul li .chevron{border:24px solid transparent;border-left:14px solid #d4d4d4;border-right:0;display:block;position:absolute;right:-14px;top:0;z-index:1}.wizard ul li .chevron:before{border:24px solid transparent;border-left:14px solid #ededed;border-right:0;content:"";display:block;position:absolute;right:1px;top:-24px}.wizard ul li.active{background:#f1f6fc;color:#333}.wizard ul li.active .chevron:before{border-left:14px solid #f1f6fc}.wizard ul li .badge{margin-right:8px}.wizard ul li:first-child{border-radius:4px 0 0 4px;padding-left:20px}.o_process{position:relative;padding-left:25px}.o_process .o_step{position:relative;height:auto;padding-top:10px;padding-left:30px;padding-bottom:10px}.o_process .o_bar{position:absolute;top:10px;left:8px;height:100%;border-left:4px solid #777}.o_process .o_bar:after{position:absolute;top:0;left:-10px;height:16px;width:16px;border:4px solid #777;border-radius:16px;background:#fff;content:" "}.o_process .o_title{margin-top:-1px;color:#777 !important}.o_process .o_step.o_active .o_bar,.o_process .o_step.o_active .o_bar:after{border-color:#337ab7}.o_process .o_step.o_active .o_title{color:#337ab7 !important}.o_process .o_step.o_active .o_title:before{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;transform:translate(0, 0);content:""}.o_process .o_step.o_done .o_bar,.o_process .o_step.o_done .o_bar:after{border-color:#5094ce}.o_process .o_step.o_done .o_title{color:#5094ce !important}.o_process .o_step.o_done .o_title:before{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;transform:translate(0, 0);content:""}.o_process .o_meta{color:#777;font-size:12px;margin-top:-0.5em}.o_cal_orange{background:#ffc266;border-color:#ff9900;color:#5D5D5D}.o_cal_orange .o_cal_wv_event_header{background:#ff9900}.o_cal_orange a{color:#5D5D5D !important}.o_cal_green{background:#66c266;border-color:#009900;color:#FFF}.o_cal_green .o_cal_wv_event_header{background:#009900}.o_cal_green a{color:#FFF !important}.o_cal_blue{background:#4d6e9f;border-color:#2e5894;color:#FFF}.o_cal_blue .o_cal_wv_event_header{background:#2e5894}.o_cal_blue a{color:#FFF !important}.o_cal_yellow{background:#ffe066;border-color:#ffcc00;color:#5D5D5D}.o_cal_yellow .o_cal_wv_event_header{background:#ffcc00}.o_cal_yellow a{color:#5D5D5D !important}.o_cal_red{background:#c26666;border-color:#990000;color:#FFF}.o_cal_red .o_cal_wv_event_header{background:#990000}.o_cal_red a{color:#FFF !important}.o_cal_rebeccapurple{background:#663399;border-color:#663399;color:#FFF}.o_cal_rebeccapurple .o_cal_wv_event_header{background:#663399}.o_cal_rebeccapurple a{color:#FFF !important}.o_cal_grey{background:#DDDAAA;border-color:#5D5D5D;color:#FFF}.o_cal_grey .o_cal_wv_event_header{background:#5D5D5D}.o_cal_grey a{color:#FFF !important}.o_cal_config_enabled,.o_cal_config_disabled{position:relative;float:left;display:inline}.o_cal_config_calendar{margin:0 5px;padding:1px 6px 1px 4px;position:relative;width:200px;overflow:hidden;float:left;display:inline}.o_cal_colorchooser_selected:before{content:""}#o_cal_colorchooser div{border:1px solid #337ab7;margin:5px;display:inline-block}#o_cal_colorchooser div:hover{border:1px solid #333}#o_cal_colorchooser a{width:20px;height:20px;display:inline-block}.fc-button{color:#333;background-color:#fff;border-color:#ccc}.fc-button:hover,.fc-button:focus,.fc-button.focus,.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{background-image:none}.fc-button.disabled,.fc-button.disabled:hover,.fc-button.disabled:focus,.fc-button.disabled.focus,.fc-button.disabled:active,.fc-button.disabled.active,.fc-button[disabled],.fc-button[disabled]:hover,.fc-button[disabled]:focus,.fc-button[disabled].focus,.fc-button[disabled]:active,.fc-button[disabled].active,fieldset[disabled] .fc-button,fieldset[disabled] .fc-button:hover,fieldset[disabled] .fc-button:focus,fieldset[disabled] .fc-button.focus,fieldset[disabled] .fc-button:active,fieldset[disabled] .fc-button.active{background-color:#fff;border-color:#ccc}.fc-button .badge{color:#fff;background-color:#333}.fc-button.fc-state-default{text-shadow:none}.fc-button.fc-state-active{color:#fff;background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active:hover,.fc-button.fc-state-active:focus,.fc-button.fc-state-active.focus,.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{background-image:none}.fc-button.fc-state-active.disabled,.fc-button.fc-state-active.disabled:hover,.fc-button.fc-state-active.disabled:focus,.fc-button.fc-state-active.disabled.focus,.fc-button.fc-state-active.disabled:active,.fc-button.fc-state-active.disabled.active,.fc-button.fc-state-active[disabled],.fc-button.fc-state-active[disabled]:hover,.fc-button.fc-state-active[disabled]:focus,.fc-button.fc-state-active[disabled].focus,.fc-button.fc-state-active[disabled]:active,.fc-button.fc-state-active[disabled].active,fieldset[disabled] .fc-button.fc-state-active,fieldset[disabled] .fc-button.fc-state-active:hover,fieldset[disabled] .fc-button.fc-state-active:focus,fieldset[disabled] .fc-button.fc-state-active.focus,fieldset[disabled] .fc-button.fc-state-active:active,fieldset[disabled] .fc-button.fc-state-active.active{background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active .badge{color:#337ab7;background-color:#fff}.o_visual{position:absolute;top:0;left:0;overflow:hidden;height:120px;width:180px;vertical-align:middle}@media (min-width: 768px) and (max-width: 991px){.o_visual{height:80px;width:120px}}@media (max-width: 767px){.o_visual{height:50px;width:75px}}.o_visual img{width:100%;height:auto}.o_visual .o_visual_not_available{width:100%;height:100%;background-image:url("../light/images/no_preview.png");background-repeat:no-repeat;background-position:50% 50%;background-size:contain}.o_coursetable.o_rendertype_custom .o_table_row{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_coursetable.o_rendertype_custom .o_table_row .o_visual{border-right:1px solid #337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_access{position:absolute;top:0;right:0;height:120px;width:180px;overflow:hidden;border-left:1px solid #337ab7;padding-top:0.25em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{padding:0 1em;height:20px;line-height:20px;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{position:relative;left:2px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score .o_label{color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social{position:absolute;width:100%;bottom:32px;height:20px;padding-left:1em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_title,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating o_rating_legend,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_explanation{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings{padding:0 0 0 1em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label{margin-bottom:1em;color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_methods{color:#5bc0de}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{position:absolute;display:block;bottom:0;width:90px;height:30px;line-height:30px;text-align:center}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{right:0}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start{color:#fff;background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start: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_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active{background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start .badge{color:#337ab7;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book: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_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active{background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book .badge{color:#f0ad4e;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:90px;color:#fff;background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active{background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details .badge{color:#5cb85c;background-color:#fff}@media (min-width: 768px) and (max-width: 991px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{height:80px;width:120px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_comments,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_label{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{width:60px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:60px}}@media (max-width: 767px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{display:none}}.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:120px;margin:0 180px 0 180px;position:relative;padding:1em 0.5em 0.25em 1em;overflow:hidden}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title{margin:0;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a{display:block;color:#337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover{color:#286090}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_author{margin-top:0.5em;line-height:1em;font-size:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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:#337ab7}.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 #337ab7;border-bottom:1px solid #337ab7}.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:#337ab7}.o_catalog .o_level .o_meta .o_title a:hover{color:#286090}.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_list .o_sublevel{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_catalog .o_sublevels_list .o_sublevel .o_visual{height:75px;width:75px}.o_catalog .o_sublevels_list .o_sublevel .o_title{margin:0}.o_catalog .o_sublevels_list .o_sublevel .o_meta{border-left:1px solid #337ab7;min-height:75px;height:75px;margin:0 0 0 75px;padding:0 0 0 1em;overflow:hidden}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_title{line-height:75px}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_desc{display:none}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a{font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a>i,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a>i,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a>i{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 #337ab7;position:relative;height:180px}.o_catalog .o_sublevels .o_sublevel .o_meta{position:absolute;left:0;bottom:0;width:100%;border:1px solid #337ab7;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:#337ab7;font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover{color:#286090}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a>i{display:none}@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%}}@media (min-width: 768px){.o_catalog .o_sublevels_list,.o_catalog .o_sublevels_compact{-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2;columns:2}}.o_repo_details{position:relative}.o_repo_details .o_lead{margin-bottom:10px}.o_repo_details .o_lead .o_author{margin-top:0.5em;margin-bottom:1em;font-size:120%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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-left:0;float:none;text-align:center}}@media (max-width: 613px){.o_repo_details .o_subcolumn{width:100%}}.o_meta .o_closed{padding:2px 5px;margin:5px 0}.o_overview .o_closed{padding:12px 15px;margin:15px 0}.o_ac_configuration span.o_ac_infos{font-weight:normal;color:grey}.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_tree.o_course_menu div.o_tree_l0>a:first-child{background-color: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_ll_container h5{margin-bottom:5px}.o_ll_container h5 a.o_desc{color:#337ab7}.o_ll_container h5 a.o_desc small{display:none}.o_ll_container h5 a.o_desc:hover{color:#286090;text-decoration:none}.o_ll_container h5 a.o_desc:hover small{color:#5e5e5e;display:inline}.o_ll_container div.o_comment{color:#777}.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,div.o_qti_menu_section_clickable,div.o_qti_menu_section_active{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:"ï„"}.o_qti_menu_item_attempts_marked:after{content:"";color:#337ab7}.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:""}.o_ep_icon_collection:before{content:""}.o_ep_icon_page:before{content:""}.o_ep_icon_struct:before{content:""}.o_ep_icon_liveblog:before{content:"ï‚¡"}.o_artefact_closed:before{content:""}.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.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%;border-top:50px solid transparent;border-bottom:70px solid transparent;background:url("../light/images/learn-bg.jpg");background-size:cover;background-position:center center;background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=1 )}body.o_dmz #o_bg:after{content:" ";position:absolute;top:0;left:0;width:100%;height:100%;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%);background-size:cover;background-position:center center;background-repeat:no-repeat}body.o_dmz #o_toplink{display:none}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:#337ab7}.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:#337ab7}.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_providers .o_icon_provider_olat{font-size:1em}.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.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.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].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.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 .btn-default.o_sel_auth_facebook{color:#fff;background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{color:#fff;background-color:#37538d;border-color:#2d4374}.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled],.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.active{background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook .badge{color:#4568b2;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_twitter{color:#fff;background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{color:#fff;background-color:#00b4f8;border-color:#009ad4}.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled],.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.active{background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter .badge{color:#2cc5ff;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_google{color:#fff;background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google:hover,.o_login .o_login_social .btn-default.o_sel_auth_google:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.focus,.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{color:#fff;background-color:#d83825;border-color:#ba3120}.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_google.disabled,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled],.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.active{background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google .badge{color:#e15f4f;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_linkedin{color:#fff;background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{color:#fff;background-color:#015e8a;border-color:#014667}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled],.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.active{background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin .badge{color:#0181bd;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_adfs{color:#fff;background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{color:#fff;background-color:#000;border-color:#000}.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled],.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.active{background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs .badge{color:#1a1a1a;background-color:#fff}@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:""}.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 #337ab7 !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:#337ab7;font-weight:500;font-family:inherit;line-height:1.1}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close:before{content:"ï€" !important}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;font-size:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close span{display:none}.ui-dialog .ui-widget-header .ui-button.ui-corner-all{border:none !important;background:#fff !important;float:right}.ui-dialog .ui-widget-content{border-color:#fff;padding:5px;overflow:auto;background:white !important}.ui-dialog .ui-dialog-titlebar{padding:4px 7px 4px 7px;background-color:#eee !important}.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:"ï¡";font-weight:normal;color:black}.ui-datepicker .ui-widget-header .ui-icon.ui-icon-circle-triangle-w:before{content:"ï ";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 #2e6da4;background:#337ab7;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}.progress{-webkit-print-color-adjust:exact;background-color:rgba(0,0,0,0.1) !important;border:1px solid rgba(0,0,0,0.5)}.progress-bar{-webkit-print-color-adjust:exact;background-color:#000 !important;border:10px solid #000}}body.o_browser_ie7 #o_offcanvas_right,body.o_browser_ie8 #o_offcanvas_right{right:0px} \ No newline at end of file +.o_block_inline .o_portait,.o_block_inline .o_portrait_name,.o_block_inline .o_portrait_image,.o_block_inline_right .o_portait,.o_block_inline_right .o_portrait_name,.o_block_inline_right .o_portrait_image,.o_block_inline_left .o_portait,.o_block_inline_left .o_portrait_name,.o_block_inline_left .o_portrait_image,.o_block_inline_both .o_portait,.o_block_inline_both .o_portrait_name,.o_block_inline_both .o_portrait_image{display:inline-block}.o_portrait_avatar,.o_portrait_dummy,.o_portrait_dummy_female_big,.o_portrait_dummy_male_big,.o_portrait_anonymous{width:100px;height:100px}.o_portrait_dummy{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_dummy_female_big{background-image:url("../light/images/portrait/dummy_female_big.png")}.o_portrait_dummy_male_big{background-image:url("../light/images/portrait/dummy_male_big.png")}.o_portrait_anonymous{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_avatar_small,.o_portrait_dummy_small,.o_portrait_dummy_female_small,.o_portrait_dummy_male_small,.o_portrait_anonymous_small{width:30px;height:30px}.o_portrait_dummy_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_portrait_dummy_female_small{background-image:url("../light/images/portrait/dummy_female_small.png")}.o_portrait_dummy_male_small{background-image:url("../light/images/portrait/dummy_male_small.png")}.o_portrait_anonymous_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_datecomp{position:relative;width:40px;height:52px;border:1px solid #555;margin-right:12px;text-align:center;vertical-align:middle}.o_datecomp div.o_year{position:absolute;left:0;width:100%;top:-20px;height:20px;line-height:20px;font-size:10px}.o_datecomp div.o_month{height:20px;line-height:20px;font-size:12px;background-color:#337ab7;color:#fff}.o_datecomp div.o_day{height:30px;line-height:30px;font-size:18px;border-top:1px solid #555;background-color:#fff;color:#333}.o_block_with_datecomp .o_head{position:relative;padding-left:52px}.o_block_with_datecomp .o_datecomp{position:absolute;top:0.2em;left:0}.o_block_with_datecomp .o_title{margin-top:0}.o_block_with_datecomp .o_meta{color:#777}.o_block_with_datecomp .o_content{border-left:5px solid #eee;padding:0 20px}.o_block_with_datecomp .o_block_footer{padding-left:25px}ul.o_certificates li{padding:5px 0}ul.o_certificates li a.o_sel_certificate_delete{padding-left:2em}.o_cal_toptoolbar{margin-bottom:6px}.o_cal_toptoolbar .o_cal_toptoolbar_help{float:left;margin-right:12px}.o_feed .o_date,.o_feed .o_author{color:#777}.o_feed .o_subscription a{margin-right:1.5em}.o_feed .o_subscription .form-group{margin-bottom:5px}.o_feed .o_subscription .form-control{border:0;background:none;padding:0;height:auto;-webkit-box-shadow:none;box-shadow:none}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper{float:left}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_title,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_explanation,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_legend{display:none}.o_feed .o_blog_posts .o_ratings_and_comments a.o_comments span{display:none}.o_feed .o_content:before,.o_feed .o_content:after{content:" ";display:table}.o_feed .o_content:after{clear:both}.o_glossary .o_register{text-align:center}.o_glossary .o_meta{font-size:90%;color:#777;font-style:italic}.o_glossary dl dt:first-letter{font-size:21px}.o_glossary dl dt small{color:#777}.o_tm_glossary{border-bottom:1px dotted #666699 !important}.o_tm_yellow{background-color:#FFFF66}.o_tm_blue{background-color:#33FFFF}.o_tm_red{background-color:#FF3333}.o_tm_green{background-color:#99FF00}.vitero_iframe{width:100%;height:100%;border:none;min-height:60em}.o_reminder_rule{padding:5px 0}.o_segments.btn-group a span{overflow:hidden;display:block;text-overflow:ellipsis}.o_segments_content{margin-top:20px}.o_tabbed_pane .o_tabbed_pane_content{padding:20px 0 6px 0}.o_togglebox_wrapper .o_opener{position:relative;left:-0.5em}.o_togglebox_wrapper div.o_togglebox_content{position:relative;margin:0}.o_togglebox_wrapper div.o_togglebox_content .o_hide{position:absolute;bottom:0.5em;right:1em}.o_toolboxes ul{margin:0 0 1.5em 0;padding:0 0 0 1.5em}.o_qrcode{width:256px;height:256px}#o_ajax_busy{position:absolute;left:50%;top:20em;margin-left:-2.5em;height:5em;width:5em;color:#fff;z-index:1201;display:none}#o_body.o_ajax_busy{cursor:busy}.o_exception .o_visual{position:relative;background-image:url("../light/images/lion-500x333.jpg");filter:grayscale(50%);-webkit-filter:grayscale(50%);-moz-filter:grayscale(50%);-ms-filter:grayscale(50%);-o-filter:grayscale(50%);width:500px;height:333px;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;margin:0 0 10px 16px}@media (min-width: 768px) and (max-width: 991px){.o_exception .o_visual{width:375px;height:249px}}@media (min-width: 500px) and (max-width: 767px){.o_exception .o_visual{width:250px;height:166px}}@media (max-width: 500px){.o_exception .o_visual{background-size:cover}}.o_exception .jumbotron h1,.o_exception .o_repo_details .o_lead h1,.o_repo_details .o_exception .o_lead h1{color:#d9534f}.tt-input{width:400px}.tt-dropdown-menu{width:400px;margin-top:6px;padding:0 0 0;color:#555;background-color:#fff;border:1px solid #66afe9;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;-webkit-box-shadow:0 0 8px rgba(102,175,233,0.6);box-shadow:0 0 8px rgba(102,175,233,0.6)}.tt-suggestion{padding:6px 12px;font-size:14px;line-height:1.42857}.tt-suggestion.tt-cursor{color:#fff;background-color:#337ab7}.tt-suggestion p{margin:0}.o_search_link_extended,.o_search_link_simple{margin-top:12px;display:inline-block}.o_search_results_stats{color:#777;padding-left:1.5em}.o_search_highlight{margin-left:12px;font-size:12px}.o_search_result_title h4,.o_search_result_title .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_search_result_title h2{display:inline-block;margin-right:12px;margin-bottom:6px}.o_search_result_highlight{font-weight:bold}.o_search_result_context{color:#3c763d}.o_search_result_excerpt{color:#555}.o_search_result_details .o_togglebox_wrapper.o_block{margin-top:0;margin-bottom:0}.o_search_result_details .o_togglebox_wrapper .o_togglebox_content{color:#777;font-size:12px;background:#fff;padding:6px 12px}@media (max-width: 767px){.o_search_result_details{display:none}} +.wizard{border:1px solid #d4d4d4;border-radius:2px;background-color:#f9f9f9;position:relative;overflow:hidden;margin-bottom:15px}.wizard ul{list-style:none outside none;padding:0;margin:0;width:4000px}.wizard ul li{float:left;margin:0;padding:0 20px 0 30px;height:46px;line-height:46px;position:relative;background:#ededed;color:#333;font-size:16px;cursor:default}.wizard ul li .chevron{border:24px solid transparent;border-left:14px solid #d4d4d4;border-right:0;display:block;position:absolute;right:-14px;top:0;z-index:1}.wizard ul li .chevron:before{border:24px solid transparent;border-left:14px solid #ededed;border-right:0;content:"";display:block;position:absolute;right:1px;top:-24px}.wizard ul li.active{background:#f1f6fc;color:#333}.wizard ul li.active .chevron:before{border-left:14px solid #f1f6fc}.wizard ul li .badge{margin-right:8px}.wizard ul li:first-child{border-radius:4px 0 0 4px;padding-left:20px}.o_process{position:relative;padding-left:25px}.o_process .o_step{position:relative;height:auto;padding-top:10px;padding-left:30px;padding-bottom:10px}.o_process .o_bar{position:absolute;top:10px;left:8px;height:100%;border-left:4px solid #777}.o_process .o_bar:after{position:absolute;top:0;left:-10px;height:16px;width:16px;border:4px solid #777;border-radius:16px;background:#fff;content:" "}.o_process .o_title{margin-top:-1px;color:#777 !important}.o_process .o_step.o_active .o_bar,.o_process .o_step.o_active .o_bar:after{border-color:#337ab7}.o_process .o_step.o_active .o_title{color:#337ab7 !important}.o_process .o_step.o_active .o_title:before{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;transform:translate(0, 0);content:"\f0a4"}.o_process .o_step.o_done .o_bar,.o_process .o_step.o_done .o_bar:after{border-color:#5094ce}.o_process .o_step.o_done .o_title{color:#5094ce !important}.o_process .o_step.o_done .o_title:before{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;transform:translate(0, 0);content:"\f00c"}.o_process .o_meta{color:#777;font-size:12px;margin-top:-0.5em}.o_cal_orange{background:#ffc266;border-color:#ff9900;color:#5D5D5D}.o_cal_orange .o_cal_wv_event_header{background:#ff9900}.o_cal_orange a{color:#5d5d5d !important}.o_cal_green{background:#66c266;border-color:#009900;color:#FFF}.o_cal_green .o_cal_wv_event_header{background:#009900}.o_cal_green a{color:#fff !important}.o_cal_blue{background:#4d6e9f;border-color:#2e5894;color:#FFF}.o_cal_blue .o_cal_wv_event_header{background:#2e5894}.o_cal_blue a{color:#fff !important}.o_cal_yellow{background:#ffe066;border-color:#ffcc00;color:#5D5D5D}.o_cal_yellow .o_cal_wv_event_header{background:#ffcc00}.o_cal_yellow a{color:#5d5d5d !important}.o_cal_red{background:#c26666;border-color:#990000;color:#FFF}.o_cal_red .o_cal_wv_event_header{background:#990000}.o_cal_red a{color:#fff !important}.o_cal_rebeccapurple{background:#663399;border-color:#663399;color:#FFF}.o_cal_rebeccapurple .o_cal_wv_event_header{background:#663399}.o_cal_rebeccapurple a{color:#fff !important}.o_cal_grey{background:#DDDAAA;border-color:#5D5D5D;color:#FFF}.o_cal_grey .o_cal_wv_event_header{background:#5D5D5D}.o_cal_grey a{color:#fff !important}.o_cal_config_enabled,.o_cal_config_disabled{position:relative;float:left;display:inline}.o_cal_config_calendar{margin:0 5px;padding:1px 6px 1px 4px;position:relative;width:200px;overflow:hidden;float:left;display:inline}.o_cal_config_color{display:block;width:16px;height:16px;border-radius:8px}.o_cal_colorchooser_selected:before{content:"\f00c"}#o_cal_colorchooser div{border:1px solid #337ab7;margin:5px;display:inline-block}#o_cal_colorchooser div:hover{border:1px solid #333}#o_cal_colorchooser a{width:20px;height:20px;display:inline-block}.fc-button{color:#333;background-color:#fff;border-color:#ccc}.fc-button:hover,.fc-button:focus,.fc-button.focus,.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.fc-button:active,.fc-button.active,.open>.fc-button.dropdown-toggle{background-image:none}.fc-button.disabled,.fc-button.disabled:hover,.fc-button.disabled:focus,.fc-button.disabled.focus,.fc-button.disabled:active,.fc-button.disabled.active,.fc-button[disabled],.fc-button[disabled]:hover,.fc-button[disabled]:focus,.fc-button[disabled].focus,.fc-button[disabled]:active,.fc-button[disabled].active,fieldset[disabled] .fc-button,fieldset[disabled] .fc-button:hover,fieldset[disabled] .fc-button:focus,fieldset[disabled] .fc-button.focus,fieldset[disabled] .fc-button:active,fieldset[disabled] .fc-button.active{background-color:#fff;border-color:#ccc}.fc-button .badge{color:#fff;background-color:#333}.fc-button.fc-state-default{text-shadow:none}.fc-button.fc-state-active{color:#fff;background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active:hover,.fc-button.fc-state-active:focus,.fc-button.fc-state-active.focus,.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.fc-button.fc-state-active:active,.fc-button.fc-state-active.active,.open>.fc-button.fc-state-active.dropdown-toggle{background-image:none}.fc-button.fc-state-active.disabled,.fc-button.fc-state-active.disabled:hover,.fc-button.fc-state-active.disabled:focus,.fc-button.fc-state-active.disabled.focus,.fc-button.fc-state-active.disabled:active,.fc-button.fc-state-active.disabled.active,.fc-button.fc-state-active[disabled],.fc-button.fc-state-active[disabled]:hover,.fc-button.fc-state-active[disabled]:focus,.fc-button.fc-state-active[disabled].focus,.fc-button.fc-state-active[disabled]:active,.fc-button.fc-state-active[disabled].active,fieldset[disabled] .fc-button.fc-state-active,fieldset[disabled] .fc-button.fc-state-active:hover,fieldset[disabled] .fc-button.fc-state-active:focus,fieldset[disabled] .fc-button.fc-state-active.focus,fieldset[disabled] .fc-button.fc-state-active:active,fieldset[disabled] .fc-button.fc-state-active.active{background-color:#337ab7;border-color:#2e6da4}.fc-button.fc-state-active .badge{color:#337ab7;background-color:#fff}.o_visual{position:absolute;top:0;left:0;overflow:hidden;height:120px;width:180px;vertical-align:middle}@media (min-width: 768px) and (max-width: 991px){.o_visual{height:80px;width:120px}}@media (max-width: 767px){.o_visual{height:50px;width:75px}}.o_visual img{width:100%;height:auto}.o_visual .o_visual_not_available{width:100%;height:100%;background-image:url("../light/images/no_preview.png");background-repeat:no-repeat;background-position:50% 50%;background-size:contain}.o_coursetable.o_rendertype_custom .o_table_row{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_coursetable.o_rendertype_custom .o_table_row .o_visual{border-right:1px solid #337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_access{position:absolute;top:0;right:0;height:120px;width:180px;overflow:hidden;border-left:1px solid #337ab7;padding-top:0.25em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{padding:0 1em;height:20px;line-height:20px;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score{position:relative;left:2px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score .o_label{color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social{position:absolute;width:100%;bottom:32px;height:20px;padding-left:1em}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_title,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating o_rating_legend,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_rating .o_rating_explanation{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings{padding:0 0 0 1em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label{margin-bottom:1em;color:#777}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_methods{color:#5bc0de}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{position:absolute;display:block;bottom:0;width:90px;height:30px;line-height:30px;text-align:center}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{right:0}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start{color:#fff;background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start: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_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active{background-color:#337ab7;border-color:#2e6da4}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start .badge{color:#337ab7;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book: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_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book.active{background-color:#f0ad4e;border-color:#eea236}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book .badge{color:#f0ad4e;background-color:#fff}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:90px;color:#fff;background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active,.open>.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.dropdown-toggle{background-image:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.disabled.active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled],.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled]:active,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details[disabled].active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.focus,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details.active{background-color:#5cb85c;border-color:#4cae4c}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details .badge{color:#5cb85c;background-color:#fff}@media (min-width: 768px) and (max-width: 991px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{height:80px;width:120px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_comments,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_label{display:none}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{width:60px}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details{right:60px}}@media (max-width: 767px){.o_coursetable.o_rendertype_custom .o_table_row .o_access{display:none}}.o_coursetable.o_rendertype_custom .o_table_row .o_meta{height:120px;margin:0 180px 0 180px;position:relative;padding:1em 0.5em 0.25em 1em;overflow:hidden}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title{margin:0;position:relative;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a{display:block;color:#337ab7}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover{color:#286090}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_author{margin-top:0.5em;line-height:1em;font-size:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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:#337ab7}.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 #337ab7;border-bottom:1px solid #337ab7}.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:#337ab7}.o_catalog .o_level .o_meta .o_title a:hover{color:#286090}.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_list .o_sublevel{position:relative;border:1px solid #337ab7;margin-bottom:10px}.o_catalog .o_sublevels_list .o_sublevel .o_visual{height:75px;width:75px}.o_catalog .o_sublevels_list .o_sublevel .o_title{margin:0}.o_catalog .o_sublevels_list .o_sublevel .o_meta{border-left:1px solid #337ab7;min-height:75px;height:75px;margin:0 0 0 75px;padding:0 0 0 1em;overflow:hidden}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_title{line-height:75px}.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_desc{display:none}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a{font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels_list .o_sublevel .o_meta h4.o_title>a>i,.o_catalog .o_sublevels_list .o_sublevel .o_meta .o_cal .fc-header-title h2.o_title>a>i,.o_cal .fc-header-title .o_catalog .o_sublevels_list .o_sublevel .o_meta h2.o_title>a>i{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 #337ab7;position:relative;height:180px}.o_catalog .o_sublevels .o_sublevel .o_meta{position:absolute;left:0;bottom:0;width:100%;border:1px solid #337ab7;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:#337ab7;font-family:inherit;font-weight:inherit}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover{color:#286090}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a>i{display:none}@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%}}@media (min-width: 768px){.o_catalog .o_sublevels_list,.o_catalog .o_sublevels_compact{-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2;columns:2}} +.o_repo_details{position:relative}.o_repo_details .o_lead{margin-bottom:10px}.o_repo_details .o_lead .o_author{margin-top:0.5em;margin-bottom:1em;font-size:120%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#3c763d}.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-left:0;float:none;text-align:center}}@media (max-width: 613px){.o_repo_details .o_subcolumn{width:100%}} +.o_meta .o_closed{padding:2px 5px;margin:5px 0}.o_overview .o_closed{padding:12px 15px;margin:15px 0}.o_ac_configuration span.o_ac_infos{font-weight:normal;color:grey}.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_tree.o_course_menu div.o_tree_l0>a:first-child{background-color: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_ll_container h5{margin-bottom:5px}.o_ll_container h5 a.o_desc{color:#337ab7}.o_ll_container h5 a.o_desc small{display:none}.o_ll_container h5 a.o_desc:hover{color:#286090;text-decoration:none}.o_ll_container h5 a.o_desc:hover small{color:#5e5e5e;display:inline}.o_ll_container div.o_comment{color:#777}.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,div.o_qti_menu_section_clickable,div.o_qti_menu_section_active{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:#337ab7}.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.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%;border-top:50px solid transparent;border-bottom:70px solid transparent;background:url("../light/images/learn-bg.jpg");background-size:cover;background-position:center center;background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=1 )}body.o_dmz #o_bg:after{content:" ";position:absolute;top:0;left:0;width:100%;height:100%;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%);background-size:cover;background-position:center center;background-repeat:no-repeat}body.o_dmz #o_toplink{display:none}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:#337ab7}.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:#337ab7}.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_providers .o_icon_provider_olat{font-size:1em}.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.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.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].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.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 .btn-default.o_sel_auth_facebook{color:#fff;background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{color:#fff;background-color:#37538d;border-color:#2d4374}.o_login .o_login_social .btn-default.o_sel_auth_facebook:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_facebook.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled],.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_facebook[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_facebook.active{background-color:#4568b2;border-color:#3e5da0}.o_login .o_login_social .btn-default.o_sel_auth_facebook .badge{color:#4568b2;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_twitter{color:#fff;background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{color:#fff;background-color:#00b4f8;border-color:#009ad4}.o_login .o_login_social .btn-default.o_sel_auth_twitter:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_twitter.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled],.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_twitter[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_twitter.active{background-color:#2cc5ff;border-color:#13beff}.o_login .o_login_social .btn-default.o_sel_auth_twitter .badge{color:#2cc5ff;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_google{color:#fff;background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google:hover,.o_login .o_login_social .btn-default.o_sel_auth_google:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.focus,.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{color:#fff;background-color:#d83825;border-color:#ba3120}.o_login .o_login_social .btn-default.o_sel_auth_google:active,.o_login .o_login_social .btn-default.o_sel_auth_google.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_google.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_google.disabled,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_google.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled],.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_google[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_google.active{background-color:#e15f4f;border-color:#dd4b39}.o_login .o_login_social .btn-default.o_sel_auth_google .badge{color:#e15f4f;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_linkedin{color:#fff;background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{color:#fff;background-color:#015e8a;border-color:#014667}.o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_linkedin.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled],.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_linkedin[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_linkedin.active{background-color:#0181bd;border-color:#0170a4}.o_login .o_login_social .btn-default.o_sel_auth_linkedin .badge{color:#0181bd;background-color:#fff}.o_login .o_login_social .btn-default.o_sel_auth_adfs{color:#fff;background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{color:#fff;background-color:#000;border-color:#000}.o_login .o_login_social .btn-default.o_sel_auth_adfs:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.active,.open>.o_login .o_login_social .btn-default.o_sel_auth_adfs.dropdown-toggle{background-image:none}.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs.disabled.active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled],.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:hover,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].focus,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled]:active,.o_login .o_login_social .btn-default.o_sel_auth_adfs[disabled].active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:hover,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.focus,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs:active,fieldset[disabled] .o_login .o_login_social .btn-default.o_sel_auth_adfs.active{background-color:#1a1a1a;border-color:#0d0d0d}.o_login .o_login_social .btn-default.o_sel_auth_adfs .badge{color:#1a1a1a;background-color:#fff}@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 #337ab7 !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:#337ab7;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:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ui-dialog .ui-widget-header .ui-dialog-titlebar-close span{display:none}.ui-dialog .ui-widget-header .ui-button.ui-corner-all{border:none !important;background:#fff !important;float:right}.ui-dialog .ui-widget-content{border-color:#fff;padding:5px;overflow:auto;background:white !important}.ui-dialog .ui-dialog-titlebar{padding:4px 7px 4px 7px;background-color:#eee !important}.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 #2e6da4;background:#337ab7;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}.progress{-webkit-print-color-adjust:exact;background-color:rgba(0,0,0,0.1) !important;border:1px solid rgba(0,0,0,0.5)}.progress-bar{-webkit-print-color-adjust:exact;background-color:#000 !important;border:10px solid #000}}body.o_browser_ie7 #o_offcanvas_right,body.o_browser_ie8 #o_offcanvas_right{right:0px} \ No newline at end of file diff --git a/src/test/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAOTest.java b/src/test/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAOTest.java new file mode 100644 index 00000000000..6974cc58e3f --- /dev/null +++ b/src/test/java/org/olat/commons/calendar/manager/CalendarUserConfigurationDAOTest.java @@ -0,0 +1,128 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.commons.calendar.manager; + +import java.util.List; +import java.util.UUID; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.model.CalendarUserConfiguration; +import org.olat.commons.calendar.model.Kalendar; +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 25.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CalendarUserConfigurationDAOTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private CalendarUserConfigurationDAO calendarDao; + + @Test + public void createConfiguration() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Cal-1"); + String calendarId = UUID.randomUUID().toString(); + Kalendar calendar = new Kalendar(calendarId, CalendarManager.TYPE_USER); + + CalendarUserConfiguration config = calendarDao.createCalendarUserConfiguration(calendar, user); + dbInstance.commit(); + Assert.assertNotNull(config); + Assert.assertNotNull(config.getKey()); + Assert.assertNotNull(config.getCreationDate()); + Assert.assertNotNull(config.getLastModified()); + Assert.assertEquals(calendarId, config.getCalendarId()); + Assert.assertEquals(CalendarManager.TYPE_USER, config.getType()); + Assert.assertEquals(user, config.getIdentity()); + } + + @Test + public void getCalendarUserConfigurations() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Cal-2"); + String calendarId = UUID.randomUUID().toString(); + Kalendar calendar = new Kalendar(calendarId, CalendarManager.TYPE_COURSE); + + CalendarUserConfiguration config = calendarDao.createCalendarUserConfiguration(calendar, user); + dbInstance.commit(); + Assert.assertNotNull(config); + + //retrieve + List<CalendarUserConfiguration> configList = calendarDao.getCalendarUserConfigurations(user); + Assert.assertNotNull(configList); + Assert.assertEquals(1, configList.size()); + Assert.assertEquals(config, configList.get(0)); + //paranoia check + CalendarUserConfiguration loadedConfig = configList.get(0); + Assert.assertNotNull(loadedConfig.getCreationDate()); + Assert.assertNotNull(loadedConfig.getLastModified()); + Assert.assertEquals(config.getKey(), loadedConfig.getKey()); + Assert.assertEquals(calendarId, loadedConfig.getCalendarId()); + Assert.assertEquals(CalendarManager.TYPE_COURSE, loadedConfig.getType()); + Assert.assertEquals(user, loadedConfig.getIdentity()); + Assert.assertTrue(loadedConfig.isVisible()); + Assert.assertTrue(loadedConfig.isInAggregatedFeed()); + } + + @Test + public void getCalendarUserConfigurations_byTypes() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Cal-3"); + String calendarId = UUID.randomUUID().toString(); + + Kalendar courseCalendar = new Kalendar(calendarId, CalendarManager.TYPE_COURSE); + CalendarUserConfiguration courseCalConfig = calendarDao.createCalendarUserConfiguration(courseCalendar, user); + Kalendar groupCalendar = new Kalendar(calendarId, CalendarManager.TYPE_GROUP); + CalendarUserConfiguration groupCalConfig = calendarDao.createCalendarUserConfiguration(groupCalendar, user); + Kalendar personalCalendar = new Kalendar(user.getName(), CalendarManager.TYPE_USER); + CalendarUserConfiguration personalCalConfig = calendarDao.createCalendarUserConfiguration(personalCalendar, user); + dbInstance.commit(); + Assert.assertNotNull(courseCalConfig); + + //get all + List<CalendarUserConfiguration> configList = calendarDao.getCalendarUserConfigurations(user); + Assert.assertNotNull(configList); + Assert.assertEquals(3, configList.size()); + Assert.assertTrue(configList.contains(courseCalConfig)); + Assert.assertTrue(configList.contains(groupCalConfig)); + Assert.assertTrue(configList.contains(personalCalConfig)); + + //get course + List<CalendarUserConfiguration> courseConfigList = calendarDao.getCalendarUserConfigurations(user, CalendarManager.TYPE_COURSE); + Assert.assertNotNull(courseConfigList); + Assert.assertEquals(1, courseConfigList.size()); + Assert.assertTrue(courseConfigList.contains(courseCalConfig)); + + //null check + List<CalendarUserConfiguration> nullConfigList = calendarDao.getCalendarUserConfigurations(user, (String)null); + Assert.assertNotNull(nullConfigList); + Assert.assertEquals(3, nullConfigList.size()); + } + + +} diff --git a/src/test/java/org/olat/commons/calendar/ICalFileCalendarManagerTest.java b/src/test/java/org/olat/commons/calendar/manager/ICalFileCalendarManagerTest.java similarity index 63% rename from src/test/java/org/olat/commons/calendar/ICalFileCalendarManagerTest.java rename to src/test/java/org/olat/commons/calendar/manager/ICalFileCalendarManagerTest.java index ca87d94aa87..4bed54ba38c 100644 --- a/src/test/java/org/olat/commons/calendar/ICalFileCalendarManagerTest.java +++ b/src/test/java/org/olat/commons/calendar/manager/ICalFileCalendarManagerTest.java @@ -24,7 +24,7 @@ * <p> */ -package org.olat.commons.calendar; +package org.olat.commons.calendar.manager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -32,6 +32,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -41,6 +43,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Random; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -49,6 +52,9 @@ import org.apache.commons.io.FileUtils; import org.infinispan.manager.EmbeddedCacheManager; import org.junit.Assert; import org.junit.Test; +import org.olat.commons.calendar.CalendarImportTest; +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.CalendarUtils; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; @@ -61,12 +67,19 @@ import org.olat.core.util.coordinate.Cacher; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +import net.fortuna.ical4j.data.CalendarOutputter; +import net.fortuna.ical4j.model.ValidationException; public class ICalFileCalendarManagerTest extends OlatTestCase { private static final OLog log = Tracing.createLoggerFor(ICalFileCalendarManagerTest.class); + @Autowired + private ICalFileCalendarManager calendarManager; + private final void emptyCalendarCache() { CoordinatorManager coordinator = CoreSpringFactory.getImpl(CoordinatorManager.class); Cacher cacher = coordinator.getCoordinator().getCacher(); @@ -79,20 +92,18 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("ical-1-"); String TEST_EVENT_ID = "id-testAddEvent"; - CalendarManager manager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = manager.getPersonalCalendar(test).getKalendar(); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); // 1. Test Add Event Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MILLISECOND, 0); Date start = calendar.getTime(); KalendarEvent testEvent = new KalendarEvent(TEST_EVENT_ID, "testEvent", start, 60 * 60 * 1000);// 1 hour - manager.addEventTo(cal, testEvent); + calendarManager.addEventTo(cal, testEvent); // set manager null to force reload of calendar from file-system emptyCalendarCache(); - manager = CalendarManagerFactory.getInstance().getCalendarManager(); - cal = manager.getPersonalCalendar(test).getKalendar(); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent reloadedEvent = cal.getEvent(TEST_EVENT_ID); Assert.assertNotNull("Could not found added event", reloadedEvent); Assert.assertEquals("Added event has wrong subject", testEvent.getSubject(), reloadedEvent.getSubject()); @@ -109,12 +120,11 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Date updatedEnd = calendar.getTime(); reloadedEvent.setSubject("testEvent changed"); reloadedEvent.setEnd(updatedEnd); - manager.updateEventFrom(cal, reloadedEvent); + calendarManager.updateEventFrom(cal, reloadedEvent); // set manager null to force reload of calendar from file-system emptyCalendarCache(); - manager = CalendarManagerFactory.getInstance().getCalendarManager(); - cal = manager.getPersonalCalendar(test).getKalendar(); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent updatedEvent = cal.getEvent(TEST_EVENT_ID); Assert.assertNotNull("Could not found updated event", updatedEvent); Assert.assertEquals("Added event has wrong subject", reloadedEvent.getSubject(), updatedEvent.getSubject()); @@ -122,10 +132,9 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Assert.assertEquals(updatedEnd, reloadedEvent.getEnd()); // 3. Test Remove event - manager.removeEventFrom(cal, updatedEvent); + calendarManager.removeEventFrom(cal, updatedEvent); emptyCalendarCache(); - manager = CalendarManagerFactory.getInstance().getCalendarManager(); - cal = manager.getPersonalCalendar(test).getKalendar(); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent removedEvent = cal.getEvent(TEST_EVENT_ID); assertNull("Found removed event", removedEvent); } @@ -135,8 +144,7 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("ical-1-"); String TEST_EVENT_ID = "id-testAddEvent"; - CalendarManager manager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = manager.getPersonalCalendar(test).getKalendar(); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); // 1. Test Add Event Calendar calendar = Calendar.getInstance(); @@ -145,13 +153,12 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { calendar.add(Calendar.HOUR_OF_DAY, 1); Date end = calendar.getTime(); KalendarEvent testEvent = new KalendarEvent(TEST_EVENT_ID, "testEvent", start, end); - manager.addEventTo(cal, testEvent); + calendarManager.addEventTo(cal, testEvent); //empty the cache emptyCalendarCache(); - manager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar reloadedCal = manager.getPersonalCalendar(test).getKalendar(); + Kalendar reloadedCal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent reloadedEvent = reloadedCal.getEvent(TEST_EVENT_ID); Assert.assertNotNull("Could not found added event", reloadedEvent); Assert.assertEquals("Added event has wrong subject", testEvent.getSubject(), reloadedEvent.getSubject()); @@ -166,13 +173,12 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { reloadedEvent.setSubject("testEvent changed"); reloadedEvent.setBegin(updatedStart); reloadedEvent.setEnd(updatedEnd); - manager.updateEventFrom(cal, reloadedEvent); + calendarManager.updateEventFrom(cal, reloadedEvent); //empty the cache emptyCalendarCache(); - manager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar updatedCal = manager.getPersonalCalendar(test).getKalendar(); + Kalendar updatedCal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent updatedEvent = updatedCal.getEvent(TEST_EVENT_ID); Assert.assertNotNull("Could not found updated event", updatedEvent); Assert.assertEquals("Added event has wrong subject", "testEvent changed", updatedEvent.getSubject()); @@ -186,9 +192,8 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { */ @Test public void testTodayEvent() throws IOException { - Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("ical-3-"); - CalendarManager manager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = manager.getPersonalCalendar(test).getKalendar(); + Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("ical-3-"); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); // 1. Test Today Event String eventId = "today-" + UUID.randomUUID(); @@ -201,7 +206,7 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { calendar.set(Calendar.HOUR_OF_DAY, 12); Date end = calendar.getTime(); KalendarEvent testEvent = new KalendarEvent(eventId, "Today Event", start, end); - manager.addEventTo(cal, testEvent); + calendarManager.addEventTo(cal, testEvent); //Next days event String nextEventId = "next-" + UUID.randomUUID(); @@ -215,13 +220,12 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { calendar.set(Calendar.HOUR_OF_DAY, 12); Date nextEnd = calendar.getTime(); KalendarEvent nextEvent = new KalendarEvent(nextEventId, "Next Event", nextStart, nextEnd); - manager.addEventTo(cal, nextEvent); + calendarManager.addEventTo(cal, nextEvent); //2. reload and test - emptyCalendarCache(); - manager = CalendarManagerFactory.getInstance().getCalendarManager(); - KalendarEvent reloadedEvent = manager.getPersonalCalendar(test).getKalendar().getEvent(eventId); + emptyCalendarCache(); + KalendarEvent reloadedEvent = calendarManager.getPersonalCalendar(test).getKalendar().getEvent(eventId); Assert.assertNotNull(reloadedEvent); Assert.assertEquals("Today Event", reloadedEvent.getSubject()); Assert.assertEquals(start, reloadedEvent.getBegin()); @@ -230,7 +234,7 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Assert.assertTrue(reloadedEvent.isWithinOneDay()); Assert.assertFalse(reloadedEvent.isAllDayEvent()); - KalendarEvent reloadedNextEvent = manager.getPersonalCalendar(test).getKalendar().getEvent(nextEventId); + KalendarEvent reloadedNextEvent = calendarManager.getPersonalCalendar(test).getKalendar().getEvent(nextEventId); Assert.assertNotNull(reloadedNextEvent); Assert.assertEquals("Next Event", reloadedNextEvent.getSubject()); Assert.assertEquals(nextStart, reloadedNextEvent.getBegin()); @@ -242,9 +246,8 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { @Test public void testWithinOneDay() throws IOException { - Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("ical-4-"); - CalendarManager manager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = manager.getPersonalCalendar(test).getKalendar(); + Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("ical-4-"); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); // 1. Test Today Event String eventId = "short-" + UUID.randomUUID(); @@ -257,7 +260,7 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { calendar.set(Calendar.HOUR_OF_DAY, 15); Date end = calendar.getTime(); KalendarEvent testEvent = new KalendarEvent(eventId, "Short Event", start, end); - manager.addEventTo(cal, testEvent); + calendarManager.addEventTo(cal, testEvent); //Next days event String nextEventId = "long-" + UUID.randomUUID(); @@ -277,13 +280,12 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { calendar.set(Calendar.HOUR_OF_DAY, 18); Date nextEnd = calendar.getTime(); KalendarEvent nextEvent = new KalendarEvent(nextEventId, "Long Event", nextStart, nextEnd); - manager.addEventTo(cal, nextEvent); + calendarManager.addEventTo(cal, nextEvent); //2. reload and test - emptyCalendarCache(); - manager = CalendarManagerFactory.getInstance().getCalendarManager(); - KalendarEvent reloadedEvent = manager.getPersonalCalendar(test).getKalendar().getEvent(eventId); + emptyCalendarCache(); + KalendarEvent reloadedEvent = calendarManager.getPersonalCalendar(test).getKalendar().getEvent(eventId); Assert.assertNotNull(reloadedEvent); Assert.assertEquals("Short Event", reloadedEvent.getSubject()); Assert.assertEquals(start, reloadedEvent.getBegin()); @@ -292,7 +294,7 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Assert.assertTrue(reloadedEvent.isWithinOneDay()); Assert.assertFalse(reloadedEvent.isAllDayEvent()); - KalendarEvent reloadedNextEvent = manager.getPersonalCalendar(test).getKalendar().getEvent(nextEventId); + KalendarEvent reloadedNextEvent = calendarManager.getPersonalCalendar(test).getKalendar().getEvent(nextEventId); Assert.assertNotNull(reloadedNextEvent); Assert.assertEquals("Long Event", reloadedNextEvent.getSubject()); Assert.assertEquals(nextStart, reloadedNextEvent.getBegin()); @@ -310,8 +312,7 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { public void testPersistCalendarWithoutDTEndEvent() throws IOException { //replace the standard calendar with a forged one Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cal-test-1-"); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - File calendarFile = calManager.getCalendarFile("user", identity.getName()); + File calendarFile = calendarManager.getCalendarFile("user", identity.getName()); if(calendarFile.exists()) { calendarFile.delete(); } @@ -321,7 +322,7 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { //to be sure emptyCalendarCache(); //load the calendar - KalendarRenderWrapper reloadCalWrapper = calManager.getPersonalCalendar(identity); + KalendarRenderWrapper reloadCalWrapper = calendarManager.getPersonalCalendar(identity); //check if its the right calendar Collection<KalendarEvent> events = reloadCalWrapper.getKalendar().getEvents(); Assert.assertNotNull(events); @@ -334,10 +335,252 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Assert.assertFalse(event.isAllDayEvent()); //test persist - boolean allOk = calManager.persistCalendar(reloadCalWrapper.getKalendar()); + boolean allOk = calendarManager.persistCalendar(reloadCalWrapper.getKalendar()); Assert.assertTrue(allOk); } + @Test + public void testListEventsForPeriod() { + final int numEvents = 10000; + final int maxEventDuratio = 1000 * 60 * 60 * 24 * 14; // maximum of 14 days duration + final int oneYearSec = 60 * 60 * 24 * 365; + final int goBackNumYears = 1; + final long kalendarStart = new Date().getTime() - (((long)goBackNumYears * oneYearSec) * 1000); + + Identity test = JunitTestHelper.createAndPersistIdentityAsUser("test"); + Kalendar kalendar = new Kalendar("test", CalendarManager.TYPE_USER); + + log.info("*** Starting test with the following configuration:"); + log.info("*** Number of events: " + numEvents); + log.info("*** Maximum event duration (ms): " + maxEventDuratio); + log.info("*** Generate events in between " + + new Date(kalendarStart) + " and " + + new Date(kalendarStart + (1000 * ((long)goBackNumYears * oneYearSec)))); + + Random rand = new Random(); + long startUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + for (int i = 0; i < numEvents; i++) { + long begin = kalendarStart + (1000 * ((long)rand.nextInt(goBackNumYears * oneYearSec))); + KalendarEvent event = new KalendarEvent("id" + i, "test" + i, new Date(begin), rand.nextInt(maxEventDuratio)); + kalendar.addEvent(event); + } + long stopUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + log.info("*** SETUP: Kalendar structure uses approx. " + (stopUsed - startUsed) / 1024 + " kb memory."); + calendarManager.persistCalendar(kalendar); + + + log.info("*** Load calendar..."); + long start = System.currentTimeMillis(); + calendarManager.getPersonalCalendar(test).getKalendar(); + long stop = System.currentTimeMillis(); + log.info("Duration load: " + (stop - start) + " ms."); + + log.info("*** Find events within period..."); + start = System.currentTimeMillis(); + List<KalendarEvent> events = CalendarUtils.listEventsForPeriod(kalendar, new Date(kalendarStart), new Date(kalendarStart + (1000 * ((long)(goBackNumYears * oneYearSec))) )); + stop = System.currentTimeMillis(); + log.info("Duration find: " + (stop - start) + " ms."); + log.info("Found " + events.size() + " events out of " + kalendar.getEvents().size() + " total events."); + assertEquals(kalendar.getEvents().size(), events.size()); + + log.info("*** Save calendar..."); + start = System.currentTimeMillis(); + calendarManager.persistCalendar(kalendar); + stop = System.currentTimeMillis(); + log.info("Duration save: " + (stop - start) + " ms."); + } + + /** + * Check a NPE + * @throws IOException + */ + @Test + public void testListEventsForPeriodWithoutDTEndEvent() throws IOException { + //replace the standard calendar with a forged one + Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cal-test-1-"); + File calendarFile = calendarManager.getCalendarFile("user", identity.getName()); + if(calendarFile.exists()) { + calendarFile.delete(); + } + File newCalendarFile = new File(calendarFile.getParentFile(), calendarFile.getName()); + InputStream in = CalendarImportTest.class.getResourceAsStream("cal_without_dtend.ics"); + FileUtils.copyInputStreamToFile(in, newCalendarFile); + //to be sure + emptyCalendarCache(); + //load the calendar + KalendarRenderWrapper reloadCalWrapper = calendarManager.getPersonalCalendar(identity); + //check if its the right calendar + Collection<KalendarEvent> events = reloadCalWrapper.getKalendar().getEvents(); + Assert.assertNotNull(events); + Assert.assertEquals(1, events.size()); + KalendarEvent event = events.iterator().next(); + Assert.assertEquals("Arbeitszeit: 1-3h", event.getSubject()); + Assert.assertEquals("e73iiu9masoddi4g0vllmi2ht0@google.com", event.getID()); + Assert.assertNull(event.getEnd()); + + + //test persist + boolean allOk = calendarManager.persistCalendar(reloadCalWrapper.getKalendar()); + Assert.assertTrue(allOk); + + //an other possible RS + //within period + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, 2010); + cal.set(Calendar.MONTH, 3); + cal.set(Calendar.DATE, 15); + Date periodStart = cal.getTime(); + + cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, 2010); + cal.set(Calendar.MONTH, 3); + cal.set(Calendar.DATE, 17); + Date periodEnd = cal.getTime(); + List<KalendarEvent> eventsForPeriod = CalendarUtils.listEventsForPeriod(reloadCalWrapper.getKalendar(), periodStart, periodEnd); + Assert.assertNotNull(eventsForPeriod); + Assert.assertEquals(1, eventsForPeriod.size()); + KalendarEvent eventForPeriod = events.iterator().next(); + Assert.assertEquals("e73iiu9masoddi4g0vllmi2ht0@google.com", eventForPeriod.getID()); + + //out of scope + cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, 2008); + cal.set(Calendar.MONTH, 3); + cal.set(Calendar.DATE, 15); + Date periodStart2 = cal.getTime(); + + cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, 2008); + cal.set(Calendar.MONTH, 3); + cal.set(Calendar.DATE, 17); + Date periodEnd2 = cal.getTime(); + List<KalendarEvent> eventsOutOfPeriod = CalendarUtils.listEventsForPeriod(reloadCalWrapper.getKalendar(), periodStart2, periodEnd2); + Assert.assertNotNull(eventsOutOfPeriod); + Assert.assertTrue(eventsOutOfPeriod.isEmpty()); + } + + @Test + public void synchronizeCalendarFrom() throws ValidationException, IOException { + Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("s1-"); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); + + String eventId1 = "id-not-managed-event"; + + // 1. Add a standard event, not managed + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.MILLISECOND, 0); + Date start = calendar.getTime(); + calendar.add(Calendar.HOUR_OF_DAY, 1); + Date end = calendar.getTime(); + KalendarEvent notManagedEvent = new KalendarEvent(eventId1, "testEvent", start, end); + calendarManager.addEventTo(cal, notManagedEvent); + + // 2. Synchronize with a first calendar + Kalendar baseCalendar = calendarManager.createCalendar("user", "first-sync"); + String eventIdManaged1 = "managed-event-1"; + KalendarEvent managedEvent1 = new KalendarEvent(eventIdManaged1, "managedEvent", start, end); + baseCalendar.addEvent(managedEvent1); + String eventIdManaged2 = "managed-event-2"; + KalendarEvent managedEvent2 = new KalendarEvent(eventIdManaged2, "managedEvent", start, end); + baseCalendar.addEvent(managedEvent2); + + ByteArrayOutputStream os1 = new ByteArrayOutputStream(); + new CalendarOutputter(false).output(calendarManager.buildCalendar(baseCalendar), os1); + + InputStream in1 = new ByteArrayInputStream(os1.toByteArray()); + calendarManager.synchronizeCalendarFrom(in1, "http://localhost:8080/unittest", cal); + in1.close(); + + // 3. Synchronize with a second calendar + Kalendar resyncCalendar = calendarManager.createCalendar("user", "first-sync"); + KalendarEvent managedEvent1Alt = new KalendarEvent(eventIdManaged1, "managedEvent resync", start, end); + resyncCalendar.addEvent(managedEvent1Alt); + String eventIdManaged3 = "managed-event-3"; + KalendarEvent managedEvent3 = new KalendarEvent(eventIdManaged3, "managedEvent 3", start, end); + resyncCalendar.addEvent(managedEvent3); + + ByteArrayOutputStream os2 = new ByteArrayOutputStream(); + new CalendarOutputter(false).output(calendarManager.buildCalendar(resyncCalendar), os2); + + InputStream in2 = new ByteArrayInputStream(os2.toByteArray()); + calendarManager.synchronizeCalendarFrom(in2, "http://localhost:8080/unittest", cal); + in2.close(); + + emptyCalendarCache(); + //check + Kalendar synchedCal = calendarManager.getPersonalCalendar(test).getKalendar(); + + KalendarEvent notManagedEvent1 = synchedCal.getEvent(eventId1); + Assert.assertNotNull(notManagedEvent1); + Assert.assertEquals("testEvent", notManagedEvent1.getSubject()); + + KalendarEvent event1 = synchedCal.getEvent(eventIdManaged1); + Assert.assertNotNull(event1); + Assert.assertEquals("managedEvent resync", event1.getSubject()); + + KalendarEvent event2 = synchedCal.getEvent(eventIdManaged2); + Assert.assertNull(event2); + + KalendarEvent event3 = synchedCal.getEvent(eventIdManaged3); + Assert.assertNotNull(event3); + Assert.assertEquals("managedEvent 3", event3.getSubject()); + } + + @Test + public void updateCalendar() throws ValidationException, IOException { + Identity test = JunitTestHelper.createAndPersistIdentityAsRndUser("u1-"); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); + + String eventIdMarker = "id-marker-event"; + + // 1. Add a standard event, not managed + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.MILLISECOND, 0); + Date start = calendar.getTime(); + calendar.add(Calendar.HOUR_OF_DAY, 1); + Date end = calendar.getTime(); + KalendarEvent markerEvent = new KalendarEvent(eventIdMarker, "markerEvent", start, end); + calendarManager.addEventTo(cal, markerEvent); + + // 2. Update with a first calendar + Kalendar baseCalendar = calendarManager.createCalendar("user", "first-update"); + String eventId1 = "event-1"; + KalendarEvent event1 = new KalendarEvent(eventId1, "event 1", start, end); + baseCalendar.addEvent(event1); + String eventId2 = "event-2"; + KalendarEvent event2 = new KalendarEvent(eventId2, "event 2", start, end); + baseCalendar.addEvent(event2); + calendarManager.updateCalendar(cal, baseCalendar); + + // 3. Update with a second calendar + Kalendar updateCalendar = calendarManager.createCalendar("user", "first-update"); + KalendarEvent event1alt = new KalendarEvent(eventId1, "event 1 alt", start, end); + updateCalendar.addEvent(event1alt); + String eventId3 = "event-3"; + KalendarEvent event3 = new KalendarEvent(eventId3, "event 3", start, end); + updateCalendar.addEvent(event3); + calendarManager.updateCalendar(cal, updateCalendar); + + //Check + emptyCalendarCache(); + + Kalendar reloadedCal = calendarManager.getPersonalCalendar(test).getKalendar(); + KalendarEvent marker = reloadedCal.getEvent(eventIdMarker); + Assert.assertNotNull(marker); + Assert.assertEquals("markerEvent", marker.getSubject()); + + KalendarEvent reloaded1 = reloadedCal.getEvent(eventId1); + Assert.assertNotNull(reloaded1); + Assert.assertEquals("event 1 alt", reloaded1.getSubject()); + + KalendarEvent reloaded2 = reloadedCal.getEvent(eventId2); + Assert.assertNotNull(reloaded2); + Assert.assertEquals("event 2", reloaded2.getSubject()); + + KalendarEvent reloaded3 = reloadedCal.getEvent(eventId3); + Assert.assertNotNull(reloaded3); + Assert.assertEquals("event 3", reloaded3.getSubject()); + } /** * Test concurrent add event with two threads and code-point to control concurrency. @@ -361,22 +604,21 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { public void run() { try { // 1. load calendar - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = calManager.getPersonalCalendar(test).getKalendar(); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); // 2. add Event1 => breakpoint hit log.info("testConcurrentAddEvent thread1 addEvent1"); - calManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_1,TEST_EVENT_SUBJECT_1, new Date(), 1)); + calendarManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_1,TEST_EVENT_SUBJECT_1, new Date(), 1)); log.info("testConcurrentAddEvent thread1 addEvent1 DONE"); // 3. check event1 exist - cal = calManager.getPersonalCalendar(test).getKalendar(); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent event1 = cal.getEvent(TEST_EVENT_ID_1); assertNotNull("Did not found event with id=" + TEST_EVENT_ID_1, event1); assertEquals("Wrong calendar-event subject",event1.getSubject(), TEST_EVENT_SUBJECT_1); // 4. sleep 2sec // 5. check event1 still exist (event2 added in meantime) - cal = calManager.getPersonalCalendar(test).getKalendar(); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); event1 = cal.getEvent(TEST_EVENT_ID_1); assertNotNull("Did not found event with id=" + TEST_EVENT_ID_1, event1); assertEquals("Wrong calendar-event subject",event1.getSubject(), TEST_EVENT_SUBJECT_1); @@ -395,20 +637,19 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { public void run() { try { // 1. load calendar - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = calManager.getPersonalCalendar(test).getKalendar(); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); // 3. add Event2 (breakpoint of thread1 blocks) log.info("testConcurrentAddEvent thread2 addEvent2"); - calManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_2,TEST_EVENT_SUBJECT_2, new Date(), 1)); + calendarManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_2,TEST_EVENT_SUBJECT_2, new Date(), 1)); log.info("testConcurrentAddEvent thread1 addEvent2 DONE"); // 4. check event2 exist - cal = calManager.getPersonalCalendar(test).getKalendar(); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent event2 = cal.getEvent(TEST_EVENT_ID_2); assertNotNull("Did not found event with id=" + TEST_EVENT_ID_2, event2); assertEquals("Wrong calendar-event subject",event2.getSubject(), TEST_EVENT_SUBJECT_2); // 5. check event1 exist - cal = calManager.getPersonalCalendar(test).getKalendar(); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent event1 = cal.getEvent(TEST_EVENT_ID_1); assertNotNull("Did not found event with id=" + TEST_EVENT_ID_1, event1); assertEquals("Wrong calendar-event subject",event1.getSubject(), TEST_EVENT_SUBJECT_1); @@ -458,10 +699,9 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { final List<Boolean> statusList = Collections.synchronizedList(new ArrayList<Boolean>(1)); // Generate event for update - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = calManager.getPersonalCalendar(test).getKalendar(); - calManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_2,TEST_EVENT_SUBJECT_2, new Date(), 1)); - cal = calManager.getPersonalCalendar(test).getKalendar(); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); + calendarManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_2,TEST_EVENT_SUBJECT_2, new Date(), 1)); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent event2 = cal.getEvent(TEST_EVENT_ID_2); assertNotNull("Did not found event with id=" + TEST_EVENT_ID_2, event2); assertEquals("Wrong calendar-event subject",event2.getSubject(), TEST_EVENT_SUBJECT_2); @@ -474,7 +714,6 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { public void run() { try { // 1. load calendar - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); Kalendar currentCalendar = calendarManager.getPersonalCalendar(test).getKalendar(); // 2. add Event1 => breakpoint hit @@ -507,7 +746,6 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Thread thread2 = new Thread() { public void run() { try { - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); Kalendar calendar = calendarManager.getPersonalCalendar(test).getKalendar(); // 3. add Event2 (breakpoint of thread1 blocks) @@ -573,10 +811,9 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { final List<Boolean> statusList = Collections.synchronizedList(new ArrayList<Boolean>(1)); // Generate event for update - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - Kalendar cal = calManager.getPersonalCalendar(test).getKalendar(); - calManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_2,TEST_EVENT_SUBJECT_2, new Date(), 1)); - cal = calManager.getPersonalCalendar(test).getKalendar(); + Kalendar cal = calendarManager.getPersonalCalendar(test).getKalendar(); + calendarManager.addEventTo(cal, new KalendarEvent(TEST_EVENT_ID_2,TEST_EVENT_SUBJECT_2, new Date(), 1)); + cal = calendarManager.getPersonalCalendar(test).getKalendar(); KalendarEvent event2 = cal.getEvent(TEST_EVENT_ID_2); assertNotNull("Did not found event with id=" + TEST_EVENT_ID_2, event2); assertEquals("Wrong calendar-event subject",event2.getSubject(), TEST_EVENT_SUBJECT_2); @@ -589,7 +826,6 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { public void run() { try { // 1. load calendar - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); Kalendar calendar = calendarManager.getPersonalCalendar(test).getKalendar(); // 2. add Event1 => breakpoint hit @@ -622,7 +858,6 @@ public class ICalFileCalendarManagerTest extends OlatTestCase { Thread thread2 = new Thread() { public void run() { try { - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); Kalendar calendar = calendarManager.getPersonalCalendar(test).getKalendar(); // 3. add Event2 (breakpoint of thread1 blocks) diff --git a/src/test/java/org/olat/commons/calendar/manager/ImportedCalendarDAOTest.java b/src/test/java/org/olat/commons/calendar/manager/ImportedCalendarDAOTest.java new file mode 100644 index 00000000000..8a1755459fd --- /dev/null +++ b/src/test/java/org/olat/commons/calendar/manager/ImportedCalendarDAOTest.java @@ -0,0 +1,221 @@ +/** + * <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.commons.calendar.manager; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.commons.calendar.model.ImportedCalendar; +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ImportedCalendarDAOTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private ImportedCalendarDAO importedCalendarDao; + + @Test + public void createImportedCalendar() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Imp-cal-1"); + String calendarId = UUID.randomUUID().toString(); + String type = "imported-test"; + String url = "http://www.openolat.org/calendar.ics"; + + ImportedCalendar importedCalendar = importedCalendarDao + .createImportedCalendar(user, "Imported calendar", calendarId, type, url, new Date()); + Assert.assertNotNull(importedCalendar); + dbInstance.commit(); + + Assert.assertNotNull(importedCalendar.getKey()); + Assert.assertNotNull(importedCalendar.getCreationDate()); + Assert.assertNotNull(importedCalendar.getLastModified()); + Assert.assertNotNull(importedCalendar.getLastUpdate()); + + Assert.assertEquals("Imported calendar", importedCalendar.getDisplayName()); + Assert.assertEquals(user, importedCalendar.getIdentity()); + Assert.assertEquals(calendarId, importedCalendar.getCalendarId()); + Assert.assertEquals(type, importedCalendar.getType()); + Assert.assertEquals(url, importedCalendar.getUrl()); + } + + @Test + public void getImportedCalendar() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Imp-cal-2"); + String calendarId = UUID.randomUUID().toString(); + String type = "imported-test-2"; + String url = "http://www.openolat.org/calendar1.ics"; + + ImportedCalendar importedCalendar = importedCalendarDao + .createImportedCalendar(user, "Imported calendar 2", calendarId, type, url, new Date()); + Assert.assertNotNull(importedCalendar); + dbInstance.commit(); + + List<ImportedCalendar> calendars = importedCalendarDao.getImportedCalendar(user, calendarId, type); + Assert.assertNotNull(calendars); + Assert.assertEquals(1, calendars.size()); + Assert.assertTrue(calendars.contains(importedCalendar)); + + //paranoia check + ImportedCalendar reloadedCalendar = calendars.get(0); + Assert.assertNotNull(reloadedCalendar.getCreationDate()); + Assert.assertNotNull(reloadedCalendar.getLastModified()); + Assert.assertNotNull(reloadedCalendar.getLastUpdate()); + + Assert.assertEquals(importedCalendar.getKey(), reloadedCalendar.getKey()); + Assert.assertEquals("Imported calendar 2", reloadedCalendar.getDisplayName()); + Assert.assertEquals(user, reloadedCalendar.getIdentity()); + Assert.assertEquals(calendarId, reloadedCalendar.getCalendarId()); + Assert.assertEquals(type, reloadedCalendar.getType()); + Assert.assertEquals(url, reloadedCalendar.getUrl()); + } + + @Test + public void updateImportedCalendar() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Imp-cal-1"); + String calendarId = UUID.randomUUID().toString(); + String type = "imported-test"; + String url = "http://www.openolat.org/calendar.ics"; + + ImportedCalendar importedCalendar = importedCalendarDao + .createImportedCalendar(user, "Imported calendar up", calendarId, type, url, new Date()); + Assert.assertNotNull(importedCalendar); + dbInstance.commit(); + + //update + importedCalendar.setDisplayName("Imported calendar updated"); + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, 1); + importedCalendar.setLastUpdate(cal.getTime()); + ImportedCalendar updatedCalendar = importedCalendarDao.update(importedCalendar); + dbInstance.commit(); + + //check + Assert.assertEquals(importedCalendar, updatedCalendar); + Assert.assertEquals(importedCalendar.getKey(), updatedCalendar.getKey()); + + Assert.assertTrue(new Date().before(updatedCalendar.getLastUpdate())); + Assert.assertEquals("Imported calendar updated", updatedCalendar.getDisplayName()); + Assert.assertEquals(user, updatedCalendar.getIdentity()); + Assert.assertEquals(calendarId, updatedCalendar.getCalendarId()); + Assert.assertEquals(type, updatedCalendar.getType()); + Assert.assertEquals(url, updatedCalendar.getUrl()); + + //reload and check + List<ImportedCalendar> reloadedCalendars = importedCalendarDao.getImportedCalendar(user, calendarId, type); + Assert.assertNotNull(reloadedCalendars); + Assert.assertEquals(1, reloadedCalendars.size()); + Assert.assertTrue(reloadedCalendars.contains(importedCalendar)); + + //paranoia check + ImportedCalendar reloadedCalendar = reloadedCalendars.get(0); + Assert.assertNotNull(reloadedCalendar.getCreationDate()); + Assert.assertNotNull(reloadedCalendar.getLastModified()); + Assert.assertTrue(new Date().before(reloadedCalendar.getLastUpdate())); + + Assert.assertEquals(importedCalendar.getKey(), reloadedCalendar.getKey()); + Assert.assertEquals("Imported calendar updated", reloadedCalendar.getDisplayName()); + Assert.assertEquals(user, reloadedCalendar.getIdentity()); + Assert.assertEquals(calendarId, reloadedCalendar.getCalendarId()); + Assert.assertEquals(type, reloadedCalendar.getType()); + Assert.assertEquals(url, reloadedCalendar.getUrl()); + } + + @Test + public void getImportedCalendars() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Imp-cal-4"); + String calendarId = UUID.randomUUID().toString(); + ImportedCalendar importedCalendar1 = importedCalendarDao + .createImportedCalendar(user, "Imported calendar 3", calendarId, "rnd-3", null, new Date()); + ImportedCalendar importedCalendar2 = importedCalendarDao + .createImportedCalendar(user, "Imported calendar 4", UUID.randomUUID().toString(), "rnd-4", + "http://www.openolat.org/calendar4.ics", new Date()); + + //noise + Identity noiseUser = JunitTestHelper.createAndPersistIdentityAsRndUser("Imp-cal-4"); + ImportedCalendar noiseImportedCalendar = importedCalendarDao + .createImportedCalendar(noiseUser, "Imported calendar 3", calendarId, "rnd-3", null, new Date()); + + dbInstance.commit(); + + List<ImportedCalendar> importedCalendars = importedCalendarDao.getImportedCalendars(user); + Assert.assertNotNull(importedCalendars); + Assert.assertEquals(2, importedCalendars.size()); + Assert.assertTrue(importedCalendars.contains(importedCalendar1)); + Assert.assertTrue(importedCalendars.contains(importedCalendar2)); + Assert.assertFalse(importedCalendars.contains(noiseImportedCalendar)); + } + + @Test + public void deleteImportedCalendar() { + Identity user = JunitTestHelper.createAndPersistIdentityAsRndUser("Imp-cal-5"); + String calendarId = UUID.randomUUID().toString(); + ImportedCalendar importedCalendar1 = importedCalendarDao + .createImportedCalendar(user, "Imported calendar 5", calendarId, "rnd-3", null, new Date()); + ImportedCalendar importedCalendar2 = importedCalendarDao + .createImportedCalendar(user, "Imported calendar 6", UUID.randomUUID().toString(), "rnd-4", + "http://www.openolat.org/calendar5.ics", new Date()); + + //noise + Identity noiseUser = JunitTestHelper.createAndPersistIdentityAsRndUser("Imp-cal-6"); + ImportedCalendar noiseImportedCalendar = importedCalendarDao + .createImportedCalendar(noiseUser, "Imported calendar 7", calendarId, "rnd-3", null, new Date()); + + dbInstance.commit(); + + //check + List<ImportedCalendar> importedCalendars = importedCalendarDao.getImportedCalendars(user); + Assert.assertEquals(2, importedCalendars.size()); + List<ImportedCalendar> noiseImportedCalendars = importedCalendarDao.getImportedCalendars(noiseUser); + Assert.assertEquals(1, noiseImportedCalendars.size()); + + //delete + importedCalendarDao.deleteImportedCalendar(user, calendarId, "rnd-3"); + dbInstance.commitAndCloseSession(); + + //check the the first imported calendar is really deleted + List<ImportedCalendar> reloadedCalendars = importedCalendarDao.getImportedCalendars(user); + Assert.assertEquals(1, reloadedCalendars.size()); + Assert.assertFalse(reloadedCalendars.contains(importedCalendar1)); + Assert.assertTrue(reloadedCalendars.contains(importedCalendar2)); + Assert.assertFalse(reloadedCalendars.contains(noiseImportedCalendar)); + + //noise must still have its calendar + List<ImportedCalendar> noiseReloadedCalendars = importedCalendarDao.getImportedCalendars(noiseUser); + Assert.assertEquals(1, noiseReloadedCalendars.size()); + Assert.assertFalse(noiseReloadedCalendars.contains(importedCalendar1)); + Assert.assertFalse(noiseReloadedCalendars.contains(importedCalendar2)); + Assert.assertTrue(noiseReloadedCalendars.contains(noiseImportedCalendar)); + } +} diff --git a/src/test/java/org/olat/commons/calendar/manager/ImportedToCalendarDAOTest.java b/src/test/java/org/olat/commons/calendar/manager/ImportedToCalendarDAOTest.java new file mode 100644 index 00000000000..80b9b9d927a --- /dev/null +++ b/src/test/java/org/olat/commons/calendar/manager/ImportedToCalendarDAOTest.java @@ -0,0 +1,111 @@ +/** + * <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.commons.calendar.manager; + +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.commons.calendar.model.ImportedToCalendar; +import org.olat.core.commons.persistence.DB; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 27.08.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ImportedToCalendarDAOTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private ImportedToCalendarDAO importedToCalendarDao; + + @Test + public void createImportedToCalendar() { + String toCalendarId = UUID.randomUUID().toString(); + String toType = "to-rnd-1"; + String url = "http://www.openolat.org/importedto/calendar1.ics"; + ImportedToCalendar importToCalendar = importedToCalendarDao + .createImportedToCalendar(toCalendarId, toType, url, new Date()); + dbInstance.commit(); + + Assert.assertNotNull(importToCalendar); + Assert.assertNotNull(importToCalendar.getKey()); + Assert.assertNotNull(importToCalendar.getCreationDate()); + Assert.assertNotNull(importToCalendar.getLastModified()); + Assert.assertNotNull(importToCalendar.getLastUpdate()); + + Assert.assertEquals(toCalendarId, importToCalendar.getToCalendarId()); + Assert.assertEquals(toType, importToCalendar.getToType()); + Assert.assertEquals(url, importToCalendar.getUrl()); + } + + @Test + public void getImportedToCalendars_byCalendarIdTypeAndUrl() { + String toCalendarId = UUID.randomUUID().toString(); + String toType = "to-rnd-2"; + String url = "http://www.openolat.org/importedto/calendar2.ics"; + ImportedToCalendar importToCalendar = importedToCalendarDao + .createImportedToCalendar(toCalendarId, toType, url, new Date()); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(importToCalendar); + + List<ImportedToCalendar> loadedToCalendars = importedToCalendarDao + .getImportedToCalendars(toCalendarId, toType, url); + Assert.assertNotNull(loadedToCalendars); + Assert.assertEquals(1, loadedToCalendars.size()); + Assert.assertTrue(loadedToCalendars.contains(importToCalendar)); + + //paranoia check + ImportedToCalendar loadedToCalendar = loadedToCalendars.get(0); + Assert.assertEquals(importToCalendar, loadedToCalendar); + Assert.assertEquals(importToCalendar.getKey(), loadedToCalendar.getKey()); + Assert.assertNotNull(loadedToCalendar.getCreationDate()); + Assert.assertNotNull(loadedToCalendar.getLastModified()); + Assert.assertNotNull(loadedToCalendar.getLastUpdate()); + + Assert.assertEquals(toCalendarId, loadedToCalendar.getToCalendarId()); + Assert.assertEquals(toType, loadedToCalendar.getToType()); + Assert.assertEquals(url, loadedToCalendar.getUrl()); + } + + @Test + public void getImportedToCalendars() { + String toCalendarId = UUID.randomUUID().toString(); + String toType = "to-rnd-3"; + String url = "http://www.openolat.org/importedto/calendar3.ics"; + ImportedToCalendar importToCalendar = importedToCalendarDao + .createImportedToCalendar(toCalendarId, toType, url, new Date()); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(importToCalendar); + + //load all calendars + List<ImportedToCalendar> allCalendars = importedToCalendarDao.getImportedToCalendars(); + Assert.assertNotNull(allCalendars); + Assert.assertFalse(allCalendars.isEmpty()); + Assert.assertTrue(allCalendars.contains(importToCalendar)); + } +} diff --git a/src/test/java/org/olat/commons/calendar/test/CalendarUtilsTest.java b/src/test/java/org/olat/commons/calendar/test/CalendarUtilsTest.java deleted file mode 100644 index bef74d4512e..00000000000 --- a/src/test/java/org/olat/commons/calendar/test/CalendarUtilsTest.java +++ /dev/null @@ -1,201 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -* <p> -*/ - -package org.olat.commons.calendar.test; - -import static org.junit.Assert.assertEquals; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Random; - -import org.apache.commons.io.FileUtils; -import org.infinispan.manager.EmbeddedCacheManager; -import org.junit.Assert; -import org.junit.Test; -import org.olat.commons.calendar.CalendarImportTest; -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; -import org.olat.commons.calendar.CalendarUtils; -import org.olat.commons.calendar.ICalFileCalendarManager; -import org.olat.commons.calendar.model.Kalendar; -import org.olat.commons.calendar.model.KalendarEvent; -import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; -import org.olat.core.CoreSpringFactory; -import org.olat.core.id.Identity; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; -import org.olat.core.util.coordinate.Cacher; -import org.olat.core.util.coordinate.CoordinatorManager; -import org.olat.test.JunitTestHelper; -import org.olat.test.OlatTestCase; - -public class CalendarUtilsTest extends OlatTestCase { - - private static final OLog log = Tracing.createLoggerFor(CalendarUtilsTest.class); - - private static final int numEvents = 10000; - private static final int maxEventDuratio = 1000 * 60 * 60 * 24 * 14; // maximum of 14 days duration - private static final int oneYearSec = 60 * 60 * 24 * 365; - private static final int goBackNumYears = 1; - private static long kalendarStart = new Date().getTime() - (((long)goBackNumYears * oneYearSec) * 1000); - - - @Test - public void testListEventsForPeriod() { - Identity test = JunitTestHelper.createAndPersistIdentityAsUser("test"); - Kalendar kalendar = new Kalendar("test", CalendarManager.TYPE_USER); - - log.info("*** Starting test with the following configuration:"); - log.info("*** Number of events: " + numEvents); - log.info("*** Maximum event duration (ms): " + maxEventDuratio); - log.info("*** Generate events in between " - + new Date(kalendarStart) + " and " - + new Date(kalendarStart + (1000 * ((long)goBackNumYears * oneYearSec)))); - - createTestEvents(numEvents, kalendar); - log.info("*** Load calendar..."); - CalendarManager manager = CalendarManagerFactory.getInstance().getCalendarManager(); - long start = System.currentTimeMillis(); - manager.getPersonalCalendar(test).getKalendar(); - long stop = System.currentTimeMillis(); - log.info("Duration load: " + (stop - start) + " ms."); - - log.info("*** Find events within period..."); - start = System.currentTimeMillis(); - List<KalendarEvent> events = CalendarUtils.listEventsForPeriod(kalendar, new Date(kalendarStart), new Date(kalendarStart + (1000 * ((long)(goBackNumYears * oneYearSec))) )); - stop = System.currentTimeMillis(); - log.info("Duration find: " + (stop - start) + " ms."); - log.info("Found " + events.size() + " events out of " + kalendar.getEvents().size() + " total events."); - assertEquals(kalendar.getEvents().size(), events.size()); - - log.info("*** Save calendar..."); - start = System.currentTimeMillis(); - ((ICalFileCalendarManager)manager).persistCalendar(kalendar); - stop = System.currentTimeMillis(); - log.info("Duration save: " + (stop - start) + " ms."); - } - - /** - * Creates a number of events in certain calendar. - * @param numEvents - * @param cal - */ - private void createTestEvents(int numberOfEvents, Kalendar cal) { - Random rand = new Random(); - long startUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); - for (int i = 0; i < numberOfEvents; i++) { - long begin = kalendarStart + (1000 * ((long)rand.nextInt(goBackNumYears * oneYearSec))); - KalendarEvent event = new KalendarEvent("id" + i, "test" + i, new Date(begin), rand.nextInt(maxEventDuratio)); - cal.addEvent(event); - } - long stopUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); - log.info("*** SETUP: Kalendar structure uses approx. " + (stopUsed - startUsed) / 1024 + " kb memory."); - ((ICalFileCalendarManager)CalendarManagerFactory.getInstance().getCalendarManager()).persistCalendar(cal); - } - - /** - * Check a NPE - * @throws IOException - */ - @Test - public void testListEventsForPeriodWithoutDTEndEvent() throws IOException { - //replace the standard calendar with a forged one - Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("cal-test-1-"); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); - File calendarFile = calManager.getCalendarFile("user", identity.getName()); - if(calendarFile.exists()) { - calendarFile.delete(); - } - File newCalendarFile = new File(calendarFile.getParentFile(), calendarFile.getName()); - InputStream in = CalendarImportTest.class.getResourceAsStream("cal_without_dtend.ics"); - FileUtils.copyInputStreamToFile(in, newCalendarFile); - //to be sure - emptyCalendarCache(); - //load the calendar - KalendarRenderWrapper reloadCalWrapper = calManager.getPersonalCalendar(identity); - //check if its the right calendar - Collection<KalendarEvent> events = reloadCalWrapper.getKalendar().getEvents(); - Assert.assertNotNull(events); - Assert.assertEquals(1, events.size()); - KalendarEvent event = events.iterator().next(); - Assert.assertEquals("Arbeitszeit: 1-3h", event.getSubject()); - Assert.assertEquals("e73iiu9masoddi4g0vllmi2ht0@google.com", event.getID()); - Assert.assertNull(event.getEnd()); - - - //test persist - boolean allOk = calManager.persistCalendar(reloadCalWrapper.getKalendar()); - Assert.assertTrue(allOk); - - //an other possible RS - //within period - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.YEAR, 2010); - cal.set(Calendar.MONTH, 3); - cal.set(Calendar.DATE, 15); - Date periodStart = cal.getTime(); - - cal = Calendar.getInstance(); - cal.set(Calendar.YEAR, 2010); - cal.set(Calendar.MONTH, 3); - cal.set(Calendar.DATE, 17); - Date periodEnd = cal.getTime(); - List<KalendarEvent> eventsForPeriod = CalendarUtils.listEventsForPeriod(reloadCalWrapper.getKalendar(), periodStart, periodEnd); - Assert.assertNotNull(eventsForPeriod); - Assert.assertEquals(1, eventsForPeriod.size()); - KalendarEvent eventForPeriod = events.iterator().next(); - Assert.assertEquals("e73iiu9masoddi4g0vllmi2ht0@google.com", eventForPeriod.getID()); - - //out of scope - cal = Calendar.getInstance(); - cal.set(Calendar.YEAR, 2008); - cal.set(Calendar.MONTH, 3); - cal.set(Calendar.DATE, 15); - Date periodStart2 = cal.getTime(); - - cal = Calendar.getInstance(); - cal.set(Calendar.YEAR, 2008); - cal.set(Calendar.MONTH, 3); - cal.set(Calendar.DATE, 17); - Date periodEnd2 = cal.getTime(); - List<KalendarEvent> eventsOutOfPeriod = CalendarUtils.listEventsForPeriod(reloadCalWrapper.getKalendar(), periodStart2, periodEnd2); - Assert.assertNotNull(eventsOutOfPeriod); - Assert.assertTrue(eventsOutOfPeriod.isEmpty()); - } - - private final void emptyCalendarCache() { - CoordinatorManager coordinator = CoreSpringFactory.getImpl(CoordinatorManager.class); - Cacher cacher = coordinator.getCoordinator().getCacher(); - EmbeddedCacheManager cm = cacher.getCacheContainer(); - cm.getCache("CalendarManager@calendar").clear(); - } -} diff --git a/src/test/java/org/olat/restapi/CalendarTest.java b/src/test/java/org/olat/restapi/CalendarTest.java index 97377acf766..7254a87acaa 100644 --- a/src/test/java/org/olat/restapi/CalendarTest.java +++ b/src/test/java/org/olat/restapi/CalendarTest.java @@ -49,7 +49,6 @@ import org.junit.Before; import org.junit.Test; import org.olat.basesecurity.GroupRoles; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.restapi.CalendarVO; import org.olat.commons.calendar.restapi.EventVO; @@ -78,12 +77,14 @@ public class CalendarTest extends OlatJerseyTestCase { private static ICourse course1, course2; private static Identity id1, id2; + @Autowired + private DB dbInstance; + @Autowired + private CalendarManager calendarManager; @Autowired private RepositoryManager repositoryManager; @Autowired private RepositoryService repositoryService; - @Autowired - private DB dbInstance; @Before public void startup() { @@ -104,8 +105,6 @@ public class CalendarTest extends OlatJerseyTestCase { ICourse course = CourseFactory.loadCourse(course1.getResourceableId()); CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig(); Assert.assertTrue(courseConfig.isCalendarEnabled()); - - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course); Calendar cal = Calendar.getInstance(); @@ -140,7 +139,6 @@ public class CalendarTest extends OlatJerseyTestCase { course2 = CoursesWebService.createEmptyCourse(id2, "Cal course - 2", "Cal course - 2", config); dbInstance.commit(); - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course2); Assert.assertNotNull(calendarWrapper); @@ -407,7 +405,6 @@ public class CalendarTest extends OlatJerseyTestCase { EntityUtils.consume(putEventResponse.getEntity()); //check if the event is saved - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course2); Collection<KalendarEvent> savedEvents = calendarWrapper.getKalendar().getEvents(); @@ -490,7 +487,6 @@ public class CalendarTest extends OlatJerseyTestCase { Assert.assertNotNull(newEvent); //check if the event is saved - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course2); Collection<KalendarEvent> savedEvents = calendarWrapper.getKalendar().getEvents(); @@ -538,7 +534,6 @@ public class CalendarTest extends OlatJerseyTestCase { //check if the event is saved - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(id2); Collection<KalendarEvent> savedEvents = calendarWrapper.getKalendar().getEvents(); @@ -559,7 +554,6 @@ public class CalendarTest extends OlatJerseyTestCase { assertTrue(conn.login(id2.getName(), "A6B7C8")); //check if the event is saved - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(id2); KalendarEvent kalEvent = new KalendarEvent(UUID.randomUUID().toString(), "Rendez-vous", new Date(), new Date()); calendarManager.addEventTo(calendarWrapper.getKalendar(), kalEvent); diff --git a/src/test/java/org/olat/restapi/CourseCalendarTest.java b/src/test/java/org/olat/restapi/CourseCalendarTest.java index 15753bbb335..9b821e52c7d 100644 --- a/src/test/java/org/olat/restapi/CourseCalendarTest.java +++ b/src/test/java/org/olat/restapi/CourseCalendarTest.java @@ -55,10 +55,10 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.restapi.EventVO; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.logging.OLog; @@ -86,6 +86,8 @@ public class CourseCalendarTest extends OlatJerseyTestCase { @Autowired private DB dbInstance; + @Autowired + private CalendarManager calendarManager; /** * SetUp is called before each test. @@ -104,8 +106,8 @@ public class CourseCalendarTest extends OlatJerseyTestCase { ICourse course = CourseFactory.loadCourse(course1.getResourceableId()); Assert.assertTrue(course.getCourseConfig().isCalendarEnabled()); - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); - KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course); + CalendarManager calManager = CoreSpringFactory.getImpl(CalendarManager.class); + KalendarRenderWrapper calendarWrapper = calManager.getCourseCalendar(course); Calendar cal = Calendar.getInstance(); for(int i=0; i<2; i++) { @@ -113,7 +115,7 @@ public class CourseCalendarTest extends OlatJerseyTestCase { cal.add(Calendar.HOUR_OF_DAY, 1); Date end = cal.getTime(); KalendarEvent event = new KalendarEvent(UUID.randomUUID().toString(), "Unit test " + i, begin, end); - calendarManager.addEventTo(calendarWrapper.getKalendar(), event); + calManager.addEventTo(calendarWrapper.getKalendar(), event); cal.add(Calendar.DATE, 1); } @@ -163,7 +165,6 @@ public class CourseCalendarTest extends OlatJerseyTestCase { EntityUtils.consume(putEventResponse.getEntity()); //check if the event is saved - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course1); Collection<KalendarEvent> savedEvents = calendarWrapper.getKalendar().getEvents(); @@ -184,7 +185,6 @@ public class CourseCalendarTest extends OlatJerseyTestCase { assertTrue(conn.login(auth1.getName(), "A6B7C8")); //create an event if the event is saved - CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager(); KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course1); Calendar cal = Calendar.getInstance(); diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 5cf77c00e05..b05f154d6a3 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -75,10 +75,12 @@ import org.junit.runners.Suite; org.olat.user.EmailCheckPerformanceTest.class,//fail org.olat.user.UserTest.class, org.olat.user.UserPropertiesTest.class, - org.olat.commons.calendar.ICalFileCalendarManagerTest.class, org.olat.commons.calendar.CalendarImportTest.class, - org.olat.commons.calendar.test.CalendarUtilsTest.class, - org.olat.commons.lifecycle.LifeCycleManagerTest.class,//fail christian fragen... + org.olat.commons.calendar.manager.ImportedCalendarDAOTest.class, + org.olat.commons.calendar.manager.ImportedToCalendarDAOTest.class, + org.olat.commons.calendar.manager.ICalFileCalendarManagerTest.class, + org.olat.commons.calendar.manager.CalendarUserConfigurationDAOTest.class, + org.olat.commons.lifecycle.LifeCycleManagerTest.class, org.olat.commons.coordinate.cluster.jms.JMSTest.class, org.olat.commons.coordinate.cluster.lock.LockTest.class, org.olat.commons.coordinate.CoordinatorTest.class, -- GitLab