diff --git a/src/main/java/org/olat/admin/landingpages/ui/_content/chooser.html b/src/main/java/org/olat/admin/landingpages/ui/_content/chooser.html index f020b64cc075f11f0dbaa0ce3b95aa902df3e3c4..b32fd33bd9bd58d707599fe87ff6cdef51d55194 100644 --- a/src/main/java/org/olat/admin/landingpages/ui/_content/chooser.html +++ b/src/main/java/org/olat/admin/landingpages/ui/_content/chooser.html @@ -1,4 +1,4 @@ -<ul class="list-unstyled"> +<ul class="o_dropdown list-unstyled"> #foreach($link in $links) <li>$r.render($link)</li> #end diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryRuntimeController.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..22d4d9c628649d8b0eb47c2e4ea5b23e6d691573 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryRuntimeController.java @@ -0,0 +1,85 @@ +/** + * <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.core.commons.modules.glossary; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.glossary.GlossaryEditSettingsController; +import org.olat.modules.glossary.GlossaryRegisterSettingsController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.ui.RepositoryEntryRuntimeController; + +/** + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class GlossaryRuntimeController extends RepositoryEntryRuntimeController { + + private Link registerLink, permissionLink; + + public GlossaryRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, runtimeControllerCreator); + } + + @Override + protected void initToolbar(Dropdown toolsDropdown, Dropdown settingsDropdown) { + super.initToolbar(toolsDropdown, settingsDropdown); + + if (isOwner || isInstitutionalResourceManager || isOlatAdmin) { + registerLink = LinkFactory.createToolLink("register", translate("tab.glossary.register"), this, "o_sel_glossary_register"); + settingsDropdown.addComponent(registerLink); + + permissionLink = LinkFactory.createToolLink("permissions", translate("tab.glossary.edit"), this, "o_sel_glossary_permission"); + settingsDropdown.addComponent(permissionLink); + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(registerLink == source) { + doRegister(ureq); + } else if(permissionLink == source) { + doPermission(ureq); + } else { + super.event(ureq, source, event); + } + } + + private void doRegister(UserRequest ureq) { + RepositoryEntry glossary = getRepositoryEntry(); + GlossaryRegisterSettingsController glossRegisterSetCtr + = new GlossaryRegisterSettingsController(ureq, getWindowControl(), glossary.getOlatResource()); + pushController(ureq, translate("tab.glossary.register"), glossRegisterSetCtr); + } + + private void doPermission(UserRequest ureq) { + RepositoryEntry glossary = getRepositoryEntry(); + GlossaryEditSettingsController glossEditCtr + = new GlossaryEditSettingsController(ureq, getWindowControl(), glossary.getOlatResource()); + pushController(ureq, translate("tab.glossary.edit"), glossEditCtr); + } +} diff --git a/src/main/java/org/olat/core/gui/components/dropdown/Dropdown.java b/src/main/java/org/olat/core/gui/components/dropdown/Dropdown.java index a4e2ae10532e3e143511ada0e527362fc81b0cf5..3cffdb5532b556a3c80c1d5b63d6e05fa7b1029b 100644 --- a/src/main/java/org/olat/core/gui/components/dropdown/Dropdown.java +++ b/src/main/java/org/olat/core/gui/components/dropdown/Dropdown.java @@ -93,6 +93,16 @@ public class Dropdown extends AbstractComponent implements ComponentCollection { } } + public void addComponent(int index, Link component) { + if(component != null) { + if(index >= 0 && index < components.size()) { + components.add(index, component); + } else { + components.add(component); + } + } + } + public void addComponent(Spacer spacer) { if(spacer != null) { components.add(spacer); diff --git a/src/main/java/org/olat/core/gui/components/stack/BreadcrumbPanel.java b/src/main/java/org/olat/core/gui/components/stack/BreadcrumbPanel.java index a24c93e77aad22bc267d7725eceff185233736e8..bee6d46208a22215f24df442c5ed180c9ed6978a 100644 --- a/src/main/java/org/olat/core/gui/components/stack/BreadcrumbPanel.java +++ b/src/main/java/org/olat/core/gui/components/stack/BreadcrumbPanel.java @@ -29,6 +29,13 @@ import org.olat.core.gui.control.Controller; */ public interface BreadcrumbPanel extends StackedPanel { + /** + * Dismiss all controller and replace the root + * @param displayName + * @param controller + */ + public void rootController(String displayName, Controller controller); + /** * Dissmiss all controllers but the root */ @@ -36,6 +43,7 @@ public interface BreadcrumbPanel extends StackedPanel { public void pushController(String displayName, Controller controller); + public void popUpToController(Controller controller); } \ No newline at end of file diff --git a/src/main/java/org/olat/core/gui/components/stack/BreadcrumbedStackedPanel.java b/src/main/java/org/olat/core/gui/components/stack/BreadcrumbedStackedPanel.java index 89613e2c8a517a90cf085e5009536568553d3d1f..2d497956132a91cf843c5d7905a3f8f5733fc206 100644 --- a/src/main/java/org/olat/core/gui/components/stack/BreadcrumbedStackedPanel.java +++ b/src/main/java/org/olat/core/gui/components/stack/BreadcrumbedStackedPanel.java @@ -33,6 +33,7 @@ import org.olat.core.gui.components.panel.StackedPanel; 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.VetoableCloseController; import org.olat.core.gui.translator.Translator; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; @@ -171,9 +172,20 @@ public class BreadcrumbedStackedPanel extends Panel implements StackedPanel, Bre } if(stack.contains(source)) { + Controller controllerToPop = getControllerToPop(source); + //part of a hack for QTI editor + if(controllerToPop instanceof VetoableCloseController + && !((VetoableCloseController)controllerToPop).requestForClose()) { + // not my problem anymore, I have done what I can + fireEvent(ureq, new VetoPopEvent()); + return; + } Controller popedCtrl = popController(source); if(popedCtrl != null) { fireEvent(ureq, new PopEvent(popedCtrl)); + } else if(stack.indexOf(source) == 0) { + fireEvent(ureq, new RootEvent()); + } } } @@ -195,7 +207,7 @@ public class BreadcrumbedStackedPanel extends Panel implements StackedPanel, Bre for(int i=stack.size(); i-->(index+1); ) { Link link = stack.remove(i); popedCrumb = (BreadCrumb)link.getUserObject(); - popedCrumb.dispose(); + popedCrumb.getController().dispose(); } Link currentLink = stack.get(index); @@ -221,6 +233,19 @@ public class BreadcrumbedStackedPanel extends Panel implements StackedPanel, Bre return index; } + private Controller getControllerToPop(Component source) { + int index = stack.indexOf(source); + if(index < (stack.size() - 1)) { + BreadCrumb popedCrumb = null; + for(int i=stack.size(); i-->(index+1); ) { + Link link = stack.get(i); + popedCrumb = (BreadCrumb)link.getUserObject(); + } + return popedCrumb.getController(); + } + return null; + } + private Controller popController(Component source) { int index = stack.indexOf(source); if(index < (stack.size() - 1)) { @@ -240,6 +265,19 @@ public class BreadcrumbedStackedPanel extends Panel implements StackedPanel, Bre } return null; } + + @Override + public void rootController(String displayName, Controller controller) { + if(stack.size() > 0) { + for(int i=stack.size(); i-->0; ) { + Link link = stack.remove(i); + BreadCrumb crumb = (BreadCrumb)link.getUserObject(); + crumb.dispose(); + } + } + + pushController(displayName, controller); + } @Override public void popUpToRootController(UserRequest ureq) { diff --git a/src/main/java/org/olat/core/gui/components/stack/RootEvent.java b/src/main/java/org/olat/core/gui/components/stack/RootEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..c447a5f14bc06efbce0cef5a5590ab6f02dcb3f2 --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/stack/RootEvent.java @@ -0,0 +1,38 @@ +/** + * <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.core.gui.components.stack; + +import org.olat.core.gui.control.Event; + +/** + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RootEvent extends Event { + + private static final long serialVersionUID = 8230688627245883270L; + + public RootEvent() { + super("root"); + } + +} diff --git a/src/main/java/org/olat/core/gui/components/stack/TooledStackedPanel.java b/src/main/java/org/olat/core/gui/components/stack/TooledStackedPanel.java index 02b0cd9f237eb9c9d6fc57f5a34edd11c5658dc2..1d6b3c962408dc661e09549d48fea8cb6450e038 100644 --- a/src/main/java/org/olat/core/gui/components/stack/TooledStackedPanel.java +++ b/src/main/java/org/olat/core/gui/components/stack/TooledStackedPanel.java @@ -143,6 +143,14 @@ public class TooledStackedPanel extends BreadcrumbedStackedPanel implements Stac return (TooledBreadCrumb)stack.get(stack.size() - 1).getUserObject(); } + @Override + public void pushController(String displayName, Controller controller) { + TooledBreadCrumb currentCrumb = getCurrentCrumb(); + if(currentCrumb == null || currentCrumb.getController() != controller) { + super.pushController(displayName, controller); + } + } + /** * By default, the toolbar is enabled, using this method it can be disable to just show * the bread crumb path to the user (e.g. course site) diff --git a/src/main/java/org/olat/core/gui/components/stack/VetoPopEvent.java b/src/main/java/org/olat/core/gui/components/stack/VetoPopEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..bd587f30fe9d05588ba3ce021eedce0200e8d502 --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/stack/VetoPopEvent.java @@ -0,0 +1,38 @@ +/** + * <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.core.gui.components.stack; + +import org.olat.core.gui.control.Event; + +/** + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class VetoPopEvent extends Event { + + private static final long serialVersionUID = 8230688627245883270L; + + public VetoPopEvent() { + super("veto-pop"); + } + +} diff --git a/src/main/java/org/olat/course/config/CourseConfig.java b/src/main/java/org/olat/course/config/CourseConfig.java index 67f7d41111b1fbe526268bfcaf16f716ca652bd9..4aebe20520e4b0b9a36684e74b8cd4aed0e04a33 100644 --- a/src/main/java/org/olat/course/config/CourseConfig.java +++ b/src/main/java/org/olat/course/config/CourseConfig.java @@ -326,7 +326,11 @@ public class CourseConfig implements Serializable, Cloneable { * @param softkey */ public void setSharedFolderSoftkey(String softkey) { - configuration.put(KEY_SHAREDFOLDER_SOFTKEY, softkey); + if(softkey == null) { + configuration.put(KEY_SHAREDFOLDER_SOFTKEY, VALUE_EMPTY_SHAREDFOLDER_SOFTKEY); + } else { + configuration.put(KEY_SHAREDFOLDER_SOFTKEY, softkey); + } } /** diff --git a/src/main/java/org/olat/course/config/ui/CourseCalendarConfigForm.java b/src/main/java/org/olat/course/config/ui/CourseCalendarConfigForm.java deleted file mode 100644 index 61900c94cc626280e25497540c80f9be2c214059..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/config/ui/CourseCalendarConfigForm.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.course.config.ui; - -import org.olat.commons.calendar.CalendarManager; -import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.SelectionElement; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -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.id.OLATResourceable; -import org.olat.core.logging.activity.ILoggingAction; -import org.olat.core.logging.activity.LearningResourceLoggingAction; -import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; -import org.olat.core.util.coordinate.CoordinatorManager; -import org.olat.core.util.resource.OresHelper; -import org.olat.course.CourseFactory; -import org.olat.course.ICourse; -import org.olat.course.config.CourseConfig; -import org.olat.course.config.CourseConfigEvent; -import org.olat.course.config.CourseConfigEvent.CourseConfigType; - -/** - * - * Initial date: 06.05.2014<br> - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class CourseCalendarConfigForm extends FormBasicController { - - private SelectionElement isOn; - private final boolean editable; - private final boolean calendarEnabled; - - private final OLATResourceable courseOres; - private CourseConfig courseConfig; - - /** - * @param name - * @param chatEnabled - */ - public CourseCalendarConfigForm(UserRequest ureq, WindowControl wControl, - OLATResourceable courseOres, CourseConfig courseConfig, boolean editable) { - super(ureq, wControl); - this.courseOres = OresHelper.clone(courseOres); - this.courseConfig = courseConfig; - this.editable = editable; - this.calendarEnabled = courseConfig.isCalendarEnabled(); - initForm (ureq); - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - setFormContextHelp("org.olat.course.config.ui","course-calendar.html","help.hover.coursecal"); - - isOn = uifactory.addCheckboxesVertical("isOn", "chkbx.calendar.onoff", formLayout, new String[] {"xx"}, new String[] {""}, 1); - isOn.select("xx", calendarEnabled); - isOn.setEnabled(editable); - - if(editable) { - uifactory.addFormSubmitButton("save", "save", formLayout); - } - } - - @Override - protected void doDispose() { - // - } - - @Override - protected void formOK(UserRequest ureq) { - boolean enable = isOn.isSelected(0); - - ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId()); - courseConfig = course.getCourseEnvironment().getCourseConfig(); - courseConfig.setCalendarEnabled(enable); - CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig); - CourseFactory.closeCourseEditSession(course.getResourceableId(),true); - - ILoggingAction loggingAction = enable ? - LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_CALENDAR_ENABLED : - LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_CALENDAR_DISABLED; - - ThreadLocalUserActivityLogger.log(loggingAction, getClass()); - CoordinatorManager.getInstance().getCoordinator().getEventBus() - .fireEventToListenersOf(new KalendarModifiedEvent(), OresHelper.lookupType(CalendarManager.class)); - CoordinatorManager.getInstance().getCoordinator().getEventBus() - .fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.calendar, course.getResourceableId()), course); - - fireEvent(ureq, Event.CHANGED_EVENT); - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/CourseChatSettingsForm.java b/src/main/java/org/olat/course/config/ui/CourseChatSettingsForm.java deleted file mode 100644 index f5d50816974c56c78857d72dbe9a4886861ad521..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/config/ui/CourseChatSettingsForm.java +++ /dev/null @@ -1,101 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.course.config.ui; - -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.SelectionElement; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -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.id.OLATResourceable; -import org.olat.core.logging.activity.LearningResourceLoggingAction; -import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; -import org.olat.core.util.resource.OresHelper; -import org.olat.course.CourseFactory; -import org.olat.course.ICourse; -import org.olat.course.config.CourseConfig; - - -/** - * Description: <br> - * - * Initial Date: Jun 16, 2005 <br> - * @author patrick - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - */ -public class CourseChatSettingsForm extends FormBasicController { - - private SelectionElement isOn; - private final boolean chatEnabled; - private final boolean editable; - private final OLATResourceable courseOres; - - public CourseChatSettingsForm(UserRequest ureq, WindowControl wControl, - OLATResourceable courseOres, CourseConfig courseConfig, boolean editable) { - super(ureq, wControl); - this.editable = editable; - this.courseOres = OresHelper.clone(courseOres); - chatEnabled = courseConfig.isChatEnabled(); - initForm(ureq); - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - setFormContextHelp("org.olat.course.config.ui","course-chat.html","help.hover.course-chat"); - - isOn = uifactory.addCheckboxesVertical("isOn", "chkbx.chat.onoff", formLayout, new String[] {"xx"}, new String[] {""}, 1); - isOn.select("xx", chatEnabled); - isOn.setEnabled(editable); - - if(editable) { - uifactory.addFormSubmitButton("save", "save", formLayout); - } - } - - @Override - protected void doDispose() { - // - } - - @Override - protected void formOK(UserRequest ureq) { - ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId()); - CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig(); - courseConfig.setChatIsEnabled(isOn.isSelected(0)); - CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig); - CourseFactory.closeCourseEditSession(course.getResourceableId(), true); - - if (courseConfig.isChatEnabled()) { - ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_IM_ENABLED, getClass()); - } else { - ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_IM_DISABLED, getClass()); - } - - fireEvent (ureq, Event.CHANGED_EVENT); - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/CourseConfigGlossaryController.java b/src/main/java/org/olat/course/config/ui/CourseConfigGlossaryController.java deleted file mode 100644 index ad1b6a50754c6f3e3a1fa0937ec790cb67c70197..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/config/ui/CourseConfigGlossaryController.java +++ /dev/null @@ -1,251 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.course.config.ui; - -import java.util.List; - -import org.olat.core.CoreSpringFactory; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItem; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.FormLink; -import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -import org.olat.core.gui.components.form.flexible.impl.FormEvent; -import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.link.Link; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; -import org.olat.core.id.OLATResourceable; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; -import org.olat.core.logging.activity.ILoggingAction; -import org.olat.core.logging.activity.LearningResourceLoggingAction; -import org.olat.core.logging.activity.StringResourceableType; -import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; -import org.olat.core.util.StringHelper; -import org.olat.course.CourseFactory; -import org.olat.course.ICourse; -import org.olat.course.config.CourseConfig; -import org.olat.fileresource.types.GlossaryResource; -import org.olat.modules.glossary.GlossaryManager; -import org.olat.repository.RepositoryEntry; -import org.olat.repository.RepositoryManager; -import org.olat.repository.controllers.ReferencableEntriesSearchController; -import org.olat.resource.references.ReferenceImpl; -import org.olat.resource.references.ReferenceManager; -import org.olat.util.logging.activity.LoggingResourceable; - -/** - * - * Description: <br> - * Course config controller to modify the course glossary. The controller allows - * the user to enable / disable the course glossary by setting a - * <p> - * - * @author Florian Gnägi, frentix GmbH, http://www.frentix.com - */ -public class CourseConfigGlossaryController extends FormBasicController { - - private static final OLog log = Tracing.createLoggerFor(CourseConfigGlossaryController.class); - public static final String VALUE_EMPTY_GLOSSARY_FILEREF = "gf.notconfigured"; - private static final String COMMAND_REMOVE = "command.glossary.remove"; - private static final String COMMAND_ADD = "command.glossary.add"; - - private FormLink addCommand, removeCommand; - private StaticTextElement reNameEl; - - private CloseableModalController cmc; - private ReferencableEntriesSearchController repoSearchCtr; - - private CourseConfig courseConfig; - private final OLATResourceable courseOres; - private final RepositoryManager repositoryService; - private final ReferenceManager refM; - - /** - * - * @param ureq - * @param wControl - * @param courseOres - * @param courseConfig - * @param editable - */ - public CourseConfigGlossaryController(UserRequest ureq, WindowControl wControl, - OLATResourceable courseOres, CourseConfig courseConfig, boolean editable) { - super(ureq, wControl); - this.courseConfig = courseConfig; - this.courseOres = courseOres; - refM = ReferenceManager.getInstance(); - repositoryService = CoreSpringFactory.getImpl(RepositoryManager.class); - initForm(ureq); - - if (courseConfig.hasGlossary()) { - RepositoryEntry repoEntry = repositoryService.lookupRepositoryEntryBySoftkey(courseConfig.getGlossarySoftKey(), false); - if (repoEntry == null) { - // Something is wrong here, maybe the glossary has been deleted. Try to - // remove glossary from configuration - doRemoveGlossary(ureq); - log.warn("Course with ID::" + courseOres + " had a config for a glossary softkey::" - + courseConfig.getGlossarySoftKey() + " but no such glossary was found"); - } else if(editable) { - reNameEl.setValue(StringHelper.escapeHtml(repoEntry.getDisplayname())); - removeCommand.setVisible(true); - } - } else if(editable) { - removeCommand.setVisible(false); - addCommand.setVisible(true); - } - - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - setFormContextHelp("org.olat.course.config.ui","course-glossary.html","help.hover.course-gloss"); - - String text = translate("glossary.no.glossary"); - reNameEl = uifactory.addStaticTextElement("repoName", "glossary.isconfigured", text, formLayout); - - FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); - formLayout.add(buttonsCont); - - removeCommand = uifactory.addFormLink(COMMAND_REMOVE, buttonsCont, Link.BUTTON); - addCommand = uifactory.addFormLink(COMMAND_ADD, buttonsCont, Link.BUTTON); - } - - @Override - protected void doDispose() { - // - } - - @Override - protected void formOK(UserRequest ureq) { - // - } - - @Override - protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if (source == addCommand) { - repoSearchCtr = new ReferencableEntriesSearchController(getWindowControl(), ureq, GlossaryResource.TYPE_NAME, translate("select")); - listenTo(repoSearchCtr); - cmc = new CloseableModalController(getWindowControl(), translate("close"), repoSearchCtr.getInitialComponent()); - listenTo(cmc); - cmc.activate(); - } else if (source == removeCommand) { - doRemoveGlossary(ureq); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } - - @Override - public void event(UserRequest ureq, Controller source, Event event) { - if (source == repoSearchCtr) { - cmc.deactivate(); - if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { - RepositoryEntry repoEntry = repoSearchCtr.getSelectedEntry(); - doSelectGlossary(repoEntry, ureq); - fireEvent(ureq, Event.CHANGED_EVENT); - } - cleanUp(); - } else if(cmc == source) { - cleanUp(); - } - } - - private void cleanUp() { - removeAsListenerAndDispose(repoSearchCtr); - removeAsListenerAndDispose(cmc); - repoSearchCtr = null; - cmc = null; - } - - /** - * Updates config with selected glossary - * - * @param repoEntry - * @param ureq - */ - private void doSelectGlossary(RepositoryEntry repoEntry, UserRequest ureq) { - reNameEl.setValue(StringHelper.escapeHtml(repoEntry.getDisplayname())); - removeCommand.setVisible(true); - saveConfig(ureq, repoEntry.getSoftkey()); - } - - /** - * Removes the current glossary from the configuration - * - * @param ureq - */ - private void doRemoveGlossary(UserRequest ureq) { - reNameEl.setValue(translate("glossary.no.glossary")); - removeCommand.setVisible(false); - saveConfig(ureq, null); - } - - private void saveConfig(UserRequest ureq, String softKey) { - final String deleteGlossarySoftKey = courseConfig.getGlossarySoftKey(); - - ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId()); - courseConfig = course.getCourseEnvironment().getCourseConfig(); - courseConfig.setGlossarySoftKey(softKey); - CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig); - CourseFactory.closeCourseEditSession(course.getResourceableId(),true); - - ILoggingAction loggingAction = (softKey == null) ? - LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_GLOSSARY_DISABLED : - LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_GLOSSARY_ENABLED; - - - - LoggingResourceable lri = null; - if(softKey != null) { - lri = LoggingResourceable.wrapNonOlatResource(StringResourceableType.glossarySoftKey, softKey, softKey); - } else if (deleteGlossarySoftKey != null) { - lri = LoggingResourceable.wrapNonOlatResource(StringResourceableType.glossarySoftKey, deleteGlossarySoftKey, deleteGlossarySoftKey); - } - if (lri != null) { - ThreadLocalUserActivityLogger.log(loggingAction, getClass(), lri); - } - if(softKey == null) { - // remove references - List<ReferenceImpl> repoRefs = refM.getReferences(course); - for (ReferenceImpl ref:repoRefs) { - if (ref.getUserdata().equals(GlossaryManager.GLOSSARY_REPO_REF_IDENTIFYER)) { - refM.delete(ref); - continue; - } - } - } else { - // update references - RepositoryEntry repoEntry = repositoryService.lookupRepositoryEntryBySoftkey(softKey, false); - refM.addReference(course, repoEntry.getOlatResource(), GlossaryManager.GLOSSARY_REPO_REF_IDENTIFYER); - } - - fireEvent(ureq, Event.CHANGED_EVENT); - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/CourseEfficencyStatementForm.java b/src/main/java/org/olat/course/config/ui/CourseEfficencyStatementForm.java deleted file mode 100644 index b3e63c0b6012cd9fd4d80e5026980064807d77b3..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/config/ui/CourseEfficencyStatementForm.java +++ /dev/null @@ -1,158 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.course.config.ui; - -import java.util.List; - -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.SelectionElement; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.generic.modal.DialogBoxController; -import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; -import org.olat.core.id.Identity; -import org.olat.core.id.OLATResourceable; -import org.olat.core.logging.activity.ILoggingAction; -import org.olat.core.logging.activity.LearningResourceLoggingAction; -import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; -import org.olat.core.util.coordinate.CoordinatorManager; -import org.olat.core.util.event.EventBus; -import org.olat.core.util.resource.OresHelper; -import org.olat.course.CourseFactory; -import org.olat.course.ICourse; -import org.olat.course.assessment.EfficiencyStatementManager; -import org.olat.course.config.CourseConfig; -import org.olat.course.config.CourseConfigEvent; -import org.olat.course.config.CourseConfigEvent.CourseConfigType; -import org.olat.repository.RepositoryEntry; -import org.olat.repository.RepositoryManager; -/** - * - * Description:<br> - * TODO: guido Class Description for CourseEfficencyStatementForm - * - */ -public class CourseEfficencyStatementForm extends FormBasicController { - - private SelectionElement isOn; - private final boolean enabled; - private final boolean editable; - private CourseConfig courseConfig; - private final OLATResourceable courseOres; - - private DialogBoxController enableEfficiencyDC, disableEfficiencyDC; - - /** - * @param name - * @param chatEnabled - */ - public CourseEfficencyStatementForm(UserRequest ureq, WindowControl wControl, - OLATResourceable courseOres, CourseConfig courseConfig, boolean editable) { - super(ureq, wControl); - this.courseConfig = courseConfig; - this.courseOres = OresHelper.clone(courseOres); - enabled = courseConfig.isEfficencyStatementEnabled(); - this.editable = editable; - initForm (ureq); - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - setFormContextHelp("org.olat.course.config.ui","course-efficiency.html","help.hover.course-eff"); - - isOn = uifactory.addCheckboxesVertical("isOn", "chkbx.efficency.onoff", formLayout, new String[] {"xx"}, new String[] {""}, 1); - isOn.select("xx", enabled); - isOn.setEnabled(editable); - - if(editable) { - uifactory.addFormSubmitButton("save", "save", formLayout); - } - } - - @Override - protected void doDispose() { - // - } - - @Override - protected void event(UserRequest ureq, Controller source, Event event) { - if (source == disableEfficiencyDC) { - if (DialogBoxUIFactory.isOkEvent(event)) { - doChangeConfig(); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } else if (source == enableEfficiencyDC) { - if (DialogBoxUIFactory.isOkEvent(event)) { - doChangeConfig(); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } - } - - @Override - protected void formOK(UserRequest ureq) { - if (courseConfig.isEfficencyStatementEnabled()) { - // a change from enabled Efficiency to disabled - disableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.todisabled"), disableEfficiencyDC); - } else { - // a change from disabled Efficiency - enableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.toenable"), enableEfficiencyDC); - } - } - - private void doChangeConfig() { - ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId()); - courseConfig = course.getCourseEnvironment().getCourseConfig(); - - boolean enable = isOn.isSelected(0); - - ILoggingAction loggingAction = enable ? - LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_ENABLED : - LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_DISABLED; - - courseConfig.setEfficencyStatementIsEnabled(enable); - CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig); - CourseFactory.closeCourseEditSession(course.getResourceableId(), true); - - if(enable) { - // first create the efficiencies, send event to agency (all courses add link) - List<Identity> identitiesWithData = course.getCourseEnvironment().getCoursePropertyManager().getAllIdentitiesWithCourseAssessmentData(null); - EfficiencyStatementManager.getInstance().updateEfficiencyStatements(course, identitiesWithData); - } else { - // delete really the efficiencies of the users. - RepositoryEntry courseRepoEntry = RepositoryManager.getInstance().lookupRepositoryEntry(course, true); - EfficiencyStatementManager.getInstance().deleteEfficiencyStatementsFromCourse(courseRepoEntry.getKey()); - } - //inform everybody else - EventBus eventBus = CoordinatorManager.getInstance().getCoordinator().getEventBus(); - CourseConfigEvent courseConfigEvent = new CourseConfigEvent(CourseConfigType.efficiencyStatement, course.getResourceableId()); - eventBus.fireEventToListenersOf(courseConfigEvent, course); - ThreadLocalUserActivityLogger.log(loggingAction, getClass()); - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/CourseOptionsController.java b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java new file mode 100644 index 0000000000000000000000000000000000000000..f017cbcd1a8ebe38ae84874e8fe02f55a7350066 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/CourseOptionsController.java @@ -0,0 +1,465 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.config.ui; + +import java.util.List; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.SelectionElement; +import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.logging.activity.ILoggingAction; +import org.olat.core.logging.activity.LearningResourceLoggingAction; +import org.olat.core.logging.activity.StringResourceableType; +import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; +import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.EventBus; +import org.olat.core.util.resource.OresHelper; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.assessment.EfficiencyStatementManager; +import org.olat.course.config.CourseConfig; +import org.olat.course.config.CourseConfigEvent; +import org.olat.course.config.CourseConfigEvent.CourseConfigType; +import org.olat.fileresource.types.GlossaryResource; +import org.olat.fileresource.types.SharedFolderFileResource; +import org.olat.modules.glossary.GlossaryManager; +import org.olat.modules.sharedfolder.SharedFolderManager; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.repository.controllers.ReferencableEntriesSearchController; +import org.olat.resource.references.ReferenceImpl; +import org.olat.resource.references.ReferenceManager; +import org.olat.util.logging.activity.LoggingResourceable; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 14.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CourseOptionsController extends FormBasicController { + private static final OLog log = Tracing.createLoggerFor(CourseOptionsController.class); + private static final String COMMAND_REMOVE = "command.glossary.remove"; + private static final String COMMAND_ADD = "command.glossary.add"; + + private SelectionElement efficencyEl, calendarEl, chatEl; + private FormLink addGlossaryCommand, removeGlossaryCommand; + private StaticTextElement glossaryNameEl; + + private FormLink addFolderCommand, removeFolderCommand; + private StaticTextElement folderNameEl; + + private final boolean editable; + private CourseConfig courseConfig; + private final OLATResourceable courseOres; + + + private CloseableModalController cmc; + private ReferencableEntriesSearchController glossarySearchCtr, folderSearchCtr; + private DialogBoxController enableEfficiencyDC, disableEfficiencyDC; + + @Autowired + private ReferenceManager referenceManager; + @Autowired + private RepositoryManager repositoryService; + + /** + * @param name + * @param chatEnabled + */ + public CourseOptionsController(UserRequest ureq, WindowControl wControl, + OLATResourceable courseOres, CourseConfig courseConfig, boolean editable) { + super(ureq, wControl, LAYOUT_BAREBONE); + this.courseConfig = courseConfig; + this.courseOres = OresHelper.clone(courseOres); + + this.editable = editable; + initForm (ureq); + + //glossary setup + if (courseConfig.hasGlossary()) { + RepositoryEntry repoEntry = repositoryService.lookupRepositoryEntryBySoftkey(courseConfig.getGlossarySoftKey(), false); + if (repoEntry == null) { + // Something is wrong here, maybe the glossary has been deleted. Try to + // remove glossary from configuration + doRemoveGlossary(); + log.warn("Course with ID::" + courseOres + " had a config for a glossary softkey::" + + courseConfig.getGlossarySoftKey() + " but no such glossary was found"); + } else if(editable) { + glossaryNameEl.setValue(StringHelper.escapeHtml(repoEntry.getDisplayname())); + glossaryNameEl.setUserObject(repoEntry); + removeGlossaryCommand.setVisible(true); + } + } else if(editable) { + removeGlossaryCommand.setVisible(false); + addGlossaryCommand.setVisible(true); + } + + //shared folder + if (courseConfig.hasCustomSharedFolder()) { + RepositoryEntry repoEntry = repositoryService.lookupRepositoryEntryBySoftkey(courseConfig.getGlossarySoftKey(), false); + if (repoEntry == null) { + // Something is wrong here, maybe the glossary has been deleted. + // Try to remove shared folder from configuration + doRemoveSharedFolder(); + } else if(editable) { + folderNameEl.setValue(StringHelper.escapeHtml(repoEntry.getDisplayname())); + folderNameEl.setUserObject(repoEntry); + removeFolderCommand.setVisible(true); + } + } else if(editable) { + removeFolderCommand.setVisible(false); + addFolderCommand.setVisible(true); + } + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + //efficiency statement + FormLayoutContainer effCont = FormLayoutContainer.createDefaultFormLayout("eff", getTranslator()); + effCont.setRootForm(mainForm); + formLayout.add(effCont); + effCont.setFormContextHelp("org.olat.course.config.ui","course-efficiency.html","help.hover.course-eff"); + + boolean effEnabled = courseConfig.isEfficencyStatementEnabled(); + efficencyEl = uifactory.addCheckboxesHorizontal("effIsOn", "chkbx.efficency.onoff", effCont, new String[] {"xx"}, new String[] {""}); + efficencyEl.select("xx", effEnabled); + efficencyEl.setEnabled(editable); + + //calendar + FormLayoutContainer calCont = FormLayoutContainer.createDefaultFormLayout("cal", getTranslator()); + calCont.setRootForm(mainForm); + formLayout.add(calCont); + calCont.setFormContextHelp("org.olat.course.config.ui","course-calendar.html","help.hover.coursecal"); + + boolean calendarEnabled = courseConfig.isCalendarEnabled(); + calendarEl = uifactory.addCheckboxesHorizontal("calIsOn", "chkbx.calendar.onoff", calCont, new String[] {"xx"}, new String[] {""}); + calendarEl.select("xx", calendarEnabled); + calendarEl.setEnabled(editable); + + //chat + FormLayoutContainer chatCont = FormLayoutContainer.createDefaultFormLayout("chat", getTranslator()); + chatCont.setRootForm(mainForm); + formLayout.add(chatCont); + chatCont.setFormContextHelp("org.olat.course.config.ui","course-chat.html","help.hover.course-chat"); + + boolean chatEnabled = courseConfig.isChatEnabled(); + chatEl = uifactory.addCheckboxesHorizontal("chatIsOn", "chkbx.chat.onoff", chatCont, new String[] {"xx"}, new String[] {""}); + chatEl.select("xx", chatEnabled); + chatEl.setEnabled(editable); + + //glossary + FormLayoutContainer glossaryCont = FormLayoutContainer.createDefaultFormLayout("glossary", getTranslator()); + glossaryCont.setRootForm(mainForm); + formLayout.add(glossaryCont); + glossaryCont.setFormContextHelp("org.olat.course.config.ui","course-glossary.html","help.hover.course-gloss"); + + glossaryNameEl = uifactory.addStaticTextElement("glossaryName", "glossary.isconfigured", + translate("glossary.no.glossary"), glossaryCont); + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + glossaryCont.add(buttonsCont); + removeGlossaryCommand = uifactory.addFormLink(COMMAND_REMOVE, buttonsCont, Link.BUTTON); + addGlossaryCommand = uifactory.addFormLink(COMMAND_ADD, buttonsCont, Link.BUTTON); + + //shared folder + FormLayoutContainer sharedFolderCont = FormLayoutContainer.createDefaultFormLayout("sharedfolder", getTranslator()); + sharedFolderCont.setRootForm(mainForm); + formLayout.add(sharedFolderCont); + sharedFolderCont.setFormContextHelp("org.olat.course.config.ui","course-resfolder.html","help.hover.course-res"); + + folderNameEl = uifactory.addStaticTextElement("folderName", "sf.resourcetitle", + translate("sf.notconfigured"), sharedFolderCont); + + FormLayoutContainer buttons2Cont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + sharedFolderCont.add(buttons2Cont); + removeFolderCommand = uifactory.addFormLink("sf.unselectsfresource", buttons2Cont, Link.BUTTON); + addFolderCommand = uifactory.addFormLink("sf.changesfresource", buttons2Cont, Link.BUTTON); + + + if(editable) { + FormLayoutContainer save = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator()); + save.setRootForm(mainForm); + formLayout.add(save); + uifactory.addFormSubmitButton("save", "save", save); + } + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == disableEfficiencyDC) { + if (DialogBoxUIFactory.isOkEvent(event)) { + doChangeConfig(ureq); + } + } else if (source == enableEfficiencyDC) { + if (DialogBoxUIFactory.isOkEvent(event)) { + doChangeConfig(ureq); + } + } else if (source == glossarySearchCtr) { + cmc.deactivate(); + if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { + RepositoryEntry repoEntry = glossarySearchCtr.getSelectedEntry(); + doSelectGlossary(repoEntry); + } + cleanUp(); + } else if (source == folderSearchCtr) { + cmc.deactivate(); + if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { + RepositoryEntry repoEntry = folderSearchCtr.getSelectedEntry(); + doSelectSharedFolder(repoEntry); + } + cleanUp(); + } else if(cmc == source) { + cleanUp(); + } + } + + private void cleanUp() { + + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == addGlossaryCommand) { + glossarySearchCtr = new ReferencableEntriesSearchController(getWindowControl(), ureq, GlossaryResource.TYPE_NAME, translate("select")); + listenTo(glossarySearchCtr); + cmc = new CloseableModalController(getWindowControl(), translate("close"), glossarySearchCtr.getInitialComponent()); + listenTo(cmc); + cmc.activate(); + } else if (source == removeGlossaryCommand) { + doRemoveGlossary(); + } else if (source == addFolderCommand) { + folderSearchCtr = new ReferencableEntriesSearchController(getWindowControl(), ureq, SharedFolderFileResource.TYPE_NAME, translate("select")); + listenTo(folderSearchCtr); + cmc = new CloseableModalController(getWindowControl(), translate("close"), folderSearchCtr.getInitialComponent()); + listenTo(cmc); + cmc.activate(); + } else if (source == removeFolderCommand) { + doRemoveSharedFolder(); + } + } + + @Override + protected void formOK(UserRequest ureq) { + boolean confirmUpdateStatement = courseConfig.isEfficencyStatementEnabled() != efficencyEl.isSelected(0); + if(confirmUpdateStatement) { + if (courseConfig.isEfficencyStatementEnabled()) { + // a change from enabled Efficiency to disabled + disableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.todisabled"), disableEfficiencyDC); + } else { + // a change from disabled Efficiency + enableEfficiencyDC = activateYesNoDialog(ureq, null, translate("warning.change.toenable"), enableEfficiencyDC); + } + } else { + doChangeConfig(ureq); + } + } + + private void doChangeConfig(UserRequest ureq) { + ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId()); + courseConfig = course.getCourseEnvironment().getCourseConfig(); + + boolean enableEfficiencyStatment = efficencyEl.isSelected(0); + boolean updateStatement = courseConfig.isEfficencyStatementEnabled() != enableEfficiencyStatment; + courseConfig.setEfficencyStatementIsEnabled(enableEfficiencyStatment); + + boolean enableChat = chatEl.isSelected(0); + boolean updateChat = courseConfig.isChatEnabled() != enableChat; + courseConfig.setChatIsEnabled(enableChat); + + boolean enableCalendar = calendarEl.isSelected(0); + boolean updateCalendar = courseConfig.isCalendarEnabled() != enableCalendar; + courseConfig.setCalendarEnabled(enableCalendar); + + + String currentGlossarySoftKey = courseConfig.getGlossarySoftKey(); + RepositoryEntry glossary = (RepositoryEntry)glossaryNameEl.getUserObject(); + String newGlossarySoftKey = glossary == null ? null : glossary.getSoftkey(); + boolean updateGlossary = (currentGlossarySoftKey == null && newGlossarySoftKey != null) + || (currentGlossarySoftKey != null && newGlossarySoftKey == null) + || (newGlossarySoftKey != null && !newGlossarySoftKey.equals(currentGlossarySoftKey)); + + courseConfig.setGlossarySoftKey(newGlossarySoftKey); + + + String currentFolderSoftKey = courseConfig.getSharedFolderSoftkey(); + RepositoryEntry folder = (RepositoryEntry)folderNameEl.getUserObject(); + String newFolderSoftKey = folder == null ? null : folder.getSoftkey(); + boolean updateFolder = (currentFolderSoftKey == null && newFolderSoftKey != null) + || (currentFolderSoftKey != null && newFolderSoftKey == null) + || (currentFolderSoftKey != null && !currentFolderSoftKey.equals(newFolderSoftKey)); + + courseConfig.setSharedFolderSoftkey(newFolderSoftKey); + + CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig); + CourseFactory.closeCourseEditSession(course.getResourceableId(), true); + + if(updateChat) { + ILoggingAction loggingAction = enableChat ? + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_IM_ENABLED : + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_IM_DISABLED; + ThreadLocalUserActivityLogger.log(loggingAction, getClass()); + } + + if(updateCalendar) { + ILoggingAction loggingAction = enableCalendar ? + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_CALENDAR_ENABLED : + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_CALENDAR_DISABLED; + + ThreadLocalUserActivityLogger.log(loggingAction, getClass()); + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .fireEventToListenersOf(new KalendarModifiedEvent(), OresHelper.lookupType(CalendarManager.class)); + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.calendar, course.getResourceableId()), course); + } + + if(updateGlossary) { + ILoggingAction loggingAction = (newGlossarySoftKey == null) ? + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_GLOSSARY_DISABLED : + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_GLOSSARY_ENABLED; + + LoggingResourceable lri = null; + if(newGlossarySoftKey != null) { + lri = LoggingResourceable.wrapNonOlatResource(StringResourceableType.glossarySoftKey, newGlossarySoftKey, newGlossarySoftKey); + } else if (currentGlossarySoftKey != null) { + lri = LoggingResourceable.wrapNonOlatResource(StringResourceableType.glossarySoftKey, currentGlossarySoftKey, currentGlossarySoftKey); + } + if (lri != null) { + ThreadLocalUserActivityLogger.log(loggingAction, getClass(), lri); + } + + // remove references + List<ReferenceImpl> repoRefs = referenceManager.getReferences(course); + for (ReferenceImpl ref:repoRefs) { + if (ref.getUserdata().equals(GlossaryManager.GLOSSARY_REPO_REF_IDENTIFYER)) { + referenceManager.delete(ref); + } + } + // update references + if(glossary != null) { + referenceManager.addReference(course, glossary.getOlatResource(), GlossaryManager.GLOSSARY_REPO_REF_IDENTIFYER); + } + } + + if(updateFolder) { + List<ReferenceImpl> repoRefs = referenceManager.getReferences(course); + for (ReferenceImpl ref:repoRefs) { + if (ref.getUserdata().equals(SharedFolderManager.SHAREDFOLDERREF)) { + referenceManager.delete(ref); + } + } + + if(folder != null) { + ReferenceManager.getInstance().addReference(course, folder.getOlatResource(), SharedFolderManager.SHAREDFOLDERREF); + ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_SHARED_FOLDER_REMOVED, + getClass(), LoggingResourceable.wrapBCFile(folder.getDisplayname())); + } else { + ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_SHARED_FOLDER_ADDED, + getClass(), LoggingResourceable.wrapBCFile("")); + } + } + + if(updateStatement) { + if(enableEfficiencyStatment) { + // first create the efficiencies, send event to agency (all courses add link) + List<Identity> identitiesWithData = course.getCourseEnvironment().getCoursePropertyManager().getAllIdentitiesWithCourseAssessmentData(null); + EfficiencyStatementManager.getInstance().updateEfficiencyStatements(course, identitiesWithData); + } else { + // delete really the efficiencies of the users. + RepositoryEntry courseRepoEntry = RepositoryManager.getInstance().lookupRepositoryEntry(course, true); + EfficiencyStatementManager.getInstance().deleteEfficiencyStatementsFromCourse(courseRepoEntry.getKey()); + } + + //inform everybody else + EventBus eventBus = CoordinatorManager.getInstance().getCoordinator().getEventBus(); + CourseConfigEvent courseConfigEvent = new CourseConfigEvent(CourseConfigType.efficiencyStatement, course.getResourceableId()); + eventBus.fireEventToListenersOf(courseConfigEvent, course); + + ILoggingAction loggingAction = enableEfficiencyStatment ? + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_ENABLED : + LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EFFICIENCY_STATEMENT_DISABLED; + ThreadLocalUserActivityLogger.log(loggingAction, getClass()); + } + + fireEvent(ureq, Event.CHANGED_EVENT); + } + + /** + * Updates config with selected glossary + * + * @param repoEntry + * @param ureq + */ + private void doSelectGlossary(RepositoryEntry repoEntry) { + glossaryNameEl.setValue(StringHelper.escapeHtml(repoEntry.getDisplayname())); + glossaryNameEl.setUserObject(repoEntry); + removeGlossaryCommand.setVisible(true); + } + + /** + * Removes the current glossary from the configuration + * + * @param ureq + */ + private void doRemoveGlossary() { + glossaryNameEl.setValue(translate("glossary.no.glossary")); + glossaryNameEl.setUserObject(null); + removeGlossaryCommand.setVisible(false); + } + + private void doSelectSharedFolder(RepositoryEntry repoEntry) { + folderNameEl.setValue(StringHelper.escapeHtml(repoEntry.getDisplayname())); + folderNameEl.setUserObject(repoEntry); + removeFolderCommand.setVisible(true); + } + + private void doRemoveSharedFolder() { + folderNameEl.setValue(translate("sf.notconfigured")); + folderNameEl.setUserObject(null); + removeFolderCommand.setVisible(false); + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/CourseSharedFolderController.java b/src/main/java/org/olat/course/config/ui/CourseSharedFolderController.java deleted file mode 100644 index b05db819c3edbc164dd7c662b26ed9a45a010792..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/config/ui/CourseSharedFolderController.java +++ /dev/null @@ -1,239 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.course.config.ui; - -import java.util.Iterator; -import java.util.List; - -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.link.Link; -import org.olat.core.gui.components.link.LinkFactory; -import org.olat.core.gui.components.velocity.VelocityContainer; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.controller.BasicController; -import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; -import org.olat.core.id.OLATResourceable; -import org.olat.core.logging.activity.LearningResourceLoggingAction; -import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; -import org.olat.course.CourseFactory; -import org.olat.course.ICourse; -import org.olat.course.config.CourseConfig; -import org.olat.fileresource.types.SharedFolderFileResource; -import org.olat.repository.RepositoryEntry; -import org.olat.repository.RepositoryManager; -import org.olat.repository.controllers.ReferencableEntriesSearchController; -import org.olat.resource.references.ReferenceImpl; -import org.olat.resource.references.ReferenceManager; -import org.olat.util.logging.activity.LoggingResourceable; - -/** - * Description: <br> - * User (un)selects one shared folder per course. The softkey of the shared - * folder repository entry is saved in the course config. Also the reference - * (course -> repo entry)is saved, that nobody can delete a shared folder which - * is still referenced from a course. - * <P> - * - * @version Initial Date: July 11, 2005 - * @author Alexander Schneider - */ -public class CourseSharedFolderController extends BasicController { - - private static final String SHAREDFOLDERREF = "sharedfolderref"; - - private VelocityContainer myContent; - - private ReferencableEntriesSearchController searchController; - - private RepositoryManager rm = RepositoryManager.getInstance(); - - private boolean hasSF; // has shared folder configured - private Link changeSFResButton; - private Link unselectSFResButton; - private Link selectSFResButton; - private CloseableModalController cmc; - private CourseConfig courseConfig; - private OLATResourceable courseOres; - - /** - * - * @param ureq - * @param wControl - * @param courseOres - * @param courseConfig - * @param editable - */ - public CourseSharedFolderController(UserRequest ureq, WindowControl wControl, - OLATResourceable courseOres, CourseConfig courseConfig, boolean editable) { - super(ureq, wControl); - - this.courseConfig = courseConfig; - this.courseOres = courseOres; - - myContent = createVelocityContainer("CourseSharedFolder"); - if(editable) { - changeSFResButton = LinkFactory.createButton("sf.changesfresource", myContent, this); - unselectSFResButton = LinkFactory.createButton("sf.unselectsfresource", myContent, this); - selectSFResButton = LinkFactory.createButton("sf.selectsfresource", myContent, this); - } - - String softkey = courseConfig.getSharedFolderSoftkey(); - String name; - - if (!courseConfig.hasCustomSharedFolder()) { - name = translate("sf.notconfigured"); - hasSF = false; - myContent.contextPut("hasSharedFolder", new Boolean(hasSF)); - } else { - RepositoryEntry re = rm.lookupRepositoryEntryBySoftkey(softkey, false); - if (re == null) { - //log.warning("Removed configured sahred folder from course config, because repo entry does not exist anymore."); - courseConfig.setSharedFolderSoftkey(CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY); - name = translate("sf.notconfigured"); - hasSF = false; - myContent.contextPut("hasSharedFolder", new Boolean(hasSF)); - } else { - name = re.getDisplayname(); - hasSF = true; - myContent.contextPut("hasSharedFolder", new Boolean(hasSF)); - } - } - myContent.contextPut("resourceTitle", name); - putInitialPanel(myContent); - } - - @Override - public void event(UserRequest ureq, Component source, Event event) { - if (source == selectSFResButton || source == changeSFResButton) { // select or change shared folder - // let user choose a shared folder - searchController = new ReferencableEntriesSearchController(getWindowControl(), ureq, - SharedFolderFileResource.TYPE_NAME, translate("command.choose")); - listenTo(searchController); - cmc = new CloseableModalController(getWindowControl(), translate("close"), searchController.getInitialComponent()); - listenTo(cmc); - cmc.activate(); - } else if (source == unselectSFResButton) { // unselect shared folder - if (courseConfig.hasCustomSharedFolder()) { - // delete reference from course to sharedfolder - // get unselected shared folder's softkey used for logging - // set default value to delete configured value in course config - courseConfig.setSharedFolderSoftkey(CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY); - //deleteRefTo(course); - //course.getCourseEnvironment().setCourseConfig(cc); - String emptyKey = translate(CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY); - myContent.contextPut("resourceTitle", emptyKey); - hasSF = false; - myContent.contextPut("hasSharedFolder", new Boolean(hasSF)); - saveSharedfolderConfiguration(null); - fireEvent(ureq, Event.CHANGED_EVENT); - } - } - } - - @Override - public void event(UserRequest ureq, Controller source, Event event) { - if (searchController == source) { - if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { - // repository search controller done - RepositoryEntry sharedFolder = searchController.getSelectedEntry(); - hasSF = true; - myContent.contextPut("hasSharedFolder", new Boolean(hasSF)); - myContent.contextPut("resourceTitle", sharedFolder.getDisplayname()); - saveSharedfolderConfiguration(sharedFolder); - } - cmc.deactivate(); - cleanUp(); - } else if(cmc == source) { - cleanUp(); - } - } - - private void cleanUp() { - removeAsListenerAndDispose(searchController); - removeAsListenerAndDispose(cmc); - searchController = null; - cmc = null; - } - - private void saveSharedfolderConfiguration(RepositoryEntry sharedFolder) { - String softKey = sharedFolder == null ? - CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY : sharedFolder.getSoftkey(); - - ICourse course = CourseFactory.openCourseEditSession(courseOres.getResourceableId()); - courseConfig = course.getCourseEnvironment().getCourseConfig(); - courseConfig.setSharedFolderSoftkey(softKey); - CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig); - CourseFactory.closeCourseEditSession(course.getResourceableId(),true); - - if(sharedFolder != null) { - CourseSharedFolderController.updateRefTo(sharedFolder, course); - ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_SHARED_FOLDER_REMOVED, - getClass(), LoggingResourceable.wrapBCFile(sharedFolder.getDisplayname())); - } else { - CourseSharedFolderController.deleteRefTo(course); - ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_SHARED_FOLDER_ADDED, - getClass(), LoggingResourceable.wrapBCFile("")); - } - } - - /** - * Sets the reference from a course to a shared folder. - * - * @param sharedFolderRe - * @param course - */ - public static void updateRefTo(RepositoryEntry sharedFolderRe, ICourse course) { - deleteRefTo(course); - ReferenceManager.getInstance().addReference(course, sharedFolderRe.getOlatResource(), SHAREDFOLDERREF); - } - - /** - * Deletes the reference from a course to a shared folder. - * - * @param entry - the course that holds a reference to a sharedfolder - */ - public static void deleteRefTo(ICourse course) { - ReferenceManager refM = ReferenceManager.getInstance(); - List<ReferenceImpl> repoRefs = refM.getReferences(course); - for (Iterator<ReferenceImpl> iter = repoRefs.iterator(); iter.hasNext();) { - ReferenceImpl ref = iter.next(); - if (ref.getUserdata().equals(SHAREDFOLDERREF)) { - refM.delete(ref); - return; - } - } - } - - /** - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ - protected void doDispose() { - // - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/_content/CourseSharedFolder.html b/src/main/java/org/olat/course/config/ui/_content/CourseSharedFolder.html deleted file mode 100644 index a1ff8de6bd115731c191c856c54ea64935fd142a..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/config/ui/_content/CourseSharedFolder.html +++ /dev/null @@ -1,12 +0,0 @@ -<fieldset> - $r.contextHelpWithWrapper("org.olat.course.config.ui","course-resfolder.html","help.hover.course-res") - <strong>$r.translate("sf.resourcetitle")</strong>: $resourceTitle - <div class="o_button_group"> - #if($hasSharedFolder) - $r.render("sf.changesfresource") - $r.render("sf.unselectsfresource") - #elseif($r.available("sf.selectsfresource")) - $r.render("sf.selectsfresource") - #end - </div> -</fieldset> diff --git a/src/main/java/org/olat/course/nodes/CourseNodeFactory.java b/src/main/java/org/olat/course/nodes/CourseNodeFactory.java index 51417b2e3621a4230f0a3954d59cdd627b85abb3..5545404860b53dedbe1fc27bc567627607e60b08 100644 --- a/src/main/java/org/olat/course/nodes/CourseNodeFactory.java +++ b/src/main/java/org/olat/course/nodes/CourseNodeFactory.java @@ -33,17 +33,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.olat.NewControllerFactory; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; -import org.olat.core.gui.Windows; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.generic.dtabs.DTab; -import org.olat.core.gui.control.generic.dtabs.DTabs; -import org.olat.core.id.OLATResourceable; -import org.olat.core.id.context.BusinessControlFactory; -import org.olat.core.id.context.ContextEntry; +import org.olat.core.gui.control.WindowControl; import org.olat.core.logging.AssertException; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.course.CorruptedCourseException; import org.olat.repository.RepositoryEntry; +import org.olat.repository.handlers.EditionSupport; import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; @@ -54,6 +53,8 @@ import org.olat.repository.handlers.RepositoryHandlerFactory; * @author guido */ public class CourseNodeFactory { + + private static final OLog log = Tracing.createLoggerFor(CourseNodeFactory.class); private static CourseNodeFactory INSTANCE; private Map<String, CourseNodeConfiguration> allCourseNodeConfigurations; @@ -127,7 +128,7 @@ public class CourseNodeFactory { * @param ureq * @param node */ - public void launchReferencedRepoEntryEditor(UserRequest ureq, CourseNode node) { + public void launchReferencedRepoEntryEditor(UserRequest ureq, WindowControl wControl, CourseNode node) { RepositoryEntry repositoryEntry = node.getReferencedRepositoryEntry(); if (repositoryEntry == null) { // do nothing @@ -135,32 +136,16 @@ public class CourseNodeFactory { } RepositoryHandler typeToEdit = RepositoryHandlerFactory.getInstance().getRepositoryHandler(repositoryEntry); - if (!typeToEdit.supportsEdit(repositoryEntry.getOlatResource())){ + if (typeToEdit.supportsEdit(repositoryEntry.getOlatResource()) == EditionSupport.no){ throw new AssertException("Trying to edit repository entry which has no assoiciated editor: "+ typeToEdit); - } - // Open editor in new tab - OLATResourceable ores = repositoryEntry.getOlatResource(); - DTabs dts = Windows.getWindows(ureq).getWindow(ureq).getDTabs(); - DTab dt = dts.getDTab(ores); - if (dt == null) { - // does not yet exist -> create and add - //fxdiff BAKS-7 Resume function - dt = dts.createDTab(ores, repositoryEntry, repositoryEntry.getDisplayname()); - if (dt == null){ - //null means DTabs are full -> warning is shown - return; - } - //user activity logger is set by course factory - Controller editorController = typeToEdit.createEditorController(repositoryEntry, ureq, dt.getWindowControl()); - if(editorController == null){ - //editor could not be created -> warning is shown - return; - } - dt.setController(editorController); - dts.addDTab(ureq, dt); } - List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromResourceType("activateEditor"); - dts.activate(ureq, dt, entries); + + try { + String businessPath = "[RepositoryEntry:" + repositoryEntry.getKey() + "][Editor:0]"; + NewControllerFactory.getInstance().launch(businessPath, ureq, wControl); + } catch (CorruptedCourseException e) { + log.error("Course corrupted: " + repositoryEntry.getKey() + " (" + repositoryEntry.getOlatResource().getResourceableId() + ")", e); + } } private static class OrderComparator implements Comparator<CourseNodeConfiguration> { diff --git a/src/main/java/org/olat/course/nodes/cp/CPEditController.java b/src/main/java/org/olat/course/nodes/cp/CPEditController.java index dafee967041ebaa5791fe416bb6fe5b2ccb0fb54..be28f0cdc414f3f5ab2388a1708354b4400c81dd 100644 --- a/src/main/java/org/olat/course/nodes/cp/CPEditController.java +++ b/src/main/java/org/olat/course/nodes/cp/CPEditController.java @@ -220,7 +220,7 @@ public class CPEditController extends ActivateableTabbableDefaultController impl stackPanel.pushController(translate("preview.cp"), previewCtr); } } else if (source == editLink) { - CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, cpNode); + CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, getWindowControl(), cpNode); } } diff --git a/src/main/java/org/olat/course/nodes/feed/FeedNodeEditController.java b/src/main/java/org/olat/course/nodes/feed/FeedNodeEditController.java index 68ba91fc7ef0728cd61d64adbc582ced7a079bfb..732e30ed15ad49840272014667fe07eeae4aa1fe 100644 --- a/src/main/java/org/olat/course/nodes/feed/FeedNodeEditController.java +++ b/src/main/java/org/olat/course/nodes/feed/FeedNodeEditController.java @@ -243,7 +243,7 @@ public abstract class FeedNodeEditController extends ActivateableTabbableDefault } } else if (source == editLink) { - CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, node); + CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, getWindowControl(), node); } } diff --git a/src/main/java/org/olat/course/nodes/iq/IQEditController.java b/src/main/java/org/olat/course/nodes/iq/IQEditController.java index 97db4fc5347773d65736b43ad37c0f49bc51ac98..632100026c318a7850bd12c1b7bb9d13357ab289 100644 --- a/src/main/java/org/olat/course/nodes/iq/IQEditController.java +++ b/src/main/java/org/olat/course/nodes/iq/IQEditController.java @@ -455,7 +455,7 @@ public class IQEditController extends ActivateableTabbableDefaultController impl //not found } else { RepositoryHandler typeToEdit = RepositoryHandlerFactory.getInstance().getRepositoryHandler(re); - correctQTIcontroller = typeToEdit.createEditorController(re, ureq, getWindowControl()); + correctQTIcontroller = typeToEdit.createEditorController(re, ureq, getWindowControl(), null); getWindowControl().pushToMainArea(correctQTIcontroller.getInitialComponent()); listenTo(correctQTIcontroller); } @@ -556,7 +556,7 @@ public class IQEditController extends ActivateableTabbableDefaultController impl cmc.activate(); } else if (source == editTestButton) { - CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, courseNode); + CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, getWindowControl(), courseNode); } } diff --git a/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java b/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java index 0234ea88b8f652f8a938a227634f4fd4e65a7a1b..4b00332a7a6c9d582d759b8894ef06bb208f54e7 100644 --- a/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java +++ b/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java @@ -185,7 +185,7 @@ public class PortfolioConfigForm extends FormBasicController { } cmc.activate(); } else if (source == editMapLink) { - CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, courseNode); + CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, getWindowControl(), courseNode); } else if (source == previewMapLink) { EPSecurityCallback secCallback = new EPSecurityCallbackImpl(false, true); diff --git a/src/main/java/org/olat/course/run/RunMainController.java b/src/main/java/org/olat/course/run/RunMainController.java index 5269c57fa7cdc7ea0db628f51ba3f0241e4f8cb3..e101af6649e7fd371f2fbb8fcca9d06a02347694 100644 --- a/src/main/java/org/olat/course/run/RunMainController.java +++ b/src/main/java/org/olat/course/run/RunMainController.java @@ -120,6 +120,8 @@ import org.olat.course.assessment.UserEfficiencyStatement; import org.olat.course.assessment.manager.UserCourseInformationsManager; import org.olat.course.config.CourseConfig; import org.olat.course.config.CourseConfigEvent; +import org.olat.course.config.ui.CourseOptionsController; +import org.olat.course.config.ui.courselayout.CourseLayoutGeneratorController; import org.olat.course.db.CourseDBManager; import org.olat.course.db.CustomDBMainController; import org.olat.course.editor.EditorMainController; @@ -152,7 +154,10 @@ import org.olat.repository.RepositoryEntryStatus; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.EntryChangedEvent; +import org.olat.repository.ui.author.AuthoringEditAccessController; import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.author.CatalogSettingsController; +import org.olat.repository.ui.author.RepositoryEditDescriptionController; import org.olat.repository.ui.list.RepositoryEntryDetailsController; import org.olat.resource.OLATResource; import org.olat.util.logging.activity.LoggingResourceable; @@ -181,11 +186,17 @@ public class RunMainController extends MainLayoutBasicController implements Gene private MenuTree luTree; //tools private Link previousLink, nextLink, - runLink, editLink, editSettingsLink, areaLink, folderLink, - surveyStatisticLink, testStatisticLink, courseStatisticLink, - userMgmtLink, archiverLink, assessmentLink, dbLink, + //tools + editLink, folderLink, + userMgmtLink, assessmentLink, archiverLink, + courseStatisticLink, surveyStatisticLink, testStatisticLink, + areaLink, dbLink, bookingLink, + //settings + editDescriptionLink, accessLink, catalogLink, + layoutLink, optionsLink, + //my course efficiencyStatementsLink, bookmarkLink, calendarLink, detailsLink, noteLink, chatLink; - private Dropdown editTools; + private Dropdown settings, tools, myCourse; private Panel contentP; @@ -194,6 +205,10 @@ public class RunMainController extends MainLayoutBasicController implements Gene private LayoutMain3ColsController columnLayoutCtr; private Controller currentToolCtr; + private CourseOptionsController optionsToolCtr; + private AuthoringEditAccessController accessToolCtr; + + private Controller currentNodeController; // the currently open node config private TooledStackedPanel toolbarPanel; private String launchdFromBusinessPath; @@ -353,7 +368,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene toolbarPanel.pushController(courseTitle, columnLayoutCtr); // init the menu and tool controller - initToolController(); + initToolbar(); coursemain = createVelocityContainer("index"); coursemain.setDomReplaceable(false); @@ -549,56 +564,67 @@ public class RunMainController extends MainLayoutBasicController implements Gene } // Links in editTools dropdown - if(runLink == source) { - toolbarPanel.popUpToRootController(ureq); - addCustomCSS(ureq); - currentToolCtr = null; - editTools.setActiveLink(runLink); - } else if(editLink == source) { + if(editLink == source) { launchEdit(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(editLink); - } else if(editSettingsLink == source) { - launchEditSettings(ureq); - removeCustomCSS(ureq); - editTools.setActiveLink(editSettingsLink); + tools.setActiveLink(editLink); } else if(userMgmtLink == source) { launchMembersManagement(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(userMgmtLink); + tools.setActiveLink(userMgmtLink); } else if(archiverLink == source) { launchArchive(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(archiverLink); + tools.setActiveLink(archiverLink); } else if(assessmentLink == source) { launchAssessmentTool(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(assessmentLink); + tools.setActiveLink(assessmentLink); } else if(testStatisticLink == source) { launchAssessmentStatistics(ureq, "command.openteststatistic", "TestStatistics", QTIType.test, QTIType.onyx); removeCustomCSS(ureq); - editTools.setActiveLink(testStatisticLink); + tools.setActiveLink(testStatisticLink); } else if (surveyStatisticLink == source) { launchAssessmentStatistics(ureq, "command.opensurveystatistic", "SurveyStatistics", QTIType.survey); removeCustomCSS(ureq); - editTools.setActiveLink(surveyStatisticLink); + tools.setActiveLink(surveyStatisticLink); } else if(courseStatisticLink == source) { launchStatistics(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(courseStatisticLink); + tools.setActiveLink(courseStatisticLink); } else if(dbLink == source) { launchDbs(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(dbLink); + tools.setActiveLink(dbLink); } else if(folderLink == source) { launchCourseFolder(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(folderLink); + tools.setActiveLink(folderLink); } else if(areaLink == source) { launchCourseAreas(ureq); removeCustomCSS(ureq); - editTools.setActiveLink(areaLink); - + tools.setActiveLink(areaLink); + //links as settings + } else if(editDescriptionLink == source) { + launchEditSettings(ureq); + removeCustomCSS(ureq); + settings.setActiveLink(editDescriptionLink); + } else if(accessLink == source) { + launchAccess(ureq); + removeCustomCSS(ureq); + settings.setActiveLink(accessLink); + } else if(catalogLink == source) { + launchCatalog(ureq); + removeCustomCSS(ureq); + settings.setActiveLink(catalogLink); + } else if(layoutLink == source) { + launchLayout(ureq); + removeCustomCSS(ureq); + settings.setActiveLink(layoutLink); + } else if(optionsLink == source) { + launchOptions(ureq); + removeCustomCSS(ureq); + settings.setActiveLink(optionsLink); // links as dedicated tools } else if(efficiencyStatementsLink == source) { launchEfficiencyStatements(ureq); @@ -644,7 +670,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene if(event instanceof PopEvent) { PopEvent pop = (PopEvent)event; if(pop.getController() == currentToolCtr) { - eventDone(ureq); + toolCtrDone(ureq); } } else if (event == Event.CLOSE_EVENT) { // Navigate beyond the stack, our own layout has been popped - close this tab @@ -692,10 +718,13 @@ public class RunMainController extends MainLayoutBasicController implements Gene setCustomCSS(null); } - private void eventDone(UserRequest ureq) { + private void toolCtrDone(UserRequest ureq) { // release current node controllers resources and do cleanup removeAsListenerAndDispose(currentToolCtr); currentToolCtr = null; + accessToolCtr = null; + optionsToolCtr = null; + addCustomCSS(ureq); if (isInEditor) { @@ -714,7 +743,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene updateTreeAndContent(ureq, null, null); // and also tools (maybe new assessable nodes -> show efficiency // statment) - initToolController(); + initToolbar(); } } } @@ -730,17 +759,28 @@ public class RunMainController extends MainLayoutBasicController implements Gene needsRebuildAfter = false; } - // event from the current tool (editor, groupmanagement, archiver) - if (source == currentToolCtr) { + // event from the current tool (editor, groupmanagement, archiver) + + if(optionsToolCtr == source) { + if(event == Event.CHANGED_EVENT) { + initToolbar(); + toolCtrDone(ureq); + } + } else if(accessToolCtr == source) { + if(event == Event.CHANGED_EVENT) { + initToolbar(); + toolCtrDone(ureq); + } + } else if (currentToolCtr == source) { if (event == Event.DONE_EVENT) { // special check for editor - eventDone(ureq); + toolCtrDone(ureq); } } else if (source == toolbarPanel) { if(event instanceof PopEvent) { PopEvent pop = (PopEvent)event; if(pop.getController() == currentToolCtr) { - eventDone(ureq); + toolCtrDone(ureq); } } } else if (source == currentNodeController) { @@ -794,32 +834,6 @@ public class RunMainController extends MainLayoutBasicController implements Gene } } - private void launchEdit(UserRequest ureq) { - if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { - if(!(currentToolCtr instanceof EditorMainController)) { - //pop and push happens in this method - EditorMainController ec = CourseFactory.createEditorController(ureq, getWindowControl(), toolbarPanel, course, currentCourseNode); - //user activity logger which was initialized with course run - if(ec != null){ - currentToolCtr = ec; - listenTo(currentToolCtr); - isInEditor = true; - } - } - } - } - - private void launchEditSettings(UserRequest ureq) { - if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { - if(!(currentToolCtr instanceof AuthoringEditEntrySettingsController)) { - toolbarPanel.popUpToRootController(ureq); - //push happen in the init method of the controller - currentToolCtr = new AuthoringEditEntrySettingsController(ureq, getWindowControl(), toolbarPanel, courseRepositoryEntry); - listenTo(currentToolCtr); - } - } - } - private void doNext(UserRequest ureq) { List<TreeNode> flatList = new ArrayList<>(); TreeNode currentNode = luTree.getSelectedNode(); @@ -920,6 +934,73 @@ public class RunMainController extends MainLayoutBasicController implements Gene updateNextPrevious(); } + private void launchAccess(UserRequest ureq) { + if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { + toolbarPanel.popUpToRootController(ureq); + currentToolCtr = accessToolCtr = new AuthoringEditAccessController(ureq, getWindowControl(), courseRepositoryEntry); + toolbarPanel.pushController(translate("command.access"), currentToolCtr); + } + } + + private void launchCatalog(UserRequest ureq) { + if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { + toolbarPanel.popUpToRootController(ureq); + currentToolCtr = new CatalogSettingsController(ureq, getWindowControl(), toolbarPanel, courseRepositoryEntry); + listenTo(currentToolCtr); + } + } + + private void launchEdit(UserRequest ureq) { + if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { + if(!(currentToolCtr instanceof EditorMainController)) { + //pop and push happens in this method + EditorMainController ec = CourseFactory.createEditorController(ureq, getWindowControl(), toolbarPanel, course, currentCourseNode); + //user activity logger which was initialized with course run + if(ec != null){ + currentToolCtr = ec; + listenTo(currentToolCtr); + isInEditor = true; + } + } + } + } + + private void launchEditSettings(UserRequest ureq) { + if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { + if(!(currentToolCtr instanceof AuthoringEditEntrySettingsController)) { + toolbarPanel.popUpToRootController(ureq); + //push happen in the init method of the controller + currentToolCtr = new RepositoryEditDescriptionController(ureq, getWindowControl(), courseRepositoryEntry, false); + listenTo(currentToolCtr); + toolbarPanel.pushController(translate("command.settings"), currentToolCtr); + } + } + } + + private void launchLayout(UserRequest ureq) { + if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { + toolbarPanel.popUpToRootController(ureq); + + boolean managedLayout = RepositoryEntryManagedFlag.isManaged(courseRepositoryEntry, RepositoryEntryManagedFlag.layout); + CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig().clone(); + currentToolCtr = new CourseLayoutGeneratorController(ureq, getWindowControl(), course, courseConfig, + course.getCourseEnvironment(), !managedLayout); + toolbarPanel.pushController(translate("command.layout"), currentToolCtr); + } + } + + private void launchOptions(UserRequest ureq) { + if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { + toolbarPanel.popUpToRootController(ureq); + + CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig().clone(); + boolean managedFolder = RepositoryEntryManagedFlag.isManaged(courseRepositoryEntry, RepositoryEntryManagedFlag.resourcefolder); + currentToolCtr = optionsToolCtr = new CourseOptionsController(ureq, getWindowControl(), course, courseConfig, !managedFolder); + toolbarPanel.pushController(translate("command.options"), currentToolCtr); + + } + } + private void launchArchive(UserRequest ureq) { if (hasCourseRight(CourseRights.RIGHT_ARCHIVING) || isCourseAdmin) { toolbarPanel.popUpToRootController(ureq); @@ -1300,7 +1381,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene // 2) reinitialize the users roles and rights reloadUserRolesAndRights(identity); // 3) rebuild toolboxes with link to groups and tools - initToolController(); + initToolbar(); needsRebuildAfterRunDone = true; } else if (bgme.getCommand().equals(BusinessGroupModifiedEvent.GROUPRIGHTS_MODIFIED_EVENT)) { // check if this affects a right group where the user does participate. @@ -1313,7 +1394,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene // 2) reinitialize the users roles and rights reloadUserRolesAndRights(identity); // 3) rebuild toolboxes with link to groups and tools - initToolController(); + initToolbar(); } } } @@ -1336,17 +1417,18 @@ public class RunMainController extends MainLayoutBasicController implements Gene * @param ureq * @return ToolController */ - private void initToolController() { + private void initToolbar() { CourseConfig cc = uce.getCourseEnvironment().getCourseConfig(); toolbarPanel.removeAllTools(); - initEditionTools(); - initGroupTools(); + initTools(); + initSettings(); + initToolsMyCourse(cc); initGeneralTools(cc); } - private void initEditionTools() { + private void initTools() { boolean managed = RepositoryEntryManagedFlag.isManaged(courseRepositoryEntry, RepositoryEntryManagedFlag.editcontent); // 1) administrative tools if (isCourseAdmin || isCourseCoach || hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) @@ -1354,44 +1436,42 @@ public class RunMainController extends MainLayoutBasicController implements Gene || hasCourseRight(CourseRights.RIGHT_STATISTICS) || hasCourseRight(CourseRights.RIGHT_DB) || hasCourseRight(CourseRights.RIGHT_ASSESSMENT)) { - editTools = new Dropdown("editTools", "header.tools", false, getTranslator()); - editTools.setElementCssClass("o_sel_course_tools"); - editTools.setIconCSS("o_icon o_icon_tools"); - - runLink = LinkFactory.createToolLink("run.cmd", translate("command.run"), this, "o_icon_courserun"); - runLink.setElementCssClass("o_sel_course_run"); - editTools.addComponent(runLink); + tools = new Dropdown("editTools", "header.tools", false, getTranslator()); + tools.setElementCssClass("o_sel_course_tools"); + tools.setIconCSS("o_icon o_icon_tools"); if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { editLink = LinkFactory.createToolLink("edit.cmd", translate("command.openeditor"), this, "o_icon_courseeditor"); editLink.setElementCssClass("o_sel_course_editor"); editLink.setEnabled(!managed); - editTools.addComponent(editLink); - editSettingsLink = LinkFactory.createToolLink("settings.cmd", translate("command.settings"), this, "o_icon_settings"); - editSettingsLink.setElementCssClass("o_sel_course_settings"); - editSettingsLink.setEnabled(!managed); - editTools.addComponent(editSettingsLink); + tools.addComponent(editLink); + folderLink = LinkFactory.createToolLink("cfd", translate("command.coursefolder"), this, "o_icon_coursefolder"); folderLink.setElementCssClass("o_sel_course_folder"); folderLink.setEnabled(!managed); - editTools.addComponent(folderLink); + tools.addComponent(folderLink); + tools.addComponent(new Spacer("")); } - editTools.addComponent(new Spacer("")); + if (hasCourseRight(CourseRights.RIGHT_GROUPMANAGEMENT) || isCourseAdmin) { userMgmtLink = LinkFactory.createToolLink("unifiedusermngt", translate("command.opensimplegroupmngt"), this, "o_icon_membersmanagement"); - editTools.addComponent(userMgmtLink); + tools.addComponent(userMgmtLink); } if (hasCourseRight(CourseRights.RIGHT_ASSESSMENT) || isCourseCoach || isCourseAdmin) { assessmentLink = LinkFactory.createToolLink("assessment",translate("command.openassessment"), this, "o_icon_assessment_tool"); - editTools.addComponent(assessmentLink); + tools.addComponent(assessmentLink); } - if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { - areaLink = LinkFactory.createToolLink("careas", translate("command.courseareas"), this, "o_icon_courseareas"); - areaLink.setElementCssClass("o_sel_course_areas"); - areaLink.setEnabled(!managed); - editTools.addComponent(areaLink); + if (hasCourseRight(CourseRights.RIGHT_ARCHIVING) || isCourseAdmin) { + archiverLink = LinkFactory.createToolLink("archiver", translate("command.openarchiver"), this, "o_icon_archive_tool"); + tools.addComponent(archiverLink); + } + tools.addComponent(new Spacer("")); + + + if (hasCourseRight(CourseRights.RIGHT_STATISTICS) || isCourseAdmin) { + courseStatisticLink = LinkFactory.createToolLink("statistic",translate("command.openstatistic"), this, "o_icon_statistics_tool"); + tools.addComponent(courseStatisticLink); } - editTools.addComponent(new Spacer("")); if (hasCourseRight(CourseRights.RIGHT_STATISTICS) || isCourseAdmin || isCourseCoach) { final AtomicInteger testNodes = new AtomicInteger(); final AtomicInteger surveyNodes = new AtomicInteger(); @@ -1407,42 +1487,118 @@ public class RunMainController extends MainLayoutBasicController implements Gene }, course.getRunStructure().getRootNode(), true).visitAll(); if(testNodes.intValue() > 0) { testStatisticLink = LinkFactory.createToolLink("qtistatistic", translate("command.openteststatistic"), this, "o_icon_statistics_tool"); - editTools.addComponent(testStatisticLink); + tools.addComponent(testStatisticLink); } if(surveyNodes.intValue() > 0) { surveyStatisticLink = LinkFactory.createToolLink("qtistatistic", translate("command.opensurveystatistic"), this, "o_icon_statistics_tool"); - editTools.addComponent(surveyStatisticLink); + tools.addComponent(surveyStatisticLink); } } - if (hasCourseRight(CourseRights.RIGHT_STATISTICS) || isCourseAdmin) { - courseStatisticLink = LinkFactory.createToolLink("statistic",translate("command.openstatistic"), this, "o_icon_statistics_tool"); - editTools.addComponent(courseStatisticLink); - } - editTools.addComponent(new Spacer("")); - if (hasCourseRight(CourseRights.RIGHT_ARCHIVING) || isCourseAdmin) { - archiverLink = LinkFactory.createToolLink("archiver", translate("command.openarchiver"), this, "o_icon_archive_tool"); - editTools.addComponent(archiverLink); + tools.addComponent(new Spacer("")); + + if (hasCourseRight(CourseRights.RIGHT_COURSEEDITOR) || isCourseAdmin) { + areaLink = LinkFactory.createToolLink("careas", translate("command.courseareas"), this, "o_icon_courseareas"); + areaLink.setElementCssClass("o_sel_course_areas"); + areaLink.setEnabled(!managed); + tools.addComponent(areaLink); } if (CourseDBManager.getInstance().isEnabled() && (hasCourseRight(CourseRights.RIGHT_DB) || isCourseAdmin)) { dbLink = LinkFactory.createToolLink("customDb",translate("command.opendb"), this, "o_icon_coursedb"); - editTools.addComponent(dbLink); + tools.addComponent(dbLink); } - toolbarPanel.addTool(editTools, Align.left, true); - editTools.setActiveLink(runLink); + toolbarPanel.addTool(tools, Align.left, true); } } - private void initGroupTools() { - Dropdown groupTools = new Dropdown("editTools", "header.tools.participatedGroups", false, getTranslator()); - groupTools.setIconCSS("o_icon o_icon_group"); + private void initSettings() { + if (isCourseAdmin || hasCourseRight(CourseRights.RIGHT_COURSEEDITOR)) { + boolean managed = RepositoryEntryManagedFlag.isManaged(courseRepositoryEntry, RepositoryEntryManagedFlag.editcontent); + + settings = new Dropdown("settings", "header.settings", false, getTranslator()); + settings.setElementCssClass("o_sel_course_settings"); + settings.setIconCSS("o_icon o_icon_customize"); + + editDescriptionLink = LinkFactory.createToolLink("settings.cmd", translate("command.settings"), this, "o_icon_settings"); + editDescriptionLink.setElementCssClass("o_sel_course_settings"); + editDescriptionLink.setEnabled(!managed); + settings.addComponent(editDescriptionLink); + + accessLink = LinkFactory.createToolLink("access.cmd", translate("command.access"), this, "o_icon_password"); + accessLink.setElementCssClass("o_sel_course_access"); + accessLink.setEnabled(!managed); + settings.addComponent(accessLink); + + catalogLink = LinkFactory.createToolLink("access.cmd", translate("command.catalog"), this, "o_icon_catalog"); + catalogLink.setElementCssClass("o_sel_course_catalog"); + catalogLink.setEnabled(!managed); + settings.addComponent(catalogLink); + + settings.addComponent(new Spacer("")); + + layoutLink = LinkFactory.createToolLink("access.cmd", translate("command.layout"), this, "o_icon_layout"); + layoutLink.setElementCssClass("o_sel_course_layout"); + layoutLink.setEnabled(!managed); + settings.addComponent(layoutLink); + + optionsLink = LinkFactory.createToolLink("access.cmd", translate("command.options"), this, "o_icon_options"); + optionsLink.setElementCssClass("o_sel_course_options"); + optionsLink.setEnabled(!managed); + settings.addComponent(optionsLink); + + toolbarPanel.addTool(settings, Align.left, true); + } + } + + private void initToolsMyCourse(CourseConfig cc) { + myCourse = new Dropdown("myCourse", "header.tools.participatedGroups", false, getTranslator()); + myCourse.setIconCSS("o_icon o_icon_user"); + + // Personal tools on right side + if (course.hasAssessableNodes() && !isGuest) { + // link to efficiency statements should + // - not appear when not configured in course configuration + // - not appear when configured in course configuration but no assessable + // node exist + // - appear but dimmed when configured, assessable node exist but no + // assessment data exists for user + // - appear as link when configured, assessable node exist and assessment + // data exists for user + efficiencyStatementsLink = LinkFactory.createToolLink("efficiencystatement",translate("command.efficiencystatement"), this, "o_icon_certificate"); + efficiencyStatementsLink.setPopup(new LinkPopupSettings(750, 800, "eff")); + efficiencyStatementsLink.setVisible(cc.isEfficencyStatementEnabled()); + myCourse.addComponent(efficiencyStatementsLink); + if(cc.isEfficencyStatementEnabled()) { + UserEfficiencyStatement es = efficiencyStatementManager + .getUserEfficiencyStatementLight(courseRepositoryEntry.getKey(), getIdentity()); + efficiencyStatementsLink.setEnabled(es != null); + } + } + + if (!isGuest) { + noteLink = LinkFactory.createToolLink("personalnote",translate("command.personalnote"), this, "o_icon_notes"); + noteLink.setPopup(new LinkPopupSettings(750, 550, "notes")); + myCourse.addComponent(noteLink); + } + + if (offerBookmark && !isGuest) { + boolean marked = markManager.isMarked(courseRepositoryEntry, getIdentity(), null); + String css = marked ? Mark.MARK_CSS_ICON : Mark.MARK_ADD_CSS_ICON; + bookmarkLink = LinkFactory.createToolLink("bookmark",translate("command.bookmark"), this, css); + bookmarkLink.setTitle(translate(marked ? "details.bookmark.remove" : "details.bookmark")); + myCourse.addComponent(bookmarkLink); + } + + if(myCourse.size() > 0 && (uce.getCoachedGroups().size() > 0 || uce.getParticipatingGroups().size() > 0 || uce.getWaitingLists().size() > 0)) { + myCourse.addComponent(new Spacer("")); + } // 2) add coached groups if (uce.getCoachedGroups().size() > 0) { for (BusinessGroup group:uce.getCoachedGroups()) { Link link = LinkFactory.createToolLink(CMD_START_GROUP_PREFIX + group.getKey(), StringHelper.escapeHtml(group.getName()), this); - groupTools.addComponent(link); + myCourse.addComponent(link); } } @@ -1450,7 +1606,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene if (uce.getParticipatingGroups().size() > 0) { for (BusinessGroup group: uce.getParticipatingGroups()) { Link link = LinkFactory.createToolLink(CMD_START_GROUP_PREFIX + group.getKey(), StringHelper.escapeHtml(group.getName()), this); - groupTools.addComponent(link); + myCourse.addComponent(link); } } @@ -1461,12 +1617,11 @@ public class RunMainController extends MainLayoutBasicController implements Gene String name = StringHelper.escapeHtml(group.getName()) + " (" + pos + ")"; Link link = LinkFactory.createToolLink(CMD_START_GROUP_PREFIX + group.getKey(), name, this); link.setEnabled(false); - groupTools.addComponent(link); + myCourse.addComponent(link); } } - - if(groupTools.size() > 0) { - toolbarPanel.addTool(groupTools, Align.left); + if(myCourse.size() > 0) { + toolbarPanel.addTool(myCourse, Align.right); } } @@ -1493,41 +1648,6 @@ public class RunMainController extends MainLayoutBasicController implements Gene toolbarPanel.addTool(chatLink); } - // Personal tools on right side - if (course.hasAssessableNodes() && !isGuest) { - // link to efficiency statements should - // - not appear when not configured in course configuration - // - not appear when configured in course configuration but no assessable - // node exist - // - appear but dimmed when configured, assessable node exist but no - // assessment data exists for user - // - appear as link when configured, assessable node exist and assessment - // data exists for user - efficiencyStatementsLink = LinkFactory.createToolLink("efficiencystatement",translate("command.efficiencystatement"), this, "o_icon_certificate"); - efficiencyStatementsLink.setPopup(new LinkPopupSettings(750, 800, "eff")); - efficiencyStatementsLink.setVisible(cc.isEfficencyStatementEnabled()); - toolbarPanel.addTool(efficiencyStatementsLink, Align.right); - if(cc.isEfficencyStatementEnabled()) { - UserEfficiencyStatement es = efficiencyStatementManager - .getUserEfficiencyStatementLight(courseRepositoryEntry.getKey(), getIdentity()); - efficiencyStatementsLink.setEnabled(es != null); - } - } - - if (!isGuest) { - noteLink = LinkFactory.createToolLink("personalnote",translate("command.personalnote"), this, "o_icon_notes"); - noteLink.setPopup(new LinkPopupSettings(750, 550, "notes")); - toolbarPanel.addTool(noteLink, Align.right); - } - - if (offerBookmark && !isGuest) { - boolean marked = markManager.isMarked(courseRepositoryEntry, getIdentity(), null); - String css = marked ? Mark.MARK_CSS_ICON : Mark.MARK_ADD_CSS_ICON; - bookmarkLink = LinkFactory.createToolLink("bookmark",translate("command.bookmark"), this, css); - bookmarkLink.setTitle(translate(marked ? "details.bookmark.remove" : "details.bookmark")); - toolbarPanel.addTool(bookmarkLink, Align.right); - } - // new toolbox 'general' previousLink = LinkFactory.createToolLink("previouselement","", this, "o_icon_previous_toolbar"); previousLink.setTitle(translate("command.previous")); @@ -1539,23 +1659,18 @@ public class RunMainController extends MainLayoutBasicController implements Gene } private void updateCurrentUserCount() { - if (currentUserCountLink == null) { - // either called from event handler, not initialized yet - // or display of user count is not configured - return; - } + if (currentUserCountLink == null) { + // either called from event handler, not initialized yet + // or display of user count is not configured + return; + } - currentUserCount = CoordinatorManager.getInstance().getCoordinator().getEventBus().getListeningIdentityCntFor(courseRunOres); - if(currentUserCount == 0) { - currentUserCount = 1; - } + currentUserCount = CoordinatorManager.getInstance().getCoordinator().getEventBus().getListeningIdentityCntFor(courseRunOres); + if(currentUserCount == 0) { + currentUserCount = 1; + } - currentUserCountLink.setCustomDisplayText( - translate( - "participants.in.course", - new String[]{String.valueOf(currentUserCount)} - ) - ); + currentUserCountLink.setCustomDisplayText(translate("participants.in.course", new String[]{String.valueOf(currentUserCount)})); } /** diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties index 1fac07e7c313ddf5b4b4a37db048f4e82dd63ebf..89992ce5faf00ba9471dd65a540f7cfb4b5a7442 100644 --- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties @@ -1,12 +1,14 @@ #Mon Mar 02 09:54:04 CET 2009 +command.access=Zugangskonfiguration command.bookmark=Bookmark command.calendar=Kalender +command.catalog=Katalogeinträge command.close=Kurs schliessen command.close.courseconfig=Einstellungen schliessen command.courseareas=Lernbereich command.coursechat=Kurs-Chat command.coursechatlog=Log von Chat -command.courseconfig=Detailansicht +command.courseconfig=Kursinfo command.coursefolder=Ablageordner command.efficiencystatement=Leistungsnachweis command.glossary=Glossar @@ -15,6 +17,7 @@ command.glossary.off=aus command.glossary.off.alt=Glossarbegriffe von Lerninhalt ausblenden command.glossary.on=ein command.glossary.on.alt=Glossarbegriffe in Lerninhalt einblenden +command.layout=Layout command.next=Weiter zur nächsten Seite command.openarchiver=Datenarchivierung command.openassessment=Bewertungswerkzeug @@ -26,6 +29,7 @@ command.openrightmngt=Rechtemanagement command.openstatistic=Kurs Statistiken command.openteststatistic=Test Statistiken command.opensurveystatistic=Fragebogen Statistiken +command.options=Optionen command.personalnote=Notizen command.previous=Zur\u00FCck zur letzten Seite command.run=Kurs Laufzeitumgebung @@ -44,7 +48,8 @@ error.editoralreadylocked=Der Kurs wird momentan von {0} editiert und ist deshal error.invalid.group=Sie wurden aus dieser Gruppe ausgetragen. Die Gruppe kann nicht mehr angezeigt werden. error.noglossary=Das Glossar ist zurzeit nicht verf\u00FCgbar. error.accesscontrol=Zugang verweigert -header.tools=Kurswerkzeuge +header.tools=Werkzeuge +header.settings=Einstellungen header.tools.general=Allgemeines header.tools.ownerGroups=Betreute Gruppen header.tools.participatedGroups=Meine Gruppen diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties index 4ef4009c6bb90e33fb84072152618de5b6a0d8a7..94d33be346a08247eba96fa2491f7ffbfb50d90c 100644 --- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties @@ -6,7 +6,7 @@ command.close.courseconfig=Close settings command.courseareas=Learning areas command.coursechat=Course chat command.coursechatlog=Chat log -command.courseconfig=Detailed view +command.courseconfig=Course infos command.coursefolder=Storage folder command.efficiencystatement=Evidence of achievement command.glossary=Glossary @@ -44,7 +44,8 @@ error.accesscontrol=Access refused error.editoralreadylocked=This course is being edited by {0} and therefore locked. error.invalid.group=You have been removed from this group as group member. This group cannot be displayed anymore. error.noglossary=This glossary is currently not available -header.tools=Course tools +header.tools=Tools +header.settings=Settings header.tools.general=General header.tools.ownerGroups=Tutored groups header.tools.participatedGroups=My groups diff --git a/src/main/java/org/olat/course/site/CourseSiteContextEntryControllerCreator.java b/src/main/java/org/olat/course/site/CourseSiteContextEntryControllerCreator.java index 828663fe3c09a65cb57984dc4c5219b629a12d70..b6201302e88c01c3d24825d2091ac21ccae09c63 100644 --- a/src/main/java/org/olat/course/site/CourseSiteContextEntryControllerCreator.java +++ b/src/main/java/org/olat/course/site/CourseSiteContextEntryControllerCreator.java @@ -22,20 +22,28 @@ package org.olat.course.site; import java.util.List; import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.dtabs.Activateable2; +import org.olat.core.gui.control.generic.layout.MainLayoutController; +import org.olat.core.gui.control.generic.messages.MessageUIFactory; import org.olat.core.gui.control.navigation.SiteDefinition; import org.olat.core.gui.control.navigation.SiteDefinitions; +import org.olat.core.gui.translator.Translator; import org.olat.core.id.OLATResourceable; -import org.olat.core.id.Roles; +import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.ContextEntryControllerCreator; import org.olat.core.id.context.DefaultContextEntryControllerCreator; +import org.olat.core.logging.AssertException; +import org.olat.core.util.Util; import org.olat.course.site.model.CourseSiteConfiguration; import org.olat.course.site.model.LanguageConfiguration; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; +import org.olat.repository.RepositoryService; import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; import org.olat.repository.ui.RepositoyUIFactory; @@ -67,25 +75,56 @@ public class CourseSiteContextEntryControllerCreator extends DefaultContextEntry */ @Override public Controller createController(List<ContextEntry> ces, UserRequest ureq, WindowControl wControl) { - Controller ctrl = null;; RepositoryEntry re = getRepositoryEntry(ureq, ces.get(0)); - if(ces.size() > 1) { - ContextEntry subcontext = ces.get(1); - if("Editor".equals(subcontext.getOLATResourceable().getResourceableTypeName())) { - RepositoryHandler handler = RepositoryHandlerFactory.getInstance().getRepositoryHandler(re); - if(handler != null && handler.supportsEdit(re.getOlatResource()) && isAllowedToEdit(ureq, re)) { - ctrl = handler.createEditorController(re, ureq, wControl); - } - } + Controller ctrl = createLaunchController(re, ureq, wControl); + if(ctrl instanceof Activateable2) { + ((Activateable2)ctrl).activate(ureq, ces, null); } - - return ctrl == null ? RepositoyUIFactory.createLaunchController(re, ureq, wControl) : ctrl; + return ctrl; } - private boolean isAllowedToEdit(UserRequest ureq, RepositoryEntry re) { - Roles roles = ureq.getUserSession().getRoles(); - return roles.isOLATAdmin() - || RepositoryManager.getInstance().isOwnerOfRepositoryEntry(ureq.getIdentity(), re); + /** + * Create a launch controller used to launch the given repo entry. + * @param re + * @param initialViewIdentifier if null the default view will be started, otherwise a controllerfactory type dependant view will be activated (subscription subtype) + * @param ureq + * @param wControl + * @return null if no entry was found, a no access message controller if not allowed to launch or the launch + * controller if successful. + */ + private Controller createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + if (re == null) return null; + RepositoryManager rm = RepositoryManager.getInstance(); + if (!rm.isAllowedToLaunch(ureq, re)) { + Translator trans = Util.createPackageTranslator(RepositoyUIFactory.class, ureq.getLocale()); + String text = trans.translate("launch.noaccess"); + Controller c = MessageUIFactory.createInfoMessage(ureq, wControl, null, text); + + // use on column layout + LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, c); + layoutCtr.addDisposableChildController(c); // dispose content on layout dispose + return layoutCtr; + } + + RepositoryService rs = CoreSpringFactory.getImpl(RepositoryService.class); + rs.incrementLaunchCounter(re); + RepositoryHandler handler = RepositoryHandlerFactory.getInstance().getRepositoryHandler(re); + + WindowControl bwControl; + OLATResourceable businessOres = re; + ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(businessOres); + //OLAT-5944: check if the current context entry is not already the repository entry to avoid duplicate in the business path + if(ce.equals(wControl.getBusinessControl().getCurrentContextEntry())) { + bwControl = wControl; + } else { + bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl); + } + + MainLayoutController ctrl = handler.createLaunchController(re, ureq, bwControl); + if (ctrl == null) { + throw new AssertException("could not create controller for repositoryEntry "+re); + } + return ctrl; } /** diff --git a/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java b/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java index 3831e21b38f14632dc3325e2f8568ddf3f4d0b16..91a49c89bb4256dea98bce2f2f00588fca273974 100644 --- a/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java +++ b/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java @@ -35,9 +35,8 @@ import org.olat.core.gui.components.tree.TreeEvent; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.controller.MainLayoutBasicController; +import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.iframe.DeliveryOptions; -import org.olat.core.gui.control.generic.layout.MainLayout3ColumnsController; import org.olat.core.id.OLATResourceable; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.LockResult; @@ -49,17 +48,16 @@ import org.olat.modules.cp.CPUIFactory; /** * The content packaging main edit controller. */ -public class CPEditMainController extends MainLayoutBasicController { +public class CPEditMainController extends BasicController { - private LayoutMain3ColsController columnLayoutCtr; private CPContentController contentCtr; private CPTreeController treeCtr; private final ContentPackage cp; private LockResult lock; private DeliveryOptions deliveryOptions; - private TooledStackedPanel all; - public CPEditMainController(UserRequest ureq, WindowControl wControl, VFSContainer cpContainer, OLATResourceable ores) { + public CPEditMainController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel, + VFSContainer cpContainer, OLATResourceable ores) { super(ureq, wControl); // acquire lock for resource @@ -70,85 +68,46 @@ public class CPEditMainController extends MainLayoutBasicController { if(packageConfig != null) { deliveryOptions = packageConfig.getDeliveryOptions(); } - // set up the components - all = new TooledStackedPanel("courseStackPanel", getTranslator(), this); - all.setInvisibleCrumb(-1); - putInitialPanel(all); String errorString = cp.getLastError(); if (errorString == null) { if (lock.isSuccess()) { - initDefaultView(ureq, wControl); + treeCtr = new CPTreeController(ureq, wControl, cp); + listenTo(treeCtr); + + contentCtr = new CPContentController(ureq, wControl, cp); + listenTo(contentCtr); + contentCtr.init(ureq); + + // Make tree controller aware of contentCtr in order to display pages after import. + treeCtr.setContentController(contentCtr); + + Controller columnLayoutCtr = new LayoutMain3ColsController(ureq, wControl, treeCtr.getInitialComponent(), contentCtr.getInitialComponent(), "cptestmain"); + listenTo(columnLayoutCtr); + + putInitialPanel(columnLayoutCtr.getInitialComponent()); + + stackPanel.pushController("Editor", this); + treeCtr.initToolbar(stackPanel); + contentCtr.initToolbar(stackPanel); + + if (!cp.isOLATContentPackage()) { + showWarning("maincontroller.cp.created.with.third.party.editor"); + } } else { showInfo("contentcontroller.no.lock"); - displayCP(ureq, wControl, cpContainer); + Controller cpCtr = CPUIFactory.getInstance() + .createMainLayoutController(ureq, wControl, cpContainer, true, deliveryOptions); + putInitialPanel(cpCtr.getInitialComponent()); } } else { - initErrorView(ureq, wControl); showError("maincontroller.loaderror", errorString); + Controller columnLayoutCtr = new LayoutMain3ColsController(ureq, wControl, null, new Panel("errorPanel"), "cptestmain"); + putInitialPanel(columnLayoutCtr.getInitialComponent()); } logAudit("cp editor started. oresId: " + ores.getResourceableId(), null); } - /** - * Displays the cp without being able to modify it. - * - * @param ureq - * @param wControl - * @param root - */ - private void displayCP(UserRequest ureq, WindowControl wControl, VFSContainer root) { - MainLayout3ColumnsController cpCtr = CPUIFactory.getInstance().createMainLayoutController(ureq, wControl, root, true, deliveryOptions); - putInitialPanel(cpCtr.getInitialComponent()); - } - - /** - * initializes default controllers - * - * @param ureq - * @param wControl - * @param cp - */ - private void initDefaultView(UserRequest ureq, WindowControl wControl) { - - treeCtr = new CPTreeController(ureq, wControl, cp); - listenTo(treeCtr); - - contentCtr = new CPContentController(ureq, wControl, cp); - listenTo(contentCtr); - contentCtr.init(ureq); - - // Make tree controller aware of contentCtr in order to display pages after - // import. - treeCtr.setContentController(contentCtr); - - columnLayoutCtr = new LayoutMain3ColsController(ureq, wControl, treeCtr.getInitialComponent(), contentCtr.getInitialComponent(), - "cptestmain"); - listenTo(columnLayoutCtr); // auto dispose - all.pushController("Editor", columnLayoutCtr); - - treeCtr.initToolbar(all); - contentCtr.initToolbar(all); - - if (!cp.isOLATContentPackage()) { - showWarning("maincontroller.cp.created.with.third.party.editor"); - } - } - - /** - * initializes a special view, where the user is informed about errors. (while - * loading cp) - * - * @param ureq - * @param wControl - * @param cp - */ - private void initErrorView(UserRequest ureq, WindowControl wControl) { - Panel p = new Panel("errorPanel"); - columnLayoutCtr = new LayoutMain3ColsController(ureq, wControl, null, p, "cptestmain"); - all.pushController("Editor", columnLayoutCtr); - } - @Override protected void doDispose() { Long oresId = cp.getResourcable().getResourceableId(); diff --git a/src/main/java/org/olat/ims/cp/ui/CPRuntimeController.java b/src/main/java/org/olat/ims/cp/ui/CPRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..0c2eb2b1691aefb4ace6c2faa03f6e6de6c64729 --- /dev/null +++ b/src/main/java/org/olat/ims/cp/ui/CPRuntimeController.java @@ -0,0 +1,129 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.cp.ui; + +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.dropdown.Dropdown.Spacer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.ControllerEventListener; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.iframe.DeliveryOptions; +import org.olat.core.gui.control.generic.iframe.DeliveryOptionsConfigurationController; +import org.olat.core.util.vfs.QuotaManager; +import org.olat.fileresource.FileResourceManager; +import org.olat.ims.cp.CPManager; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * The runtime add quoty management and delivery options. + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CPRuntimeController extends RepositoryEntryRuntimeController { + + private Link quotaLink, deliveryOptionsLink; + + @Autowired + private CPManager cpManager; + @Autowired + private QuotaManager quotaManager; + + public CPRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, runtimeControllerCreator); + } + + @Override + protected void initToolbar(Dropdown toolsDropdown, Dropdown settingsDropdown) { + super.initToolbar(toolsDropdown, settingsDropdown); + if (isOwner || isInstitutionalResourceManager || isOlatAdmin) { + settingsDropdown.addComponent(new Spacer("")); + + if (quotaManager.hasQuotaEditRights(getIdentity())) { + quotaLink = LinkFactory.createToolLink("quota", translate("tab.quota.edit"), this, "o_sel_repo_quota"); + quotaLink.setIconLeftCSS("o_icon o_icon-fw o_icon_quota"); + settingsDropdown.addComponent(quotaLink); + } + + deliveryOptionsLink = LinkFactory.createToolLink("layout", translate("tab.layout"), this, "o_sel_repo_layout"); + deliveryOptionsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_options"); + settingsDropdown.addComponent(deliveryOptionsLink); + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(quotaLink == source) { + doQuota(ureq); + } else if(deliveryOptionsLink == source) { + doLayout(ureq); + } else { + super.event(ureq, source, event); + } + } + + private void doQuota(UserRequest ureq) { + if (quotaManager.hasQuotaEditRights(ureq.getIdentity())) { + RepositoryEntry entry = getRepositoryEntry(); + OLATResource resource = entry.getOlatResource(); + OlatRootFolderImpl cpRoot = FileResourceManager.getInstance().unzipContainerResource(resource); + Controller quotaCtrl = quotaManager.getQuotaEditorInstance(ureq, getWindowControl(), cpRoot.getRelPath(), false); + pushController(ureq, translate("tab.quota.edit"), quotaCtrl); + } + } + + private void doLayout(UserRequest ureq) { + RepositoryEntry entry = getRepositoryEntry(); + final OLATResource resource = entry.getOlatResource(); + CPPackageConfig cpConfig = cpManager.getCPPackageConfig(resource); + DeliveryOptions config = cpConfig == null ? null : cpConfig.getDeliveryOptions(); + final DeliveryOptionsConfigurationController deliveryOptionsCtrl = new DeliveryOptionsConfigurationController(ureq, getWindowControl(), config); + + deliveryOptionsCtrl.addControllerListener(new ControllerEventListener() { + + @Override + public void dispatchEvent(UserRequest uureq, Controller source, Event event) { + if(source == deliveryOptionsCtrl + && (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT)) { + DeliveryOptions newConfig = deliveryOptionsCtrl.getDeliveryOptions(); + CPPackageConfig cConfig = cpManager.getCPPackageConfig(resource); + if(cConfig == null) { + cConfig = new CPPackageConfig(); + } + cConfig.setDeliveryOptions(newConfig); + cpManager.setCPPackageConfig(resource, cConfig); + } + } + }); + + pushController(ureq, translate("tab.layout"), deliveryOptionsCtrl); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/QTIRuntimeController.java b/src/main/java/org/olat/ims/qti/QTIRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..a9ae6945bbd1930baf9244703023f9ebdac0e37d --- /dev/null +++ b/src/main/java/org/olat/ims/qti/QTIRuntimeController.java @@ -0,0 +1,167 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.stack.VetoPopEvent; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.VetoableCloseController; +import org.olat.core.gui.control.WindowControl; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.ui.RepositoryEntryRuntimeController; + +/** + * + * This particularly overview the veto of the QTI editor + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTIRuntimeController extends RepositoryEntryRuntimeController implements VetoableCloseController { + + private Delayed delayedClose; + + public QTIRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, runtimeControllerCreator); + } + + /** + * This is only used by the QTI editor + */ + @Override + public boolean requestForClose() { + if(editorCtrl instanceof VetoableCloseController) { + return ((VetoableCloseController)editorCtrl).requestForClose(); + } + return true; + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == editorCtrl && source instanceof VetoableCloseController) { + if(event == Event.DONE_EVENT) { + if(delayedClose != null) { + switch(delayedClose) { + case access: super.doAccess(ureq); break; + case details: super.doDetails(ureq); break; + case editSettings: super.doEditSettings(ureq); break; + case catalog: super.doCatalog(ureq); break; + case members: super.doMembers(ureq); break; + case orders: super.doOrders(ureq); break; + case close: super.doClose(ureq); break; + case pop: popToRoot(ureq); cleanUp(); break; + default: {} + } + delayedClose = null; + } else { + fireEvent(ureq, Event.DONE_EVENT); + } + } + } + super.event(ureq, source, event); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(event == Event.CLOSE_EVENT) { + if(requestForClose()) { + super.event(ureq, source, event); + } else { + delayedClose = Delayed.close; + } + } else if(event instanceof VetoPopEvent) { + if(requestForClose()) { + super.event(ureq, source, event); + } else { + delayedClose = Delayed.pop; + } + } else { + super.event(ureq, source, event); + } + } + + @Override + protected void doAccess(UserRequest ureq) { + if(requestForClose()) { + super.doAccess(ureq); + } else { + delayedClose = Delayed.access; + } + } + + @Override + protected void doDetails(UserRequest ureq) { + if(requestForClose()) { + super.doDetails(ureq); + } else { + delayedClose = Delayed.details; + } + } + + @Override + protected void doEditSettings(UserRequest ureq) { + if(requestForClose()) { + super.doEditSettings(ureq); + } else { + delayedClose = Delayed.editSettings; + } + } + + @Override + protected void doCatalog(UserRequest ureq) { + if(requestForClose()) { + super.doCatalog(ureq); + } else { + delayedClose = Delayed.catalog; + } + } + + @Override + protected void doMembers(UserRequest ureq) { + if(requestForClose()) { + super.doMembers(ureq); + } else { + delayedClose = Delayed.members; + } + } + + @Override + protected void doOrders(UserRequest ureq) { + if(requestForClose()) { + super.doOrders(ureq); + } else { + delayedClose = Delayed.orders; + } + } + + private enum Delayed { + access, + details, + editSettings, + catalog, + members, + orders, + close, + pop + } +} diff --git a/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java b/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java index 3a3b83332f8a2737d4f2abf74d03fccb5cc6f945..9cddd52e8753ac90944a86da970d3398f1192380 100644 --- a/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java +++ b/src/main/java/org/olat/ims/qti/repository/handlers/QTIHandler.java @@ -31,7 +31,6 @@ import java.nio.file.PathMatcher; import java.util.Locale; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.WindowControl; @@ -47,22 +46,13 @@ import org.olat.core.util.coordinate.LockResult; import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.FileResource; import org.olat.ims.qti.editor.QTIEditorPackageImpl; -import org.olat.ims.qti.fileresource.SurveyFileResource; -import org.olat.ims.qti.process.AssessmentInstance; -import org.olat.ims.qti.process.ImsRepositoryResolver; -import org.olat.ims.qti.process.Resolver; -import org.olat.modules.iq.IQManager; -import org.olat.modules.iq.IQPreviewSecurityCallback; -import org.olat.modules.iq.IQSecurityCallback; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.handlers.FileHandler; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.resource.references.ReferenceManager; /** @@ -108,12 +98,6 @@ public abstract class QTIHandler extends FileHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - // - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { OLATResource sourceResource = source.getOlatResource(); @@ -140,21 +124,7 @@ public abstract class QTIHandler extends FileHandler { } @Override - public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { - Resolver resolver = new ImsRepositoryResolver(re); - IQSecurityCallback secCallback = new IQPreviewSecurityCallback(); - IQManager iqManager = CoreSpringFactory.getImpl(IQManager.class); - OLATResource res = re.getOlatResource(); - MainLayoutController runController = res.getResourceableTypeName().equals(SurveyFileResource.TYPE_NAME) ? - iqManager.createIQDisplayController(res, resolver, AssessmentInstance.QMD_ENTRY_TYPE_SURVEY, secCallback, ureq, wControl) : - iqManager.createIQDisplayController(res, resolver, AssessmentInstance.QMD_ENTRY_TYPE_SELF, secCallback, ureq, wControl); - // use on column layout - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, runController); - layoutCtr.addDisposableChildController(runController); // dispose content on layout dispose - - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); - return wrapper; - } + public abstract MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl); @Override public boolean readyToDelete(OLATResourceable res, Identity identity, Roles roles, Locale locale, ErrorList errors) { diff --git a/src/main/java/org/olat/ims/qti/repository/handlers/QTISurveyHandler.java b/src/main/java/org/olat/ims/qti/repository/handlers/QTISurveyHandler.java index 73eb065ac95e30b00582162a094180891580b2ae..d7b98dbfa94a748cb188069ab1e7f0e76ea2bad9 100644 --- a/src/main/java/org/olat/ims/qti/repository/handlers/QTISurveyHandler.java +++ b/src/main/java/org/olat/ims/qti/repository/handlers/QTISurveyHandler.java @@ -30,8 +30,8 @@ import java.util.List; import java.util.Locale; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -40,6 +40,7 @@ import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.AssertException; import org.olat.fileresource.types.ResourceEvaluation; +import org.olat.ims.qti.QTIRuntimeController; import org.olat.ims.qti.editor.QTIEditorMainController; import org.olat.ims.qti.fileresource.SurveyFileResource; import org.olat.ims.qti.process.AssessmentInstance; @@ -50,6 +51,8 @@ import org.olat.modules.iq.IQPreviewSecurityCallback; import org.olat.modules.iq.IQSecurityCallback; import org.olat.repository.RepositoryEntry; import org.olat.repository.controllers.WizardCloseResourceController; +import org.olat.repository.handlers.EditionSupport; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.references.ReferenceImpl; import org.olat.resource.references.ReferenceManager; @@ -115,11 +118,11 @@ public class QTISurveyHandler extends QTIHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { + public EditionSupport supportsEdit(OLATResourceable resource) { if(resource != null && OnyxModule.isOnyxTest(resource)) { - return false; + return EditionSupport.no; } - return true; + return EditionSupport.yes; } @Override @@ -135,25 +138,27 @@ public class QTISurveyHandler extends QTIHandler { */ @Override public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { - Controller runController; - OLATResource res = re.getOlatResource(); - if (OnyxModule.isOnyxTest(res)) { - runController = new OnyxRunController(ureq, wControl, re, false); - } else { - Resolver resolver = new ImsRepositoryResolver(re); - IQSecurityCallback secCallback = new IQPreviewSecurityCallback(); - runController = CoreSpringFactory.getImpl(IQManager.class) - .createIQDisplayController(res, resolver, AssessmentInstance.QMD_ENTRY_TYPE_SURVEY, secCallback, ureq, wControl); - } - - // use on column layout - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, runController); - layoutCtr.addDisposableChildController(runController); // dispose content on layout dispose - return layoutCtr; + return new QTIRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + Controller runController; + OLATResource res = entry.getOlatResource(); + if (OnyxModule.isOnyxTest(res)) { + runController = new OnyxRunController(uureq, wwControl, entry, false); + } else { + Resolver resolver = new ImsRepositoryResolver(entry); + IQSecurityCallback secCallback = new IQPreviewSecurityCallback(); + runController = CoreSpringFactory.getImpl(IQManager.class) + .createIQDisplayController(res, resolver, AssessmentInstance.QMD_ENTRY_TYPE_SURVEY, secCallback, uureq, wwControl); + } + return runController; + } + }); } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { OLATResource res = re.getOlatResource(); if (OnyxModule.isOnyxTest(res)) { return null; diff --git a/src/main/java/org/olat/ims/qti/repository/handlers/QTITestHandler.java b/src/main/java/org/olat/ims/qti/repository/handlers/QTITestHandler.java index 6c25a4524c9e7793fade713fea29b1d4a27f5dca..75a48caaa4790264e44cae465caa610854454102 100644 --- a/src/main/java/org/olat/ims/qti/repository/handlers/QTITestHandler.java +++ b/src/main/java/org/olat/ims/qti/repository/handlers/QTITestHandler.java @@ -30,8 +30,8 @@ import java.util.List; import java.util.Locale; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -40,6 +40,7 @@ import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.AssertException; import org.olat.fileresource.types.ResourceEvaluation; +import org.olat.ims.qti.QTIRuntimeController; import org.olat.ims.qti.editor.QTIEditorMainController; import org.olat.ims.qti.fileresource.TestFileResource; import org.olat.ims.qti.process.AssessmentInstance; @@ -50,6 +51,8 @@ import org.olat.modules.iq.IQPreviewSecurityCallback; import org.olat.modules.iq.IQSecurityCallback; import org.olat.repository.RepositoryEntry; import org.olat.repository.controllers.WizardCloseResourceController; +import org.olat.repository.handlers.EditionSupport; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.references.ReferenceImpl; import org.olat.resource.references.ReferenceManager; @@ -115,11 +118,11 @@ public class QTITestHandler extends QTIHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { + public EditionSupport supportsEdit(OLATResourceable resource) { if(resource != null && OnyxModule.isOnyxTest(resource)) { - return false; + return EditionSupport.no; } - return true; + return EditionSupport.yes; } @Override @@ -135,25 +138,28 @@ public class QTITestHandler extends QTIHandler { */ @Override public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { - Controller runController; - OLATResource res = re.getOlatResource(); - if (OnyxModule.isOnyxTest(res)) { - // <OLATCE-1054> - runController = new OnyxRunController(ureq, wControl, re, false); - } else { - Resolver resolver = new ImsRepositoryResolver(re); - IQSecurityCallback secCallback = new IQPreviewSecurityCallback(); - runController = CoreSpringFactory.getImpl(IQManager.class) - .createIQDisplayController(res, resolver, AssessmentInstance.QMD_ENTRY_TYPE_SELF, secCallback, ureq, wControl); - } - - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, runController); - layoutCtr.addDisposableChildController(runController); // dispose content on layout dispose - return layoutCtr; + return new QTIRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + Controller runController; + OLATResource res = entry.getOlatResource(); + if (OnyxModule.isOnyxTest(res)) { + // <OLATCE-1054> + runController = new OnyxRunController(uureq, wwControl, entry, false); + } else { + Resolver resolver = new ImsRepositoryResolver(entry); + IQSecurityCallback secCallback = new IQPreviewSecurityCallback(); + runController = CoreSpringFactory.getImpl(IQManager.class) + .createIQDisplayController(res, resolver, AssessmentInstance.QMD_ENTRY_TYPE_SELF, secCallback, uureq, wwControl); + } + return runController; + } + }); } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { OLATResource res = re.getOlatResource(); if(OnyxModule.isOnyxTest(res)) { return null; diff --git a/src/main/java/org/olat/modules/cp/CPDisplayController.java b/src/main/java/org/olat/modules/cp/CPDisplayController.java index dbb3b905613c8f373579f103da7f313d07b2fbaa..a7f77f17a75be042945db7e89c8a4a1ae078cffc 100644 --- a/src/main/java/org/olat/modules/cp/CPDisplayController.java +++ b/src/main/java/org/olat/modules/cp/CPDisplayController.java @@ -107,7 +107,7 @@ public class CPDisplayController extends BasicController implements Activateable * @param showNavigation Show the next/previous link * @param activateFirstPage */ - CPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, boolean showNavigation, + public CPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, boolean showNavigation, boolean activateFirstPage, boolean showPrint, DeliveryOptions deliveryOptions, String initialUri, OLATResourceable ores) { super(ureq, wControl); this.rootContainer = rootContainer; @@ -278,7 +278,7 @@ public class CPDisplayController extends BasicController implements Activateable * @return The menu component for this content packaging. The Controller must * be initialized properly to use this method */ - Component getMenuComponent() { + public Component getMenuComponent() { return cpTree; } diff --git a/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java b/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java index f856186efa9f8029dd99d4c2f058225dc2d83a56..8c5b6fe32adc0835302af22afc60b9bebd2bba80 100644 --- a/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java +++ b/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java @@ -230,7 +230,6 @@ public class CPOfflineReadableManager { CPManifestTreeModel ctm = new CPManifestTreeModel(manifest); TreeNode root = ctm.getRootNode(); // let's take the rootnode title as page title - String rootTitle = root.getTitle(); StringBuilder menuTreeSB = new StringBuilder(); renderMenuTreeNodeRecursively(root, menuTreeSB, 0); @@ -238,7 +237,7 @@ public class CPOfflineReadableManager { // now put values to velocityContext VelocityContext ctx = new VelocityContext(); ctx.put("menutree", menuTreeSB.toString()); - ctx.put("rootTitle", rootTitle); + ctx.put("rootTitle", root.getTitle()); ctx.put("cpoff",DIRNAME_CPOFFLINEMENUMAT); ctx.put("index", indexSrc); diff --git a/src/main/java/org/olat/modules/qpool/ui/ImportSourcesController.java b/src/main/java/org/olat/modules/qpool/ui/ImportSourcesController.java index e814ed6bcc516505d8abe832a4a61fcd0f34013a..0d1551aec895acd5d8b622271b723ba67e1ef9fb 100644 --- a/src/main/java/org/olat/modules/qpool/ui/ImportSourcesController.java +++ b/src/main/java/org/olat/modules/qpool/ui/ImportSourcesController.java @@ -47,7 +47,9 @@ public class ImportSourcesController extends BasicController { VelocityContainer mainVC = createVelocityContainer("import_sources"); importRepository = LinkFactory.createLink("import.repository", mainVC, this); + importRepository.setIconLeftCSS("o_icon o_icon-fw o_FileResource-TEST_icon"); importFile = LinkFactory.createLink("import.file", mainVC, this); + importFile.setIconLeftCSS("o_icon o_icon-fw o_filetype_file"); putInitialPanel(mainVC); } diff --git a/src/main/java/org/olat/modules/qpool/ui/QuestionItemSummaryController.java b/src/main/java/org/olat/modules/qpool/ui/QuestionItemSummaryController.java index 35269842647f71a25d57bf3ba39422c85e7cfda9..55a6c8288fa415f22fab6c7b6c06033d1467a660 100644 --- a/src/main/java/org/olat/modules/qpool/ui/QuestionItemSummaryController.java +++ b/src/main/java/org/olat/modules/qpool/ui/QuestionItemSummaryController.java @@ -85,7 +85,7 @@ public class QuestionItemSummaryController extends FormBasicController { updateItem(item, canEdit); } - public void updateItem(QuestionItem updatedItem, boolean canEdit) { + public void updateItem(QuestionItem updatedItem, boolean edit) { this.item = updatedItem; if(updatedItem == null) { canEdit = false; @@ -98,7 +98,7 @@ public class QuestionItemSummaryController extends FormBasicController { stdevDifficultyEl.setValue(""); differentiationEl.setValue(""); } else { - this.canEdit = canEdit; + canEdit = edit; subjectEl.setValue(updatedItem.getTitle()); String keywords = updatedItem.getKeywords(); keywordsEl.setValue(keywords == null ? "" : keywords); diff --git a/src/main/java/org/olat/modules/qpool/ui/ShareTargetController.java b/src/main/java/org/olat/modules/qpool/ui/ShareTargetController.java index c339a9e8f26ccad97dd34d15f043fddc7bd20532..e657a8c177967aa417028a5cbb7cc736897a5c25 100644 --- a/src/main/java/org/olat/modules/qpool/ui/ShareTargetController.java +++ b/src/main/java/org/olat/modules/qpool/ui/ShareTargetController.java @@ -45,7 +45,9 @@ public class ShareTargetController extends BasicController { VelocityContainer mainVC = createVelocityContainer("share_target"); shareGroup = LinkFactory.createLink("share.group", mainVC, this); + shareGroup.setIconLeftCSS("o_icon o_icon-fw o_icon_pool_share"); sharePool = LinkFactory.createLink("share.pool", mainVC, this); + sharePool.setIconLeftCSS("o_icon o_icon-fw o_icon_pool_pool"); putInitialPanel(mainVC); } diff --git a/src/main/java/org/olat/modules/qpool/ui/_content/collection_target.html b/src/main/java/org/olat/modules/qpool/ui/_content/collection_target.html index d4152d8c86484cc0bcbfd2461824a48a02ec2a03..3518b0332adf430459550707eeed4ef6918c0151 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_content/collection_target.html +++ b/src/main/java/org/olat/modules/qpool/ui/_content/collection_target.html @@ -1,8 +1,10 @@ -$r.render("create.list")<br/> -$r.render("add.to.list")<br/> +<ul class="o_dropdown list-unstyled"> + <li>$r.render("create.list")</li> + <li>$r.render("add.to.list")</li> #if($r.available("rename.collection")) - $r.render("rename.collection")<br/> + <li>$r.render("rename.collection")</li> #end #if($r.available("delete.collection")) - $r.render("delete.collection") -#end \ No newline at end of file + <li>$r.render("delete.collection")</li> +#end +</ul> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/qpool/ui/_content/import_sources.html b/src/main/java/org/olat/modules/qpool/ui/_content/import_sources.html index d709e4a4cca32756886ce5e33394b5577e9c5326..987227f97773479ce8efafeec87ead280b4dfd3f 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_content/import_sources.html +++ b/src/main/java/org/olat/modules/qpool/ui/_content/import_sources.html @@ -1,2 +1,4 @@ -$r.render("import.file")<br/> -$r.render("import.repository") \ No newline at end of file +<ul class="o_dropdown list-unstyled"> + <li>$r.render("import.file")</li> + <li>$r.render("import.repository")</li> +</ul> diff --git a/src/main/java/org/olat/modules/qpool/ui/_content/share_target.html b/src/main/java/org/olat/modules/qpool/ui/_content/share_target.html index 632672f25ce85acd527a7bba9cb944cfd315584f..307d8643b0782411cf075c9fd9f725a021308ed8 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_content/share_target.html +++ b/src/main/java/org/olat/modules/qpool/ui/_content/share_target.html @@ -1,2 +1,4 @@ -$r.render("share.group")<br/> -$r.render("share.pool") \ No newline at end of file +<ul class="o_dropdown list-unstyled"> + <li>$r.render("share.group")</li> + <li>$r.render("share.pool")</li> +</ul> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/scorm/ScormRuntimeController.java b/src/main/java/org/olat/modules/scorm/ScormRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..98b6e53d6e82b42ea4b8e909402a5685a951086c --- /dev/null +++ b/src/main/java/org/olat/modules/scorm/ScormRuntimeController.java @@ -0,0 +1,104 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.scorm; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.dropdown.Dropdown.Spacer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.ControllerEventListener; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.iframe.DeliveryOptions; +import org.olat.core.gui.control.generic.iframe.DeliveryOptionsConfigurationController; +import org.olat.ims.cp.CPManager; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * The runtime add delivery options. + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ScormRuntimeController extends RepositoryEntryRuntimeController { + + private Link deliveryOptionsLink; + + @Autowired + private CPManager cpManager; + + public ScormRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, runtimeControllerCreator); + } + + @Override + protected void initToolbar(Dropdown toolsDropdown, Dropdown settingsDropdown) { + super.initToolbar(toolsDropdown, settingsDropdown); + if (isOwner || isInstitutionalResourceManager || isOlatAdmin) { + settingsDropdown.addComponent(new Spacer("")); + + deliveryOptionsLink = LinkFactory.createToolLink("layout", translate("tab.layout"), this, "o_sel_repo_layout"); + deliveryOptionsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_options"); + settingsDropdown.addComponent(deliveryOptionsLink); + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(deliveryOptionsLink == source) { + doLayout(ureq); + } else { + super.event(ureq, source, event); + } + } + + private void doLayout(UserRequest ureq) { + RepositoryEntry entry = getRepositoryEntry(); + ScormPackageConfig scormConfig = ScormMainManager.getInstance().getScormPackageConfig(entry.getOlatResource()); + DeliveryOptions config = scormConfig == null ? null : scormConfig.getDeliveryOptions(); + final OLATResource resource = entry.getOlatResource(); + final DeliveryOptionsConfigurationController deliveryOptionsCtrl = new DeliveryOptionsConfigurationController(ureq, getWindowControl(), config); + + deliveryOptionsCtrl.addControllerListener(new ControllerEventListener() { + @Override + public void dispatchEvent(UserRequest uureq, Controller source, Event event) { + if(source == deliveryOptionsCtrl && (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT)) { + DeliveryOptions newConfig = deliveryOptionsCtrl.getDeliveryOptions(); + ScormPackageConfig sConfig = ScormMainManager.getInstance().getScormPackageConfig(resource); + if(sConfig == null) { + sConfig = new ScormPackageConfig(); + } + sConfig.setDeliveryOptions(newConfig); + ScormMainManager.getInstance().setScormPackageConfig(resource, sConfig); + } + } + }); + + pushController(ureq, translate("tab.layout"), deliveryOptionsCtrl); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/sharedfolder/SharedFolderManager.java b/src/main/java/org/olat/modules/sharedfolder/SharedFolderManager.java index ef8b8e7807debd463d3b35cf122e9721b2d6285f..547d4ce7c402958fd1dcf0dd4b3888f4b7ec39e3 100644 --- a/src/main/java/org/olat/modules/sharedfolder/SharedFolderManager.java +++ b/src/main/java/org/olat/modules/sharedfolder/SharedFolderManager.java @@ -34,7 +34,6 @@ import org.olat.core.commons.services.webdav.servlets.RequestUtil; import org.olat.core.gui.media.CleanupAfterDeliveryFileMediaResource; import org.olat.core.gui.media.MediaResource; import org.olat.core.id.OLATResourceable; -import org.olat.core.manager.BasicManager; import org.olat.core.util.WebappHelper; import org.olat.core.util.ZipUtil; import org.olat.core.util.vfs.LocalFileImpl; @@ -53,7 +52,9 @@ import org.olat.resource.OLATResourceManager; * Initial Date: Aug 29, 2005 <br> * @author Alexander Schneider */ -public class SharedFolderManager extends BasicManager { +public class SharedFolderManager { + + public static final String SHAREDFOLDERREF = "sharedfolderref"; private static final SharedFolderManager INSTANCE = new SharedFolderManager(); /** diff --git a/src/main/java/org/olat/modules/webFeed/ui/FeedRuntimeController.java b/src/main/java/org/olat/modules/webFeed/ui/FeedRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..8ddb4b5853d1c31a0bded5d5457809b08d6fb898 --- /dev/null +++ b/src/main/java/org/olat/modules/webFeed/ui/FeedRuntimeController.java @@ -0,0 +1,87 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.webFeed.ui; + +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.dropdown.Dropdown.Spacer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +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.vfs.QuotaManager; +import org.olat.fileresource.FileResourceManager; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * The runtime add quoty management and delivery options. + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class FeedRuntimeController extends RepositoryEntryRuntimeController { + + private Link quotaLink; + + @Autowired + private QuotaManager quotaManager; + + public FeedRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, runtimeControllerCreator); + } + + @Override + protected void initToolbar(Dropdown toolsDropdown, Dropdown settingsDropdown) { + super.initToolbar(toolsDropdown, settingsDropdown); + if (isOwner || isInstitutionalResourceManager || isOlatAdmin) { + if (quotaManager.hasQuotaEditRights(getIdentity())) { + settingsDropdown.addComponent(new Spacer("")); + quotaLink = LinkFactory.createToolLink("quota", translate("tab.quota.edit"), this, "o_sel_repo_quota"); + quotaLink.setIconLeftCSS("o_icon o_icon-fw o_icon_quota"); + settingsDropdown.addComponent(quotaLink); + } + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(quotaLink == source) { + doQuota(ureq); + } else { + super.event(ureq, source, event); + } + } + + private void doQuota(UserRequest ureq) { + if (quotaManager.hasQuotaEditRights(ureq.getIdentity())) { + RepositoryEntry entry = getRepositoryEntry(); + OlatRootFolderImpl feedRoot = FileResourceManager.getInstance().getFileResourceRootImpl(entry.getOlatResource()); + Controller quotaCtrl = quotaManager.getQuotaEditorInstance(ureq, getWindowControl(), feedRoot.getRelPath(), false); + pushController(ureq, translate("tab.quota.edit"), quotaCtrl); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java b/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java index 20ea965d6fa237e99dbda71793b2af7dd938b6ec..f610f04515f155264b7df80d4fd2ccb566758585 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java +++ b/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java @@ -64,17 +64,17 @@ public abstract class FeedUIFactory { public abstract VelocityContainer createRightColumnVelocityContainer(BasicController controller); /* used for course node */ - public FeedMainController createMainController(OLATResourceable ores, UserRequest ureq, WindowControl wControl, FeedSecurityCallback callback, + public final FeedMainController createMainController(OLATResourceable ores, UserRequest ureq, WindowControl wControl, FeedSecurityCallback callback, Long courseId, String nodeId) { return new FeedMainController(ores, ureq, wControl, courseId, nodeId, this, callback, null); } - public FeedMainController createMainController(OLATResourceable ores, UserRequest ureq, WindowControl wControl, FeedSecurityCallback callback) { + public final FeedMainController createMainController(OLATResourceable ores, UserRequest ureq, WindowControl wControl, FeedSecurityCallback callback) { return new FeedMainController(ores, ureq, wControl, null, null, this, callback, null); } // with specific FeedItemDisplayConfig - public FeedMainController createMainController(final OLATResourceable ores, final UserRequest ureq, final WindowControl wControl, final FeedSecurityCallback callback, FeedItemDisplayConfig displayConfig) { + public final FeedMainController createMainController(final OLATResourceable ores, final UserRequest ureq, final WindowControl wControl, final FeedSecurityCallback callback, FeedItemDisplayConfig displayConfig) { return new FeedMainController(ores, ureq, wControl, null, null, this, callback, displayConfig); } diff --git a/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java b/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java index c6ccce019cb53f03723ffa52c6830201b1f5c0b0..3814693b6e1c0d007e2281610fd4e62c3c0c775b 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java +++ b/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java @@ -179,7 +179,7 @@ public class ItemsController extends BasicController implements Activateable2 { createCommentsAndRatingsLinks(ureq, feed); } // Add date components - createDateComponents(ureq, feed); + createDateComponents(feed); // The year/month navigation List<Item> items = feed.getFilteredItems(callback, ureq.getIdentity()); @@ -288,7 +288,7 @@ public class ItemsController extends BasicController implements Activateable2 { * @param ureq * @param feed */ - private void createDateComponents(UserRequest ureq, Feed feed) { + private void createDateComponents(Feed feed) { List<Item> items = feed.getItems(); if (items != null) { for (Item item : items) { @@ -751,7 +751,7 @@ public class ItemsController extends BasicController implements Activateable2 { createCommentsAndRatingsLinks(ureq, feed); } // Add date components - createDateComponents(ureq, feed); + createDateComponents(feed); vcItems.setDirty(true); } diff --git a/src/main/java/org/olat/portfolio/ui/EPTemplateRuntimeController.java b/src/main/java/org/olat/portfolio/ui/EPTemplateRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..f70ea11261692354a938c3a32f4961c46408c051 --- /dev/null +++ b/src/main/java/org/olat/portfolio/ui/EPTemplateRuntimeController.java @@ -0,0 +1,78 @@ +/** + * <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.portfolio.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.stack.RootEvent; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.portfolio.ui.structel.EPMapViewController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.ui.RepositoryEntryRuntimeController; + +/** + * + * Steal the edit button and glue it on the dropdown + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class EPTemplateRuntimeController extends RepositoryEntryRuntimeController { + + private Link editLink; + + public EPTemplateRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, runtimeControllerCreator); + } + + @Override + protected void initToolbar(Dropdown toolsDropdown, Dropdown settingsDropdown) { + super.initToolbar(toolsDropdown, settingsDropdown); + + if(getRuntimeController() instanceof EPMapViewController) { + EPMapViewController mapCtrl = (EPMapViewController)getRuntimeController(); + if(mapCtrl.canEditStructure()) { + mapCtrl.delegateEditButton(); + editLink = LinkFactory.createToolLink("edit.cmd", translate("details.openeditor"), this, "o_sel_repository_editor"); + editLink.setElementCssClass("o_sel_ep_edit_map"); + editLink.setIconLeftCSS("o_icon o_icon-lg o_icon_edit"); + toolsDropdown.addComponent(0, editLink); + } + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(editLink == source) { + EPMapViewController mapCtrl = (EPMapViewController)getRuntimeController(); + mapCtrl.edit(ureq); + } else if(event instanceof RootEvent) { + EPMapViewController mapCtrl = (EPMapViewController)getRuntimeController(); + mapCtrl.view(ureq); + } else { + super.event(ureq, source, event); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/portfolio/ui/structel/EPMapViewController.java b/src/main/java/org/olat/portfolio/ui/structel/EPMapViewController.java index 740900e9d0e7db6bcedcbc279fc763f17da881fb..64c55f6822cf783c716fa9641eff5cf2cd717e19 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/EPMapViewController.java +++ b/src/main/java/org/olat/portfolio/ui/structel/EPMapViewController.java @@ -81,7 +81,9 @@ public class EPMapViewController extends BasicController implements Activateable private EPStructureTreeAndDetailsEditController editCtrl; private DialogBoxController confirmationSubmissionCtr; private final boolean back; + private boolean editInToolbar = false; + private EditMode editMode = EditMode.view; private PortfolioStructureMap map; private EPSecurityCallback secCallback; private LockResult lockEntry; @@ -90,6 +92,8 @@ public class EPMapViewController extends BasicController implements Activateable private EPFrontendManager ePFMgr; @Autowired private RepositoryManager repositoryManager; + @Autowired + private CoordinatorManager coordinatorManager; public EPMapViewController(UserRequest ureq, WindowControl control, PortfolioStructureMap initialMap, boolean back, boolean preview, EPSecurityCallback secCallback) { @@ -108,7 +112,7 @@ public class EPMapViewController extends BasicController implements Activateable } if(EPSecurityCallbackFactory.isLockNeeded(secCallback)) { - lockEntry = CoordinatorManager.getInstance().getCoordinator().getLocker().acquireLock(initialMap, ureq.getIdentity(), "mmp"); + lockEntry = coordinatorManager.getCoordinator().getLocker().acquireLock(initialMap, ureq.getIdentity(), "mmp"); if(!lockEntry.isSuccess()) { this.secCallback = EPSecurityCallbackFactory.updateAfterFailedLock(secCallback); showWarning("map.already.edited"); @@ -126,7 +130,16 @@ public class EPMapViewController extends BasicController implements Activateable initForm(ureq); putInitialPanel(mainVc); } - + + public boolean canEditStructure() { + return secCallback.canEditStructure(); + } + + public void delegateEditButton() { + if(editButton != null) { + editButton.setVisible(false); + } + } protected void initForm(UserRequest ureq) { Identity ownerIdentity = ePFMgr.getFirstOwnerIdentity(map); @@ -138,18 +151,19 @@ public class EPMapViewController extends BasicController implements Activateable mainVc.contextPut("map", map); mainVc.contextPut("style", ePFMgr.getValidStyleName(map)); - Boolean editMode = editButton == null ? Boolean.FALSE : (Boolean)editButton.getUserObject(); mainVc.remove(mainVc.getComponent("map.editButton")); if(secCallback.canEditStructure()) { editButton = LinkFactory.createButton("map.editButton", mainVc, this); editButton.setElementCssClass("o_sel_ep_edit_map"); editButton.setIconLeftCSS("o_icon o_icon-fw o_icon_edit"); - if(Boolean.FALSE.equals(editMode)) { + if(editMode == EditMode.view) { editButton.setCustomDisplayText(translate("map.editButton.on")); } else { editButton.setCustomDisplayText(translate("map.editButton.off")); } - editButton.setUserObject(editMode); + if(editInToolbar) { + mainVc.remove(mainVc.getComponent("map.editButton")); + } } if(back) { backLink = LinkFactory.createLinkBack(mainVc, this); @@ -198,29 +212,9 @@ public class EPMapViewController extends BasicController implements Activateable * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formInnerEvent(org.olat.core.gui.UserRequest, org.olat.core.gui.components.form.flexible.FormItem, org.olat.core.gui.components.form.flexible.impl.FormEvent) */ @Override - protected void event(UserRequest ureq, Component source, Event event) { + public void event(UserRequest ureq, Component source, Event event) { if (source == editButton){ - removeAsListenerAndDispose(editCtrl); - if (Boolean.FALSE.equals(editButton.getUserObject())){ - PortfolioStructure selectedPage = null; - if(pageCtrl != null) { - selectedPage = pageCtrl.getSelectedPage(); - } - initOrUpdateEditMode(ureq, selectedPage); - } else { - mainVc.remove(editCtrl.getInitialComponent()); - PortfolioStructure currentEditedStructure = editCtrl.getSelectedStructure(); - initForm(ureq); - editButton.setUserObject(Boolean.FALSE); - editButton.setCustomDisplayText(translate("map.editButton.on")); - if(currentEditedStructure != null && pageCtrl != null) { - EPPage page = getSelectedPage(currentEditedStructure); - if(page != null) { - pageCtrl.selectPage(ureq, page); - addToHistory(ureq, page, null); - } - } - } + toogleEditMode(ureq); } else if(source == backLink) { fireEvent(ureq, new EPMapEvent(EPStructureEvent.CLOSE, map)); } else if(source == submitAssessLink) { @@ -232,6 +226,45 @@ public class EPMapViewController extends BasicController implements Activateable } } + public void toogleEditMode(UserRequest ureq) { + removeAsListenerAndDispose(editCtrl); + if (editMode == EditMode.view){ + view(ureq); + } else { + edit(ureq); + } + } + + public void view(UserRequest ureq) { + PortfolioStructure currentEditedStructure = null; + if(editCtrl != null) { + removeAsListenerAndDispose(editCtrl); + mainVc.remove(editCtrl.getInitialComponent()); + currentEditedStructure = editCtrl.getSelectedStructure(); + } + initForm(ureq); + editMode = EditMode.view; + editButton.setCustomDisplayText(translate("map.editButton.on")); + if(currentEditedStructure != null && pageCtrl != null) { + EPPage page = getSelectedPage(currentEditedStructure); + if(page != null) { + pageCtrl.selectPage(ureq, page); + addToHistory(ureq, page, null); + } + } + } + + public void edit(UserRequest ureq) { + if(canEditStructure()) { + removeAsListenerAndDispose(editCtrl); + PortfolioStructure selectedPage = null; + if(pageCtrl != null) { + selectedPage = pageCtrl.getSelectedPage(); + } + initOrUpdateEditMode(ureq, selectedPage); + } + } + @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { if(entries == null || entries.isEmpty()) return; @@ -340,8 +373,13 @@ public class EPMapViewController extends BasicController implements Activateable @Override protected void doDispose() { if(lockEntry != null) { - CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry); + coordinatorManager.getCoordinator().getLocker().releaseLock(lockEntry); lockEntry = null; } } + + private enum EditMode { + view, + editor + } } \ No newline at end of file diff --git a/src/main/java/org/olat/portfolio/ui/structel/EPPageViewController.java b/src/main/java/org/olat/portfolio/ui/structel/EPPageViewController.java index f781edb572fa66cf33685130b06591b8972bd5bc..868772fc0fe7781815604cd20ca048f6e4bd4e0f 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/EPPageViewController.java +++ b/src/main/java/org/olat/portfolio/ui/structel/EPPageViewController.java @@ -138,8 +138,8 @@ public class EPPageViewController extends BasicController { removeAsListenerAndDispose(commentsAndRatingCtr); boolean anonym = ureq.getUserSession().getRoles().isGuestOnly(); - CommentAndRatingSecurityCallback secCallback = new CommentAndRatingDefaultSecurityCallback(getIdentity(), false, anonym); - commentsAndRatingCtr = new UserCommentsAndRatingsController(ureq, getWindowControl(), map.getOlatResource(), page.getKey().toString(), secCallback, true, true, true); + CommentAndRatingSecurityCallback ratingSecCallback = new CommentAndRatingDefaultSecurityCallback(getIdentity(), false, anonym); + commentsAndRatingCtr = new UserCommentsAndRatingsController(ureq, getWindowControl(), map.getOlatResource(), page.getKey().toString(), ratingSecCallback, true, true, true); listenTo(commentsAndRatingCtr); vC.put("commentCtrl", commentsAndRatingCtr.getInitialComponent()); } diff --git a/src/main/java/org/olat/portfolio/ui/structel/_content/mapview.html b/src/main/java/org/olat/portfolio/ui/structel/_content/mapview.html index 3b5cbe2880900fecc7445185f5a7e9bdecd31bda..0d85c0afb36a21f21639b838d69e6287940f4ef7 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/_content/mapview.html +++ b/src/main/java/org/olat/portfolio/ui/structel/_content/mapview.html @@ -1,4 +1,4 @@ -#if($r.available("backLink") || $r.available("map.editButton")) +#if($r.available("backLink") || $r.visible("map.editButton")) <div class="o_toolbar" role="toolbar"> <div class="container-fluid"> #if($r.available("backLink")) diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties index 87cf023a469260a0d6a63af014aadfb33cd01862..85250ee904df766d6b444175cefc179893374873 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties @@ -456,6 +456,7 @@ rentry.pic=Bild zur Lernressource (jpg,png,gif) rentry.prop=Einstellungen zur Lernressource rentry.movie=Film zur Lernressource (mp4,flv) repo.login=Login +resource.editor=Editor repositoryentry.not.existing=Diese Ressource ist nicht mehr verf\u00FCgbar. resource.error.test.xml=Die Lernressource konnte nicht hinzugef\u00FCgt werden. Die qti.xml-Datei konnte nicht geparst werden. resource.error.zip=Die Lernressource konnte nicht hinzugef\u00FCgt werden. Die Zip-Datei konnte nicht entzippt werden. @@ -526,6 +527,7 @@ tab.public=Allgemein tab.quota.edit=Quota tab.sharedfolder=Ressourcenordner table.header.ac=AC +table.header.actions=<i class='o_icon o_icon_actions o_icon-lg'> </i> table.header.access=Zugriff* table.header.access.author=<span class\="o_ochre">BA--</span> table.header.access.desc=*)<br />B\=<b>B</b>esitzer dieser Lernressource <span class\="o_ochre"> (nicht \u00F6ffentlich)</span><br />A\=Alle OpenOLAT-<b>A</b>utoren <span class\="o_ochre"> (nicht \u00F6ffentlich)</span><br />R\=<b>R</b>egistrierte OpenOLAT-Benutzer <span class\="o_blue"> (\u00F6ffentlich)</span><br />G\=<b>G</b>\u00E4ste <span class\="o_blue"> (\u00F6ffentlich)</span> @@ -539,6 +541,7 @@ table.header.date=Erstellt table.header.description=Beschreibung table.header.details=Detailansicht table.header.displayname=Titel der Lernressource +table.header.edit=<i class='o_icon o_icon_edit o_icon-lg'> </i> table.header.externalid=Ext. ID table.header.externalref=Ext. Ref. table.header.lastusage=Letzter Zugriff @@ -575,6 +578,9 @@ table.switch.table=Table table.filter=Filter table.sort=Sortierung title.prefix.closed=beendet + +tools.edit.catalog=Katalogeinträge +tools.edit.description=Kursinfo bearbeiten tools.add.blog=Blog tools.add.course=Kurs tools.add.cp=CP-Lerninhalt diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties index 4eec8abe75ba012540d257076fdfd357b484dcf5..57b2d8d50065179e070d4b2759a2f728a2e5529e 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties @@ -486,6 +486,7 @@ tab.public=General tab.quota.edit=Quota tab.sharedfolder=Resource folder table.header.ac=AC +table.header.actions=<i class='o_icon o_icon_actions o_icon-lg'> </i> table.header.access=Access* table.header.access.author=<span class\="o_ochre">OA--</span> table.header.access.desc=*)<br />O\=<b>O</b>wners of this learning resource <span class\="o_ochre"> (not public)</span><br />A\=All OpenOLAT <b>a</b>uthors <span class\="o_ochre"> (not public)</span><br />U\=Registered OpenOLAT<b>u</b>sers <span class\="o_blue"> (public)</span><br />G\=<b>G</b>uests <span class\="o_blue"> (public)</span> @@ -499,6 +500,7 @@ table.header.date=Created table.header.description=Description table.header.details=Detailed view table.header.displayname=Title of learning resource +table.header.edit=<i class='o_icon o_icon_edit o_icon-lg'> </i> table.header.externalid=Ext. ID table.header.externalref=Ext. Ref. table.header.lastusage=Last access @@ -529,6 +531,7 @@ table.subject.telPrivate=Phone No. private table.subject.zipCode=Zip code table.user.login=User name title.prefix.closed=closed +tools=<i class='o_icon o_icon_actions'> </i> tools.add.blog=Blog tools.add.course=Course tools.add.cp=CP learning content diff --git a/src/main/java/org/olat/repository/handlers/BlogHandler.java b/src/main/java/org/olat/repository/handlers/BlogHandler.java index 2f42654e7021f37ebe01e7602663d28efbe15e3c..6c22a2ce659689d13da9b4e789b5e98996c7eccf 100644 --- a/src/main/java/org/olat/repository/handlers/BlogHandler.java +++ b/src/main/java/org/olat/repository/handlers/BlogHandler.java @@ -23,10 +23,9 @@ import java.io.File; import java.util.Locale; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; -import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -41,7 +40,6 @@ import org.olat.core.util.Util; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent; -import org.olat.core.util.vfs.QuotaManager; import org.olat.core.util.vfs.VFSContainer; import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.BlogFileResource; @@ -51,16 +49,16 @@ import org.olat.modules.webFeed.FeedResourceSecurityCallback; import org.olat.modules.webFeed.FeedSecurityCallback; import org.olat.modules.webFeed.managers.FeedManager; import org.olat.modules.webFeed.ui.FeedMainController; +import org.olat.modules.webFeed.ui.FeedRuntimeController; import org.olat.modules.webFeed.ui.blog.BlogUIFactory; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.resource.references.ReferenceManager; /** @@ -118,16 +116,6 @@ public class BlogHandler implements RepositoryHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - QuotaManager qm = QuotaManager.getInstance(); - if (qm.hasQuotaEditRights(ureq.getIdentity())) { - OlatRootFolderImpl feedRoot = FileResourceManager.getInstance().getFileResourceRootImpl(entry.getOlatResource()); - Controller quotaCtrl = qm.getQuotaEditorInstance(ureq, wControl, feedRoot.getRelPath(), false); - pane.appendEditor(pane.getTranslator().translate("tab.quota.edit"), quotaCtrl); - } - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { OLATResource sourceResource = source.getOlatResource(); @@ -176,7 +164,7 @@ public class BlogHandler implements RepositoryHandler { } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl control) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl control, TooledStackedPanel panel) { return createLaunchController(re, ureq, control); } @@ -186,15 +174,19 @@ public class BlogHandler implements RepositoryHandler { * org.olat.core.gui.control.WindowControl) */ @Override - public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, + public MainLayoutController createLaunchController(final RepositoryEntry re, UserRequest ureq, WindowControl wControl) { boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(ureq.getIdentity(), re); - FeedSecurityCallback callback = new FeedResourceSecurityCallback(isAdmin, isOwner); - FeedMainController blogCtr = BlogUIFactory.getInstance(ureq.getLocale()).createMainController(re.getOlatResource(), ureq, wControl, callback); - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, blogCtr); - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); - return wrapper; + final FeedSecurityCallback callback = new FeedResourceSecurityCallback(isAdmin, isOwner); + return new FeedRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + return new FeedMainController(entry.getOlatResource(), uureq, wwControl, null, null, + BlogUIFactory.getInstance(uureq.getLocale()), callback, null); + } + }); } @Override @@ -225,8 +217,8 @@ public class BlogHandler implements RepositoryHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.embedded; } @Override diff --git a/src/main/java/org/olat/repository/handlers/CourseHandler.java b/src/main/java/org/olat/repository/handlers/CourseHandler.java index e31292b1b32ac2908419404c0491124dec0351b3..f8bd363fffa2b30f85335eaaefe567c305b8981f 100644 --- a/src/main/java/org/olat/repository/handlers/CourseHandler.java +++ b/src/main/java/org/olat/repository/handlers/CourseHandler.java @@ -40,6 +40,7 @@ import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -81,12 +82,6 @@ import org.olat.course.ICourse; import org.olat.course.PersistingCourseImpl; import org.olat.course.Structure; import org.olat.course.config.CourseConfig; -import org.olat.course.config.ui.CourseCalendarConfigForm; -import org.olat.course.config.ui.CourseChatSettingsForm; -import org.olat.course.config.ui.CourseConfigGlossaryController; -import org.olat.course.config.ui.CourseEfficencyStatementForm; -import org.olat.course.config.ui.CourseSharedFolderController; -import org.olat.course.config.ui.courselayout.CourseLayoutGeneratorController; import org.olat.course.export.CourseEnvironmentMapper; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.groupsandrights.PersistingCourseGroupManager; @@ -95,19 +90,16 @@ import org.olat.course.tree.CourseEditorTreeNode; import org.olat.fileresource.types.GlossaryResource; import org.olat.fileresource.types.ResourceEvaluation; import org.olat.fileresource.types.SharedFolderFileResource; -import org.olat.instantMessaging.InstantMessagingModule; import org.olat.modules.glossary.GlossaryManager; import org.olat.modules.sharedfolder.SharedFolderManager; +import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryImportExport; import org.olat.repository.RepositoryEntryImportExport.RepositoryEntryImport; -import org.olat.repository.ErrorList; -import org.olat.repository.RepositoryEntryManagedFlag; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseCourseController; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; @@ -320,7 +312,9 @@ public class CourseHandler implements RepositoryHandler { // set the new shared folder reference CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig(); courseConfig.setSharedFolderSoftkey(importedRepositoryEntry.getSoftkey()); - CourseSharedFolderController.updateRefTo(importedRepositoryEntry, course); + + CoreSpringFactory.getImpl(ReferenceManager.class) + .addReference(importedRepositoryEntry.getOlatResource(), course, SharedFolderManager.SHAREDFOLDERREF); CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig); } @@ -376,44 +370,6 @@ public class CourseHandler implements RepositoryHandler { } } } - - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - - final OLATResource resource = entry.getOlatResource(); - ICourse course = CourseFactory.loadCourse(resource); - CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig().clone(); - - //chat - InstantMessagingModule imModule = CoreSpringFactory.getImpl(InstantMessagingModule.class); - if (imModule.isEnabled() && imModule.isCourseEnabled() && CourseModule.isCourseChatEnabled()) { - boolean managedChat = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.chat); - CourseChatSettingsForm ccc = new CourseChatSettingsForm(ureq, wControl, course, courseConfig, !managedChat); - pane.appendEditor(pane.getTranslator().translate("tab.chat"), ccc); - } - - boolean managedLayout = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.layout); - CourseLayoutGeneratorController layoutC = new CourseLayoutGeneratorController(ureq, wControl, course, courseConfig, - course.getCourseEnvironment(), !managedLayout); - pane.appendEditor(pane.getTranslator().translate("tab.layout"), layoutC); - - boolean managedFolder = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.resourcefolder); - CourseSharedFolderController csfC = new CourseSharedFolderController(ureq, wControl, course, courseConfig, !managedFolder); - pane.appendEditor(pane.getTranslator().translate("tab.sharedfolder"), csfC); - - boolean managedStatement = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.efficencystatement); - CourseEfficencyStatementForm ceffC = new CourseEfficencyStatementForm(ureq, wControl, course, courseConfig, !managedStatement); - pane.appendEditor(pane.getTranslator().translate("tab.efficencystatement"), ceffC); - - boolean managedCalendar = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.calendar); - CourseCalendarConfigForm calCfgCtr = new CourseCalendarConfigForm(ureq, wControl, course, courseConfig, !managedCalendar); - pane.appendEditor(pane.getTranslator().translate("tab.calendar"), calCfgCtr); - - boolean managedGlossary = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.glossary); - CourseConfigGlossaryController cglosCtr = new CourseConfigGlossaryController(ureq, wControl, course, courseConfig, !managedGlossary); - pane.appendEditor(pane.getTranslator().translate("tab.glossary"), cglosCtr); - } @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { @@ -458,8 +414,8 @@ public class CourseHandler implements RepositoryHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.yes; } @Override @@ -497,7 +453,7 @@ public class CourseHandler implements RepositoryHandler { } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { //run + activate MainLayoutController courseCtrl = CourseFactory.createLaunchController(ureq, wControl, re); RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, courseCtrl); diff --git a/src/main/java/org/olat/repository/handlers/EditionSupport.java b/src/main/java/org/olat/repository/handlers/EditionSupport.java new file mode 100644 index 0000000000000000000000000000000000000000..0ae881eb6d8bd078e265db4c0076f006fe2b767f --- /dev/null +++ b/src/main/java/org/olat/repository/handlers/EditionSupport.java @@ -0,0 +1,36 @@ +/** + * <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.repository.handlers; + +/** + * Different options for edition, no editor at all, + * the editor is embbeded in the launcher, + * + * + * Initial date: 15.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum EditionSupport { + + no, + yes, + embedded, +} diff --git a/src/main/java/org/olat/repository/handlers/GlossaryHandler.java b/src/main/java/org/olat/repository/handlers/GlossaryHandler.java index c7c2bce4cbf1ea42825c3d965032ed2782e047ed..c9a8fbf49cd897798dbf8109484ab16977a93b0e 100644 --- a/src/main/java/org/olat/repository/handlers/GlossaryHandler.java +++ b/src/main/java/org/olat/repository/handlers/GlossaryHandler.java @@ -34,10 +34,12 @@ import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.modules.glossary.GlossaryItemManager; import org.olat.core.commons.modules.glossary.GlossaryMainController; +import org.olat.core.commons.modules.glossary.GlossaryRuntimeController; import org.olat.core.commons.modules.glossary.GlossarySecurityCallback; import org.olat.core.commons.modules.glossary.GlossarySecurityCallbackImpl; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -58,18 +60,15 @@ import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.FileResource; import org.olat.fileresource.types.GlossaryResource; import org.olat.fileresource.types.ResourceEvaluation; -import org.olat.modules.glossary.GlossaryEditSettingsController; import org.olat.modules.glossary.GlossaryManager; -import org.olat.modules.glossary.GlossaryRegisterSettingsController; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.resource.references.ReferenceManager; @@ -130,18 +129,6 @@ public class GlossaryHandler implements RepositoryHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - OLATResource resource = entry.getOlatResource(); - - GlossaryRegisterSettingsController glossRegisterSetCtr = new GlossaryRegisterSettingsController(ureq, wControl, resource); - pane.appendEditor(pane.getTranslator().translate("tab.glossary.register"), glossRegisterSetCtr); - - GlossaryEditSettingsController glossEditCtr = new GlossaryEditSettingsController(ureq, wControl, resource); - pane.appendEditor(pane.getTranslator().translate("tab.glossary.edit"), glossEditCtr); - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { OLATResource sourceResource = source.getOlatResource(); @@ -168,8 +155,8 @@ public class GlossaryHandler implements RepositoryHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.embedded; } @Override @@ -192,26 +179,26 @@ public class GlossaryHandler implements RepositoryHandler { */ @Override public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { - VFSContainer glossaryFolder = GlossaryManager.getInstance().getGlossaryRootFolder(re.getOlatResource()); + return new GlossaryRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + VFSContainer glossaryFolder = GlossaryManager.getInstance().getGlossaryRootFolder(entry.getOlatResource()); - RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); - Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); - boolean editableByUser = "true".equals(glossProps.getProperty(GlossaryItemManager.EDIT_USERS)); - boolean owner = repositoryService.hasRole(ureq.getIdentity(), re, GroupRoles.owner.name()); - - GlossarySecurityCallback secCallback; - if (ureq.getUserSession().getRoles().isGuestOnly()) { - secCallback = new GlossarySecurityCallbackImpl(); - } else { - secCallback = new GlossarySecurityCallbackImpl(false, owner, editableByUser, ureq.getIdentity().getKey()); - } - GlossaryMainController gctr = new GlossaryMainController(wControl, ureq, glossaryFolder, re.getOlatResource(), secCallback, false); - // use on column layout - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, gctr); - layoutCtr.addDisposableChildController(gctr); // dispose content on layout dispose - - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); - return wrapper; + RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); + Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); + boolean editableByUser = "true".equals(glossProps.getProperty(GlossaryItemManager.EDIT_USERS)); + boolean owner = repositoryService.hasRole(uureq.getIdentity(), entry, GroupRoles.owner.name()); + + GlossarySecurityCallback secCallback; + if (uureq.getUserSession().getRoles().isGuestOnly()) { + secCallback = new GlossarySecurityCallbackImpl(); + } else { + secCallback = new GlossarySecurityCallbackImpl(false, owner, editableByUser, uureq.getIdentity().getKey()); + } + return new GlossaryMainController(wwControl, uureq, glossaryFolder, entry.getOlatResource(), secCallback, false); + } + }); } @Override @@ -220,7 +207,7 @@ public class GlossaryHandler implements RepositoryHandler { } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { VFSContainer glossaryFolder = GlossaryManager.getInstance().getGlossaryRootFolder(re.getOlatResource()); Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); diff --git a/src/main/java/org/olat/repository/handlers/ImsCPHandler.java b/src/main/java/org/olat/repository/handlers/ImsCPHandler.java index 44359beba3b1f0a7e2ddd5609cf8746d1f952f82..06ddb77c3b7a8830b08faa5f56ff3c49ea0a18cb 100644 --- a/src/main/java/org/olat/repository/handlers/ImsCPHandler.java +++ b/src/main/java/org/olat/repository/handlers/ImsCPHandler.java @@ -30,25 +30,21 @@ import java.util.Locale; import org.olat.admin.quota.QuotaConstants; import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.ControllerEventListener; -import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.iframe.DeliveryOptions; -import org.olat.core.gui.control.generic.iframe.DeliveryOptionsConfigurationController; +import org.olat.core.gui.control.generic.layout.MainLayout3ColumnsController; import org.olat.core.gui.control.generic.layout.MainLayoutController; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; -import org.olat.core.id.context.BusinessControl; -import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.AssertException; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.Util; import org.olat.core.util.coordinate.LockResult; @@ -65,15 +61,15 @@ import org.olat.ims.cp.CPManager; import org.olat.ims.cp.ui.CPContentController; import org.olat.ims.cp.ui.CPEditMainController; import org.olat.ims.cp.ui.CPPackageConfig; +import org.olat.ims.cp.ui.CPRuntimeController; +import org.olat.modules.cp.CPDisplayController; import org.olat.modules.cp.CPOfflineReadableManager; -import org.olat.modules.cp.CPUIFactory; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; /** @@ -86,8 +82,6 @@ import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; */ public class ImsCPHandler extends FileHandler { - private static final OLog log = Tracing.createLoggerFor(ImsCPHandler.class); - @Override public boolean isCreate() { return true; @@ -139,41 +133,6 @@ public class ImsCPHandler extends FileHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - - final OLATResource resource = entry.getOlatResource(); - final CPManager cpManager = CPManager.getInstance(); - QuotaManager qm = QuotaManager.getInstance(); - if (qm.hasQuotaEditRights(ureq.getIdentity())) { - OlatRootFolderImpl cpRoot = FileResourceManager.getInstance().unzipContainerResource(resource); - Controller quotaCtrl = qm.getQuotaEditorInstance(ureq, wControl, cpRoot.getRelPath(), false); - pane.appendEditor(pane.getTranslator().translate("tab.quota.edit"), quotaCtrl); - } - - CPPackageConfig cpConfig = cpManager.getCPPackageConfig(resource); - DeliveryOptions config = cpConfig == null ? null : cpConfig.getDeliveryOptions(); - final DeliveryOptionsConfigurationController deliveryOptionsCtrl = new DeliveryOptionsConfigurationController(ureq, wControl, config); - pane.appendEditor(pane.getTranslator().translate("tab.layout"), deliveryOptionsCtrl); - deliveryOptionsCtrl.addControllerListener(new ControllerEventListener() { - - @Override - public void dispatchEvent(UserRequest uureq, Controller source, Event event) { - if(source == deliveryOptionsCtrl - && (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT)) { - DeliveryOptions newConfig = deliveryOptionsCtrl.getDeliveryOptions(); - CPPackageConfig cConfig = cpManager.getCPPackageConfig(resource); - if(cConfig == null) { - cConfig = new CPPackageConfig(); - } - cConfig.setDeliveryOptions(newConfig); - cpManager.setCPPackageConfig(resource, cConfig); - } - } - }); - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { final CPManager cpManager = CPManager.getInstance(); @@ -212,8 +171,8 @@ public class ImsCPHandler extends FileHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.yes; } @Override @@ -225,15 +184,15 @@ public class ImsCPHandler extends FileHandler { public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { OLATResource res = re.getOlatResource(); File cpRoot = FileResourceManager.getInstance().unzipFileResource(res); - LocalFolderImpl vfsWrapper = new LocalFolderImpl(cpRoot); + final LocalFolderImpl vfsWrapper = new LocalFolderImpl(cpRoot); + CPPackageConfig packageConfig = CPManager.getInstance().getCPPackageConfig(res); + final DeliveryOptions deliveryOptions = (packageConfig == null ? null : packageConfig.getDeliveryOptions()); // jump to either the forum or the folder if the business-launch-path says so. + /* BusinessControl bc = wControl.getBusinessControl(); ContextEntry ce = bc.popLauncherContextEntry(); MainLayoutController layoutCtr; - - CPPackageConfig packageConfig = CPManager.getInstance().getCPPackageConfig(res); - DeliveryOptions deliveryOptions = (packageConfig == null ? null : packageConfig.getDeliveryOptions()); if ( ce != null ) { // a context path is left for me log.debug("businesscontrol (for further jumps) would be:"+bc); OLATResourceable ores = ce.getOLATResourceable(); @@ -251,13 +210,27 @@ public class ImsCPHandler extends FileHandler { } else { layoutCtr = CPUIFactory.getInstance().createMainLayoutResourceableListeningWrapperController(res, ureq, wControl, vfsWrapper, deliveryOptions); } + */ - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); - return wrapper; + CPRuntimeController runtime = new CPRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + boolean activateFirstPage = true; + String initialUri = null; + + CPDisplayController cpCtr = new CPDisplayController(uureq, wwControl, vfsWrapper, true, true, activateFirstPage, true, deliveryOptions, initialUri, entry.getOlatResource()); + MainLayout3ColumnsController ctr = new LayoutMain3ColsController(uureq, wwControl, cpCtr.getMenuComponent(), cpCtr.getInitialComponent(), vfsWrapper.getName()); + ctr.addDisposableChildController(cpCtr); + return ctr; + } + }); + + return runtime; } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { // only unzips, if not already unzipped OlatRootFolderImpl cpRoot = FileResourceManager.getInstance().unzipContainerResource(re.getOlatResource()); @@ -269,7 +242,7 @@ public class ImsCPHandler extends FileHandler { VFSSecurityCallback secCallback = new FullAccessWithQuotaCallback(quota); cpRoot.setLocalSecurityCallback(secCallback); - return new CPEditMainController(ureq, wControl, cpRoot, re.getOlatResource()); + return new CPEditMainController(ureq, wControl, panel, cpRoot, re.getOlatResource()); } protected String getDeletedFilePrefix() { diff --git a/src/main/java/org/olat/repository/handlers/PodcastHandler.java b/src/main/java/org/olat/repository/handlers/PodcastHandler.java index 7ebeec6ef666a4c551b6023ce4da0e7db4588749..f2267e26bf822d2ef2eeed351d6f0042163326b3 100644 --- a/src/main/java/org/olat/repository/handlers/PodcastHandler.java +++ b/src/main/java/org/olat/repository/handlers/PodcastHandler.java @@ -23,10 +23,9 @@ import java.io.File; import java.util.Locale; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; -import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -41,7 +40,6 @@ import org.olat.core.util.Util; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent; -import org.olat.core.util.vfs.QuotaManager; import org.olat.core.util.vfs.VFSContainer; import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.FileResource; @@ -51,16 +49,16 @@ import org.olat.modules.webFeed.FeedResourceSecurityCallback; import org.olat.modules.webFeed.FeedSecurityCallback; import org.olat.modules.webFeed.managers.FeedManager; import org.olat.modules.webFeed.ui.FeedMainController; +import org.olat.modules.webFeed.ui.FeedRuntimeController; import org.olat.modules.webFeed.ui.podcast.PodcastUIFactory; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.resource.references.ReferenceManager; /** @@ -118,18 +116,6 @@ public class PodcastHandler implements RepositoryHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - - QuotaManager qm = QuotaManager.getInstance(); - if (qm.hasQuotaEditRights(ureq.getIdentity())) { - OlatRootFolderImpl feedRoot = FileResourceManager.getInstance().getFileResourceRootImpl(entry.getOlatResource()); - Controller quotaCtrl = qm.getQuotaEditorInstance(ureq, wControl, feedRoot.getRelPath(), false); - pane.appendEditor(pane.getTranslator().translate("tab.quota.edit"), quotaCtrl); - } - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { OLATResource sourceResource = source.getOlatResource(); @@ -178,7 +164,7 @@ public class PodcastHandler implements RepositoryHandler { } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl control) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl control, TooledStackedPanel panel) { // Return the launch controller. Owners and admins will be able to edit the // podcast 'inline'. return createLaunchController(re, ureq, control); @@ -188,11 +174,15 @@ public class PodcastHandler implements RepositoryHandler { public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(ureq.getIdentity(), re); - FeedSecurityCallback callback = new FeedResourceSecurityCallback(isAdmin, isOwner); - FeedMainController podcastCtr = PodcastUIFactory.getInstance(ureq.getLocale()).createMainController(re.getOlatResource(), ureq, wControl, callback); - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, podcastCtr); - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); - return wrapper; + final FeedSecurityCallback callback = new FeedResourceSecurityCallback(isAdmin, isOwner); + return new FeedRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + return new FeedMainController(entry.getOlatResource(), uureq, wwControl, null, null, + PodcastUIFactory.getInstance(uureq.getLocale()), callback, null); + } + }); } @Override @@ -223,8 +213,8 @@ public class PodcastHandler implements RepositoryHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.embedded; } @Override diff --git a/src/main/java/org/olat/repository/handlers/PortfolioHandler.java b/src/main/java/org/olat/repository/handlers/PortfolioHandler.java index c30482b7bd9c55826635fb898a8bd123a5c4828c..63eac0e9728298f5649c5ca9b5551ff5f41bac3b 100644 --- a/src/main/java/org/olat/repository/handlers/PortfolioHandler.java +++ b/src/main/java/org/olat/repository/handlers/PortfolioHandler.java @@ -25,12 +25,11 @@ import java.io.InputStream; import java.util.Locale; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.layout.MainLayoutController; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; import org.olat.core.gui.media.MediaResource; @@ -50,7 +49,6 @@ import org.olat.fileresource.types.ResourceEvaluation; import org.olat.portfolio.EPSecurityCallback; import org.olat.portfolio.EPSecurityCallbackFactory; import org.olat.portfolio.EPTemplateMapResource; -import org.olat.portfolio.EPUIFactory; import org.olat.portfolio.manager.EPFrontendManager; import org.olat.portfolio.manager.EPStructureManager; import org.olat.portfolio.manager.EPXStreamHandler; @@ -58,15 +56,16 @@ import org.olat.portfolio.model.structel.EPAbstractMap; import org.olat.portfolio.model.structel.EPStructuredMapTemplate; import org.olat.portfolio.model.structel.PortfolioStructure; import org.olat.portfolio.model.structel.PortfolioStructureMap; +import org.olat.portfolio.ui.EPTemplateRuntimeController; import org.olat.portfolio.ui.structel.EPCreateMapController; +import org.olat.portfolio.ui.structel.EPMapViewController; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.resource.references.ReferenceManager; import de.bps.onyx.plugin.StreamMediaResource; @@ -140,12 +139,6 @@ public class PortfolioHandler implements RepositoryHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - // - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { OLATResource sourceResource = source.getOlatResource(); @@ -164,8 +157,8 @@ public class PortfolioHandler implements RepositoryHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.embedded; } @Override @@ -240,23 +233,22 @@ public class PortfolioHandler implements RepositoryHandler { } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl control) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl control, TooledStackedPanel panel) { return createLaunchController(re, ureq, control); } @Override - public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, - WindowControl wControl) { - EPFrontendManager ePFMgr = CoreSpringFactory.getImpl(EPFrontendManager.class); - PortfolioStructureMap map = (PortfolioStructureMap)ePFMgr.loadPortfolioStructure(re.getOlatResource()); - EPSecurityCallback secCallback = EPSecurityCallbackFactory.getSecurityCallback(ureq, map, ePFMgr); - Controller epCtr = EPUIFactory.createPortfolioStructureMapController(ureq, wControl, map, secCallback); - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, epCtr); - if(epCtr instanceof Activateable2) { - layoutCtr.addActivateableDelegate((Activateable2)epCtr); - } - layoutCtr.addDisposableChildController(epCtr); - return new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); + public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + return new EPTemplateRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + EPFrontendManager ePFMgr = CoreSpringFactory.getImpl(EPFrontendManager.class); + PortfolioStructureMap map = (PortfolioStructureMap)ePFMgr.loadPortfolioStructure(entry.getOlatResource()); + EPSecurityCallback secCallback = EPSecurityCallbackFactory.getSecurityCallback(uureq, map, ePFMgr); + return new EPMapViewController(uureq, wwControl, map, false, false, secCallback); + } + }); } @Override diff --git a/src/main/java/org/olat/repository/handlers/RepositoryHandler.java b/src/main/java/org/olat/repository/handlers/RepositoryHandler.java index d5b70f4a10c9207729a698c401bfe87c9b0e0210..ed8299ec8f7bece7cd008827ac7dea82a91cc753 100644 --- a/src/main/java/org/olat/repository/handlers/RepositoryHandler.java +++ b/src/main/java/org/olat/repository/handlers/RepositoryHandler.java @@ -29,6 +29,7 @@ import java.io.File; import java.util.Locale; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -43,7 +44,6 @@ import org.olat.fileresource.types.ResourceEvaluation; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; /** @@ -99,14 +99,7 @@ public interface RepositoryHandler { */ public RepositoryEntry importResource(Identity initialAuthor, String initialAuthorAlt, String displayname, String description, boolean withReferences, Locale locale, File file, String filename); - - /** - * - * @param pane - * @param entry - */ - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, AuthoringEditEntrySettingsController pane, RepositoryEntry entry); - + /** * * @param source @@ -130,7 +123,7 @@ public interface RepositoryHandler { /** * @return true if this handler supports an editor for Resourceables of its type. */ - public boolean supportsEdit(OLATResourceable resource); + public EditionSupport supportsEdit(OLATResourceable resource); /** * Return the container where image and files can be saved for the description field. @@ -154,12 +147,13 @@ public interface RepositoryHandler { * Called if a user wants to edit a Resourceable that this handler can provide an editor for. * (it is given here that this method * can only be called when the current user is either olat admin or in the owning group of this resource - * @param res * @param ureq * @param wControl + * @param panel TODO + * @param res * @return Controler able to edit resourceable. */ - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl); + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel); /** * Called if a user wants to create a Resourceable via wizard. diff --git a/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java b/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java index a6da2e0f53068eee1d1b54ddfe80dd49c4b25b17..1faf64966ca8ef4f3c49d28ff7d2536463004772 100644 --- a/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java +++ b/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java @@ -31,12 +31,9 @@ import java.util.Locale; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.ControllerEventListener; -import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.generic.iframe.DeliveryOptions; -import org.olat.core.gui.control.generic.iframe.DeliveryOptionsConfigurationController; import org.olat.core.gui.control.generic.layout.MainLayoutController; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; import org.olat.core.id.Identity; @@ -51,13 +48,13 @@ import org.olat.fileresource.types.ResourceEvaluation; import org.olat.fileresource.types.ScormCPFileResource; import org.olat.modules.scorm.ScormMainManager; import org.olat.modules.scorm.ScormPackageConfig; +import org.olat.modules.scorm.ScormRuntimeController; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.util.logging.activity.LoggingResourceable; @@ -111,32 +108,6 @@ public class SCORMCPHandler extends FileHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - - ScormPackageConfig scormConfig = ScormMainManager.getInstance().getScormPackageConfig(entry.getOlatResource()); - DeliveryOptions config = scormConfig == null ? null : scormConfig.getDeliveryOptions(); - final OLATResource resource = entry.getOlatResource(); - final DeliveryOptionsConfigurationController deliveryOptionsCtrl = new DeliveryOptionsConfigurationController(ureq, wControl, config); - pane.appendEditor(pane.getTranslator().translate("tab.layout"), deliveryOptionsCtrl); - - deliveryOptionsCtrl.addControllerListener(new ControllerEventListener() { - @Override - public void dispatchEvent(UserRequest uureq, Controller source, Event event) { - if(source == deliveryOptionsCtrl && (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT)) { - DeliveryOptions newConfig = deliveryOptionsCtrl.getDeliveryOptions(); - ScormPackageConfig sConfig = ScormMainManager.getInstance().getScormPackageConfig(resource); - if(sConfig == null) { - sConfig = new ScormPackageConfig(); - } - sConfig.setDeliveryOptions(newConfig); - ScormMainManager.getInstance().setScormPackageConfig(resource, sConfig); - } - } - }); - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { final ScormMainManager scormManager = ScormMainManager.getInstance(); @@ -173,8 +144,8 @@ public class SCORMCPHandler extends FileHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return false; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.no; } @Override @@ -187,16 +158,22 @@ public class SCORMCPHandler extends FileHandler { if (re != null) { ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapScormRepositoryEntry(re)); } - OLATResource res = re.getOlatResource(); - File cpRoot = FileResourceManager.getInstance().unzipFileResource(res); - MainLayoutController realController = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, wControl, true, null, cpRoot, - res.getResourceableId(), null, "browse", "no-credit", false, null, false, false, false, null); - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, realController); - return wrapper; + + return new ScormRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + OLATResource res = entry.getOlatResource(); + File cpRoot = FileResourceManager.getInstance().unzipFileResource(res); + MainLayoutController realController = ScormMainManager.getInstance().createScormAPIandDisplayController(uureq, wwControl, true, null, cpRoot, + res.getResourceableId(), null, "browse", "no-credit", false, null, false, false, false, null); + return realController; + } + }); } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { throw new AssertException("Trying to get editor for an SCORM CP type where no editor is provided for this type."); } diff --git a/src/main/java/org/olat/repository/handlers/SharedFolderHandler.java b/src/main/java/org/olat/repository/handlers/SharedFolderHandler.java index 288a0df977998d6a7a888a8854618337e8504b15..f2255c3336a28364a8a8383f79d7487716b3a184 100644 --- a/src/main/java/org/olat/repository/handlers/SharedFolderHandler.java +++ b/src/main/java/org/olat/repository/handlers/SharedFolderHandler.java @@ -33,6 +33,7 @@ import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -60,10 +61,10 @@ import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.resource.references.ReferenceManager; @@ -114,12 +115,6 @@ public class SharedFolderHandler implements RepositoryHandler { return null; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - // - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { OLATResource sourceResource = source.getOlatResource(); @@ -146,8 +141,8 @@ public class SharedFolderHandler implements RepositoryHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.embedded; } /** @@ -172,27 +167,32 @@ public class SharedFolderHandler implements RepositoryHandler { */ @Override public MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { - OLATResource res = re.getOlatResource(); - VFSContainer sfContainer = SharedFolderManager.getInstance().getSharedFolder(res); + + RepositoryEntryRuntimeController runtime = new RepositoryEntryRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + OLATResource res = entry.getOlatResource(); + VFSContainer sfContainer = SharedFolderManager.getInstance().getSharedFolder(res); - Identity identity = ureq.getIdentity(); - Roles roles = ureq.getUserSession().getRoles(); - RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); - boolean canEdit = roles.isOLATAdmin() - || repositoryService.hasRole(identity, re, GroupRoles.owner.name(), GroupRoles.coach.name()) - || RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(identity, roles, re); - - Controller sfdCtr; - if(canEdit) { - sfdCtr = new SharedFolderEditorController(re, ureq, wControl); - } else { - sfdCtr = new SharedFolderDisplayController(ureq, wControl, sfContainer, res); - } - // use on column layout - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, sfdCtr); - layoutCtr.addDisposableChildController(sfdCtr); // dispose content on layout dispose - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); - return wrapper; + Identity identity = uureq.getIdentity(); + Roles roles = uureq.getUserSession().getRoles(); + RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); + boolean canEdit = roles.isOLATAdmin() + || repositoryService.hasRole(identity, entry, GroupRoles.owner.name(), GroupRoles.coach.name()) + || RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(identity, roles, entry); + + Controller sfdCtr; + if(canEdit) { + sfdCtr = new SharedFolderEditorController(entry, uureq, wwControl); + } else { + sfdCtr = new SharedFolderDisplayController(uureq, wwControl, sfContainer, res); + } + return sfdCtr; + } + }); + + return runtime; } /** @@ -204,7 +204,7 @@ public class SharedFolderHandler implements RepositoryHandler { } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { Controller sharedFolderCtr = new SharedFolderEditorController(re, ureq, wControl); // use on column layout LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, sharedFolderCtr); diff --git a/src/main/java/org/olat/repository/handlers/WebDocumentHandler.java b/src/main/java/org/olat/repository/handlers/WebDocumentHandler.java index 7a7f29c8b3c04ca4cc771fb4f93ae7ec3115e5ef..70d14635fe7428f9d9ab38ca8417dbf53d573e90 100644 --- a/src/main/java/org/olat/repository/handlers/WebDocumentHandler.java +++ b/src/main/java/org/olat/repository/handlers/WebDocumentHandler.java @@ -34,6 +34,7 @@ import java.util.Locale; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.MainLayoutController; @@ -60,7 +61,6 @@ import org.olat.fileresource.types.XlsFileResource; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; @@ -173,12 +173,6 @@ public class WebDocumentHandler extends FileHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - // - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { OLATResource sourceResource = source.getOlatResource(); @@ -205,8 +199,8 @@ public class WebDocumentHandler extends FileHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return false; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.no; } @Override @@ -220,7 +214,7 @@ public class WebDocumentHandler extends FileHandler { } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { throw new AssertException("a web document is not editable!!! res-id:"+re.getResourceableId()); } diff --git a/src/main/java/org/olat/repository/handlers/WikiHandler.java b/src/main/java/org/olat/repository/handlers/WikiHandler.java index 75b03113a3ee339b85fac619b1a9b532d407ee09..678a42f91bbb72dd420c90e687be2915c1f37c47 100644 --- a/src/main/java/org/olat/repository/handlers/WikiHandler.java +++ b/src/main/java/org/olat/repository/handlers/WikiHandler.java @@ -34,14 +34,13 @@ import java.util.Locale; import org.olat.basesecurity.BaseSecurityModule; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.layout.MainLayoutController; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; import org.olat.core.gui.media.MediaResource; @@ -80,11 +79,11 @@ import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.WizardCloseResourceController; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.repository.ui.RepositoyUIFactory; -import org.olat.repository.ui.author.AuthoringEditEntrySettingsController; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; -import org.olat.resource.accesscontrol.ui.RepositoryMainAccessControllerWrapper; import org.olat.resource.references.ReferenceManager; @@ -144,12 +143,6 @@ public class WikiHandler implements RepositoryHandler { return re; } - @Override - public void addExtendedEditionControllers(UserRequest ureq, WindowControl wControl, - AuthoringEditEntrySettingsController pane, RepositoryEntry entry) { - // - } - @Override public RepositoryEntry copy(RepositoryEntry source, RepositoryEntry target) { final OLATResource sourceResource = source.getOlatResource(); @@ -212,8 +205,8 @@ public class WikiHandler implements RepositoryHandler { } @Override - public boolean supportsEdit(OLATResourceable resource) { - return true; + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.embedded; } @Override @@ -234,9 +227,7 @@ public class WikiHandler implements RepositoryHandler { if (!securityModule.isWikiEnabled()) { return RepositoyUIFactory.createRepoEntryDisabledDueToSecurityMessageController(ureq, wControl); } - // proceed with standard case - Controller controller = null; - + //check role boolean isOLatAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); boolean isGuestOnly = ureq.getUserSession().getRoles().isGuestOnly(); @@ -250,31 +241,33 @@ public class WikiHandler implements RepositoryHandler { OLATResource res = re.getOlatResource(); BusinessControl bc = wControl.getBusinessControl(); - ContextEntry ce = bc.popLauncherContextEntry(); + final ContextEntry ce = bc.popLauncherContextEntry(); SubscriptionContext subsContext = new SubscriptionContext(res, WikiManager.WIKI_RESOURCE_FOLDER_NAME); - WikiSecurityCallback callback = new WikiSecurityCallbackImpl(null, isOLatAdmin, isGuestOnly, false, isResourceOwner, subsContext); - - if ( ce != null ) { //jump to a certain context - OLATResourceable ores = ce.getOLATResourceable(); - String typeName = ores.getResourceableTypeName(); - String page = typeName.substring("page=".length()); - controller = WikiManager.getInstance().createWikiMainControllerDisposeOnOres(ureq, wControl, res, callback, page); - } else { - controller = WikiManager.getInstance().createWikiMainControllerDisposeOnOres(ureq, wControl, res, callback, null); - } - // use on column layout - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, controller); - layoutCtr.addDisposableChildController(controller); // dispose content on layout dispose - if(controller instanceof Activateable2) { - layoutCtr.addActivateableDelegate((Activateable2)controller); - } - - RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, re, layoutCtr); - return wrapper; + final WikiSecurityCallback callback = new WikiSecurityCallbackImpl(null, isOLatAdmin, isGuestOnly, false, isResourceOwner, subsContext); + + RepositoryEntryRuntimeController runtime = new RepositoryEntryRuntimeController(ureq, wControl, re, + new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, RepositoryEntry entry) { + Controller controller; + if ( ce != null ) { //jump to a certain context + OLATResourceable ores = ce.getOLATResourceable(); + String typeName = ores.getResourceableTypeName(); + String page = typeName.substring("page=".length()); + controller = WikiManager.getInstance().createWikiMainControllerDisposeOnOres(uureq, wwControl, entry.getOlatResource(), callback, page); + } else { + controller = WikiManager.getInstance().createWikiMainControllerDisposeOnOres(uureq, wwControl, entry.getOlatResource(), callback, null); + } + + return controller; + } + }); + + return runtime; } @Override - public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel panel) { return createLaunchController(re, ureq, wControl); } diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..bfa09002e4e03dd26adacf691d7feb0b4777826d --- /dev/null +++ b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java @@ -0,0 +1,491 @@ +/** + * <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.repository.ui; + +import java.util.List; + +import org.olat.NewControllerFactory; +import org.olat.basesecurity.GroupRoles; +import org.olat.core.commons.services.mark.Mark; +import org.olat.core.commons.services.mark.MarkManager; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.htmlheader.jscss.CustomCSS; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.stack.TooledStackedPanel; +import org.olat.core.gui.components.stack.TooledStackedPanel.Align; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.MainLayoutBasicController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; +import org.olat.core.gui.control.generic.dtabs.DTab; +import org.olat.core.gui.control.generic.dtabs.DTabs; +import org.olat.core.gui.control.generic.layout.MainLayoutController; +import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; +import org.olat.core.id.Roles; +import org.olat.core.id.context.BusinessControl; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.HistoryPoint; +import org.olat.core.id.context.StateEntry; +import org.olat.core.logging.OLATSecurityException; +import org.olat.core.util.StringHelper; +import org.olat.core.util.UserSession; +import org.olat.core.util.Util; +import org.olat.core.util.resource.OresHelper; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.repository.RepositoryService; +import org.olat.repository.handlers.EditionSupport; +import org.olat.repository.handlers.RepositoryHandler; +import org.olat.repository.handlers.RepositoryHandlerFactory; +import org.olat.repository.ui.author.AuthoringEditAccessController; +import org.olat.repository.ui.author.CatalogSettingsController; +import org.olat.repository.ui.author.RepositoryEditDescriptionController; +import org.olat.repository.ui.author.RepositoryMembersController; +import org.olat.repository.ui.list.RepositoryEntryDetailsController; +import org.olat.resource.accesscontrol.ACService; +import org.olat.resource.accesscontrol.AccessResult; +import org.olat.resource.accesscontrol.ui.AccessEvent; +import org.olat.resource.accesscontrol.ui.AccessListController; +import org.olat.resource.accesscontrol.ui.AccessRefusedController; +import org.olat.resource.accesscontrol.ui.OrdersAdminController; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 14.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RepositoryEntryRuntimeController extends MainLayoutBasicController implements Activateable2 { + + private Controller runtimeController; + private final TooledStackedPanel toolbarPanel; + private final RuntimeControllerCreator runtimeControllerCreator; + + protected Controller editorCtrl; + private Controller accessController; + private OrdersAdminController ordersCtlr; + private CatalogSettingsController catalogCtlr; + private AuthoringEditAccessController accessCtrl; + private RepositoryEntryDetailsController detailsCtrl; + private RepositoryMembersController membersEditController; + private RepositoryEditDescriptionController descriptionCtrl; + + private Dropdown tools; + private Dropdown settings; + private Link editLink, membersLink, ordersLink, + editDescriptionLink, accessLink, catalogLink, + detailsLink, bookmarkLink; + + protected final boolean isOlatAdmin; + protected final boolean isInstitutionalResourceManager; + protected final boolean isOwner; + protected final boolean isAuthor; + protected final boolean isGuestOnly; + + private boolean corrupted; + private RepositoryEntry re; + private final RepositoryHandler handler; + + private String launchedFromBusinessPath; + + @Autowired + private ACService acService; + @Autowired + private MarkManager markManager; + @Autowired + private RepositoryService repositoryService; + @Autowired + private RepositoryHandlerFactory handlerFactory; + + public RepositoryEntryRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl); + setTranslator(Util.createPackageTranslator(RepositoryService.class, getLocale(), getTranslator())); + + //! check corrupted + + UserSession session = ureq.getUserSession(); + if(session != null && session.getHistoryStack() != null && session.getHistoryStack().size() >= 2) { + // Set previous business path as back link for this course - brings user back to place from which he launched the course + List<HistoryPoint> stack = session.getHistoryStack(); + HistoryPoint point = stack.get(stack.size() - 2); + launchedFromBusinessPath = point.getBusinessPath(); + } + + this.re = re; + this.runtimeControllerCreator = runtimeControllerCreator; + handler = handlerFactory.getRepositoryHandler(re); + + Identity identity = getIdentity(); + Roles roles = ureq.getUserSession().getRoles(); + isOlatAdmin = roles.isOLATAdmin(); + isInstitutionalResourceManager = !roles.isGuestOnly() + && RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(identity, roles, re); + isOwner = isOlatAdmin || repositoryService.hasRole(ureq.getIdentity(), re, GroupRoles.owner.name()) + | isInstitutionalResourceManager; + isAuthor = isOlatAdmin || roles.isAuthor() | isInstitutionalResourceManager; + isGuestOnly = roles.isGuestOnly(); + + // set up the components + toolbarPanel = new TooledStackedPanel("courseStackPanel", getTranslator(), this); + toolbarPanel.setInvisibleCrumb(0); // show root (course) level + toolbarPanel.setShowCloseLink(true, true); + putInitialPanel(toolbarPanel); + doRun(ureq); + initToolbar(); + } + + protected RepositoryEntry getRepositoryEntry() { + return re; + } + + protected Controller getRuntimeController() { + return runtimeController; + } + + private void initToolbar() { + tools = new Dropdown("toolbox.tools", "toolbox.tools", false, getTranslator()); + tools.setElementCssClass("o_sel_repository_tools"); + tools.setIconCSS("o_icon o_icon_tools"); + + settings = new Dropdown("settings", "details.chprop", false, getTranslator()); + settings.setElementCssClass("o_sel_course_settings"); + settings.setIconCSS("o_icon o_icon_customize"); + + initToolbar(tools, settings); + } + + protected void initToolbar(Dropdown toolsDropdown, Dropdown settingsDropdown) { + + boolean managed = false; + + if (isOwner || isInstitutionalResourceManager || isOlatAdmin) { + //tools + if(handler.supportsEdit(re) == EditionSupport.yes) { + editLink = LinkFactory.createToolLink("edit.cmd", translate("details.openeditor"), this, "o_sel_repository_editor"); + editLink.setIconLeftCSS("o_icon o_icon-lg o_icon_edit"); + editLink.setEnabled(!managed); + toolsDropdown.addComponent(editLink); + } + + membersLink = LinkFactory.createToolLink("members", translate("details.members"), this, "o_sel_repo_members"); + membersLink.setIconLeftCSS("o_icon o_icon-fw o_icon_membersmanagement"); + toolsDropdown.addComponent(membersLink); + + ordersLink = LinkFactory.createToolLink("bookings", translate("details.orders"), this, "o_sel_repo_booking"); + ordersLink.setIconLeftCSS("o_icon o_icon-fw o_icon_booking"); + boolean booking = acService.isResourceAccessControled(re.getOlatResource(), null); + ordersLink.setEnabled(!corrupted && booking); + toolsDropdown.addComponent(ordersLink); + + //settings + editDescriptionLink = LinkFactory.createToolLink("settings.cmd", translate("details.chprop"), this, "o_icon_settings"); + editDescriptionLink.setElementCssClass("o_sel_course_settings"); + editDescriptionLink.setEnabled(!managed); + settingsDropdown.addComponent(editDescriptionLink); + + accessLink = LinkFactory.createToolLink("access.cmd", translate("tab.accesscontrol"), this, "o_icon_password"); + accessLink.setElementCssClass("o_sel_course_access"); + accessLink.setEnabled(!managed); + settingsDropdown.addComponent(accessLink); + + catalogLink = LinkFactory.createToolLink("cat", translate("details.categoriesheader"), this, "o_icon_catalog"); + catalogLink.setElementCssClass("o_sel_repo_add_to_catalog"); + catalogLink.setEnabled(!managed); + settingsDropdown.addComponent(catalogLink); + } + + if(toolsDropdown.size() > 0) { + toolbarPanel.addTool(toolsDropdown, Align.left, true); + } + if(settingsDropdown.size() > 0) { + toolbarPanel.addTool(settingsDropdown, Align.left, true); + } + + detailsLink = LinkFactory.createToolLink("details", translate("details.header"), this, "o_sel_repo_details"); + detailsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_details"); + detailsLink.setElementCssClass("o_sel_author_details"); + toolbarPanel.addTool(detailsLink); + + + boolean marked = markManager.isMarked(re, getIdentity(), null); + String css = marked ? Mark.MARK_CSS_ICON : Mark.MARK_ADD_CSS_ICON; + bookmarkLink = LinkFactory.createToolLink("bookmark", translate("details.bookmark.label"), this, css); + bookmarkLink.setTitle(translate(marked ? "details.bookmark.remove" : "details.bookmark")); + toolbarPanel.addTool(bookmarkLink, Align.right); + } + + @Override + public CustomCSS getCustomCSS() { + return runtimeController instanceof MainLayoutController ? ((MainLayoutController)runtimeController).getCustomCSS() : null; + } + + @Override + public void setCustomCSS(CustomCSS newCustomCSS) { + if(runtimeController instanceof MainLayoutController) { + ((MainLayoutController)runtimeController).setCustomCSS(newCustomCSS); + } + } + + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries != null && entries.isEmpty()) { + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("Editor".equals(type)) { + + } + } + + if(runtimeController instanceof Activateable2) { + ((Activateable2)runtimeController).activate(ureq, entries, state); + } + } + + @Override + protected void doDispose() { + if(runtimeController != null && !runtimeController.isDisposed()) { + runtimeController.dispose(); + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == runtimeController) { + fireEvent(ureq, event); + } else if(editLink == source) { + doEdit(ureq); + } else if(membersLink == source) { + doMembers(ureq); + } else if(editDescriptionLink == source) { + doEditSettings(ureq); + } else if(accessLink == source) { + doAccess(ureq); + } else if(catalogLink == source) { + doCatalog(ureq); + } else if(ordersLink == source) { + doOrders(ureq); + } else if(detailsLink == source) { + doDetails(ureq); + } else if(bookmarkLink == source) { + boolean marked = doMark(); + String css = "o_icon " + (marked ? Mark.MARK_CSS_ICON : Mark.MARK_ADD_CSS_ICON); + bookmarkLink.setIconLeftCSS(css); + bookmarkLink.setTitle( translate(marked ? "details.bookmark.remove" : "details.bookmark")); + } else if(source == toolbarPanel) { + if (event == Event.CLOSE_EVENT) { + doClose(ureq); + } + } + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == accessController) { + if(event.equals(AccessEvent.ACCESS_OK_EVENT)) { + launchContent(ureq); + cleanUp(); + } else if(event.equals(AccessEvent.ACCESS_FAILED_EVENT)) { + String msg = ((AccessEvent)event).getMessage(); + if(StringHelper.containsNonWhitespace(msg)) { + getWindowControl().setError(msg); + } else { + showError("error.accesscontrol"); + } + } + } + } + + protected RepositoryEntryRuntimeController popToRoot(UserRequest ureq) { + toolbarPanel.popUpToRootController(ureq); + return this; + } + + protected RepositoryEntryRuntimeController cleanUp() { + removeAsListenerAndDispose(membersEditController); + removeAsListenerAndDispose(accessController); + removeAsListenerAndDispose(descriptionCtrl); + removeAsListenerAndDispose(catalogCtlr); + removeAsListenerAndDispose(detailsCtrl); + removeAsListenerAndDispose(editorCtrl); + removeAsListenerAndDispose(ordersCtlr); + membersEditController = null; + accessController = null; + descriptionCtrl = null; + catalogCtlr = null; + detailsCtrl = null; + editorCtrl = null; + ordersCtlr = null; + return this; + } + + /** + * Pop to root, clean up, and push + * @param ureq + * @param name + * @param controller + */ + protected void pushController(UserRequest ureq, String name, Controller controller) { + popToRoot(ureq).cleanUp(); + toolbarPanel.pushController(name, controller); + } + + /** + * Open the editor for all repository entry metadata, access control... + * @param ureq + */ + protected void doAccess(UserRequest ureq) { + popToRoot(ureq).cleanUp(); + accessCtrl = new AuthoringEditAccessController(ureq, getWindowControl(), re); + listenTo(accessCtrl); + toolbarPanel.pushController(translate("tab.accesscontrol"), accessCtrl); + } + + protected void doClose(UserRequest ureq) { + // Navigate beyond the stack, our own layout has been popped - close this tab + DTabs tabs = getWindowControl().getWindowBackOffice().getWindow().getDTabs(); + if (tabs != null) { + DTab tab = tabs.getDTab(re.getOlatResource()); + if (tab != null) { + tabs.removeDTab(ureq, tab); + } + } + // Now try to go back to place that is attacked to (optional) root back business path + if (StringHelper.containsNonWhitespace(launchedFromBusinessPath)) { + BusinessControl bc = BusinessControlFactory.getInstance().createFromString(launchedFromBusinessPath); + WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl()); + try { + //make the resume secure. If something fail, don't generate a red screen + NewControllerFactory.getInstance().launch(ureq, bwControl); + } catch (Exception e) { + logError("Error while resuming with root leve back business path::" + launchedFromBusinessPath, e); + } + } + } + + protected void doEdit(UserRequest ureq) { + popToRoot(ureq).cleanUp(); + editorCtrl = handler.createEditorController(re, ureq, getWindowControl(), toolbarPanel); + listenTo(editorCtrl); + toolbarPanel.pushController(translate("resource.editor"), editorCtrl); + } + + protected void doDetails(UserRequest ureq) { + popToRoot(ureq).cleanUp(); + detailsCtrl = new RepositoryEntryDetailsController(ureq, getWindowControl(), re); + listenTo(detailsCtrl); + toolbarPanel.pushController(translate("details.header"), detailsCtrl); + } + + /** + * Open the editor for all repository entry metadata, access control... + * @param ureq + */ + protected void doEditSettings(UserRequest ureq) { + popToRoot(ureq).cleanUp(); + descriptionCtrl = new RepositoryEditDescriptionController(ureq, getWindowControl(), re, false); + listenTo(descriptionCtrl); + toolbarPanel.pushController(translate("settings.editor"), descriptionCtrl); + } + + /** + * Internal helper to initiate the add to catalog workflow + * @param ureq + */ + protected void doCatalog(UserRequest ureq) { + popToRoot(ureq).cleanUp(); + catalogCtlr = new CatalogSettingsController(ureq, getWindowControl(), toolbarPanel, re); + listenTo(catalogCtlr); + catalogCtlr.initToolbar(); + } + + protected void doMembers(UserRequest ureq) { + if (!isOwner) { + throw new OLATSecurityException("Trying to access groupmanagement, but not allowed: user = " + getIdentity()); + } + popToRoot(ureq).cleanUp(); + membersEditController = new RepositoryMembersController(ureq, getWindowControl(), re); + listenTo(membersEditController); + toolbarPanel.pushController(translate("details.members"), membersEditController); + } + + protected void doOrders(UserRequest ureq) { + if (!isOwner) { + throw new OLATSecurityException("Trying to access groupmanagement, but not allowed: user = " + getIdentity()); + } + + popToRoot(ureq).cleanUp(); + ordersCtlr = new OrdersAdminController(ureq, getWindowControl(), re.getOlatResource()); + listenTo(ordersCtlr); + toolbarPanel.pushController(translate("details.orders"), ordersCtlr); + } + + private void doRun(UserRequest ureq) { + if(ureq.getUserSession().getRoles().isOLATAdmin()) { + launchContent(ureq); + } else { + // guest are allowed to see resource with BARG + if(re.getAccess() == RepositoryEntry.ACC_USERS_GUESTS && ureq.getUserSession().getRoles().isGuestOnly()) { + launchContent(ureq); + } else { + AccessResult acResult = acService.isAccessible(re, getIdentity(), false); + if(acResult.isAccessible()) { + launchContent(ureq); + } else if (re != null && acResult.getAvailableMethods().size() > 0) { + accessController = new AccessListController(ureq, getWindowControl(), acResult.getAvailableMethods()); + listenTo(accessController); + toolbarPanel.rootController(re.getDisplayname(), accessController); + } else { + Controller ctrl = new AccessRefusedController(ureq, getWindowControl()); + listenTo(ctrl); + toolbarPanel.rootController(re.getDisplayname(), ctrl); + } + } + } + } + + protected boolean doMark() { + OLATResourceable item = OresHelper.clone(re); + if(markManager.isMarked(item, getIdentity(), null)) { + markManager.removeMark(item, getIdentity(), null); + return false; + } else { + String businessPath = "[RepositoryEntry:" + item.getResourceableId() + "]"; + markManager.setMark(item, getIdentity(), null, businessPath); + return true; + } + } + + private void launchContent(UserRequest ureq) { + runtimeController = runtimeControllerCreator.create(ureq, getWindowControl(), re); + toolbarPanel.rootController(re.getDisplayname(), runtimeController); + } + + public interface RuntimeControllerCreator { + + public Controller create(UserRequest ureq, WindowControl wControl, RepositoryEntry entry); + + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/repository/ui/RepositoyUIFactory.java b/src/main/java/org/olat/repository/ui/RepositoyUIFactory.java index 1ee2ccf703c95ca126810f0c4787069b2d70b6ab..0b139fea1d0608a40b56f7ca7f57341e9f8309e8 100644 --- a/src/main/java/org/olat/repository/ui/RepositoyUIFactory.java +++ b/src/main/java/org/olat/repository/ui/RepositoyUIFactory.java @@ -24,7 +24,6 @@ */ package org.olat.repository.ui; -import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -32,21 +31,12 @@ import org.olat.core.gui.components.panel.Panel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.layout.GenericMainController; -import org.olat.core.gui.control.generic.layout.MainLayoutController; import org.olat.core.gui.control.generic.messages.MessageController; import org.olat.core.gui.control.generic.messages.MessageUIFactory; -import org.olat.core.gui.translator.Translator; -import org.olat.core.id.OLATResourceable; -import org.olat.core.id.context.BusinessControlFactory; -import org.olat.core.id.context.ContextEntry; -import org.olat.core.logging.AssertException; import org.olat.core.util.Util; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryShort; import org.olat.repository.RepositoryManager; -import org.olat.repository.RepositoryService; -import org.olat.repository.handlers.RepositoryHandler; -import org.olat.repository.handlers.RepositoryHandlerFactory; /** * Description:<br> @@ -83,48 +73,6 @@ public class RepositoyUIFactory { } return iconCSSClass; } - - /** - * Create a launch controller used to launch the given repo entry. - * @param re - * @param initialViewIdentifier if null the default view will be started, otherwise a controllerfactory type dependant view will be activated (subscription subtype) - * @param ureq - * @param wControl - * @return null if no entry was found, a no access message controller if not allowed to launch or the launch - * controller if successful. - */ - public static MainLayoutController createLaunchController(RepositoryEntry re, UserRequest ureq, WindowControl wControl) { - if (re == null) return null; - RepositoryManager rm = RepositoryManager.getInstance(); - if (!rm.isAllowedToLaunch(ureq, re)) { - Translator trans = Util.createPackageTranslator(RepositoyUIFactory.class, ureq.getLocale()); - String text = trans.translate("launch.noaccess"); - Controller c = MessageUIFactory.createInfoMessage(ureq, wControl, null, text); - - // use on column layout - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, c); - layoutCtr.addDisposableChildController(c); // dispose content on layout dispose - return layoutCtr; - } - - RepositoryService rs = CoreSpringFactory.getImpl(RepositoryService.class); - rs.incrementLaunchCounter(re); - RepositoryHandler handler = RepositoryHandlerFactory.getInstance().getRepositoryHandler(re); - - WindowControl bwControl; - OLATResourceable businessOres = re; - ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(businessOres); - //OLAT-5944: check if the current context entry is not already the repository entry to avoid duplicate in the business path - if(ce.equals(wControl.getBusinessControl().getCurrentContextEntry())) { - bwControl = wControl; - } else { - bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl); - } - - MainLayoutController ctrl = handler.createLaunchController(re, ureq, bwControl); - if (ctrl == null) throw new AssertException("could not create controller for repositoryEntry "+re); - return ctrl; - } /** * Create main controller that does nothing but displaying a message that diff --git a/src/main/java/org/olat/repository/ui/author/AuthorListController.java b/src/main/java/org/olat/repository/ui/author/AuthorListController.java index be43feccf7dbab222500d0edb8ad95a5a3210331..1f9e190bdc8740ed9f1ce4227b2ebc6dad7a2e78 100644 --- a/src/main/java/org/olat/repository/ui/author/AuthorListController.java +++ b/src/main/java/org/olat/repository/ui/author/AuthorListController.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.olat.NewControllerFactory; import org.olat.admin.user.UserSearchController; @@ -60,29 +61,41 @@ import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.stack.PopEvent; import org.olat.core.gui.components.stack.TooledStackedPanel; import org.olat.core.gui.components.stack.TooledStackedPanel.Align; +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.CloseableCalloutWindowController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; 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.wizard.StepsMainRunController; +import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; +import org.olat.core.id.Roles; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; +import org.olat.core.logging.OLATSecurityException; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.resource.OresHelper; import org.olat.course.CorruptedCourseException; +import org.olat.course.run.RunMainController; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.olat.repository.RepositoryManager; +import org.olat.repository.RepositoryModule; import org.olat.repository.RepositoryService; import org.olat.repository.controllers.EntryChangedEvent; import org.olat.repository.controllers.EntryChangedEvent.Change; +import org.olat.repository.controllers.WizardCloseResourceController; import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; import org.olat.repository.handlers.RepositoryHandlerFactory.OrderedRepositoryHandler; @@ -91,6 +104,7 @@ import org.olat.repository.model.SearchAuthorRepositoryEntryViewParams.OrderBy; import org.olat.repository.model.TransientRepositoryEntryRef; import org.olat.repository.ui.RepositoyUIFactory; import org.olat.repository.ui.author.AuthoringEntryDataModel.Cols; +import org.olat.user.UserManager; import org.olat.util.logging.activity.LoggingResourceable; import org.springframework.beans.factory.annotation.Autowired; @@ -106,12 +120,13 @@ public class AuthorListController extends FormBasicController implements Activat private FlexiTableElement tableEl; private final TooledStackedPanel stackPanel; - private boolean withSearch;; + private boolean withSearch; private AuthoringEntryDataModel model; private AuthoringEntryDataSource dataSource; private final SearchAuthorRepositoryEntryViewParams searchParams; + private ToolsController toolsCtrl; private CloseableModalController cmc; private StepsMainRunController wizardCtrl; private AuthorSearchController searchCtrl; @@ -119,16 +134,28 @@ public class AuthorListController extends FormBasicController implements Activat private AuthoringEntryDetailsController detailsCtrl; private ImportRepositoryEntryController importCtrl; private CreateRepositoryEntryController createCtrl; + private CloseableCalloutWindowController toolsCalloutCtrl; + + private DialogBoxController deleteDialogCtrl; + private WizardCloseResourceController wc; + private CopyRepositoryEntryController copyCtrl; private Link importLink; private Dropdown createDropdown; private FormLink addOwnersButton; + + private LockResult lockResult; + private final AtomicInteger counter = new AtomicInteger(); //only used as marker for dirty, model cannot load specific rows private final List<Integer> dirtyRows = new ArrayList<>(); + @Autowired + private UserManager userManager; @Autowired private MarkManager markManager; @Autowired + private RepositoryModule repositoryModule; + @Autowired private RepositoryService repositoryService; @Autowired private RepositoryHandlerFactory repositoryHandlerFactory; @@ -136,7 +163,7 @@ public class AuthorListController extends FormBasicController implements Activat public AuthorListController(UserRequest ureq, WindowControl wControl, String i18nName, SearchAuthorRepositoryEntryViewParams searchParams, boolean withSearch) { super(ureq, wControl, "entries"); - setTranslator(Util.createPackageTranslator(RepositoryManager.class, getLocale(), getTranslator())); + setTranslator(Util.createPackageTranslator(RepositoryService.class, getLocale(), getTranslator())); this.i18nName = i18nName; this.withSearch = withSearch; @@ -225,7 +252,8 @@ public class AuthorListController extends FormBasicController implements Activat true, OrderBy.lastUsage.name())); columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel("details", -1, "details", new StaticFlexiCellRenderer("" /* translate("details")*/, "details", "o_icon-lg o_icon_details"))); - columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel("edit", Cols.editionSupported.ordinal(), "edit", + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.tools.i18nKey(), Cols.tools.ordinal())); + columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel(Cols.editionSupported.i18nKey(), Cols.editionSupported.ordinal(), "edit", new BooleanCellRenderer(new StaticFlexiCellRenderer("" /* translate("edit") */, "edit", "o_icon-lg o_icon_edit"), null))); model = new AuthoringEntryDataModel(dataSource, columnsModel); @@ -368,20 +396,29 @@ public class AuthorListController extends FormBasicController implements Activat } cmc.deactivate(); cleanUp(); + } else if(toolsCtrl == source) { + if(event == Event.DONE_EVENT) { + toolsCalloutCtrl.deactivate(); + cleanUp(); + } } super.event(ureq, source, event); } private void cleanUp() { + removeAsListenerAndDispose(toolsCalloutCtrl); removeAsListenerAndDispose(userSearchCtr); removeAsListenerAndDispose(createCtrl); removeAsListenerAndDispose(importCtrl); removeAsListenerAndDispose(wizardCtrl); + removeAsListenerAndDispose(toolsCtrl); removeAsListenerAndDispose(cmc); + toolsCalloutCtrl = null; userSearchCtr = null; createCtrl = null; importCtrl = null; wizardCtrl = null; + toolsCtrl = null; cmc = null; } @@ -416,6 +453,9 @@ public class AuthorListController extends FormBasicController implements Activat link.setTitle(translate(marked ? "details.bookmark.remove" : "details.bookmark")); link.getComponent().setDirty(true); row.setMarked(marked); + } else if("tools".equals(cmd)) { + AuthoringEntryRow row = (AuthoringEntryRow)link.getUserObject(); + doOpenTools(ureq, row, link); } } else if(source == tableEl) { if(event instanceof SelectionEvent) { @@ -425,7 +465,7 @@ public class AuthorListController extends FormBasicController implements Activat if("details".equals(cmd)) { doOpenDetails(ureq, row); } else if("edit".equals(cmd)) { - launchResourceEditor(ureq, row); + launchEditor(ureq, row); } else if("select".equals(cmd)) { launch(ureq, row); } @@ -467,6 +507,19 @@ public class AuthorListController extends FormBasicController implements Activat return detailsCtrl; } + private void doOpenTools(UserRequest ureq, AuthoringEntryRow row, FormLink link) { + removeAsListenerAndDispose(toolsCtrl); + removeAsListenerAndDispose(toolsCalloutCtrl); + + toolsCtrl = new ToolsController(ureq, getWindowControl(), row); + listenTo(toolsCtrl); + + toolsCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), + toolsCtrl.getInitialComponent(), link.getFormDispatchId(), "", true, ""); + listenTo(toolsCalloutCtrl); + toolsCalloutCtrl.activate(); + } + private void doImport(UserRequest ureq) { if(importCtrl != null) return; @@ -498,6 +551,18 @@ public class AuthorListController extends FormBasicController implements Activat cmc.activate(); } + protected boolean doMark(AuthoringEntryRow row) { + OLATResourceable item = OresHelper.createOLATResourceableInstance("RepositoryEntry", row.getKey()); + if(markManager.isMarked(item, getIdentity(), null)) { + markManager.removeMark(item, getIdentity(), null); + return false; + } else { + String businessPath = "[RepositoryEntry:" + item.getResourceableId() + "]"; + markManager.setMark(item, getIdentity(), null, businessPath); + return true; + } + } + private void doPostCreateWizard(UserRequest ureq, RepositoryEntry newEntry, RepositoryHandler handler) { if(wizardCtrl != null) return; @@ -546,6 +611,105 @@ public class AuthorListController extends FormBasicController implements Activat } } + private void doCloseResource(UserRequest ureq, AuthoringEntryRow row) { + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(wc); + + RepositoryEntry entry = repositoryService.loadByKey(row.getKey()); + RepositoryHandler repoHandler = repositoryHandlerFactory.getRepositoryHandler(entry); + wc = repoHandler.createCloseResourceController(ureq, getWindowControl(), entry); + listenTo(wc); + wc.startWorkflow(); + + String title = wc.getAndRemoveWizardTitle(); + cmc = new CloseableModalController(getWindowControl(), translate("close"), wc.getInitialComponent(), true, title); + listenTo(cmc); + cmc.activate(); + } + + private void doCopy(UserRequest ureq, AuthoringEntryRow row) { + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(copyCtrl); + + RepositoryEntry entry = repositoryService.loadByKey(row.getKey()); + copyCtrl = new CopyRepositoryEntryController(ureq, getWindowControl(), entry); + listenTo(copyCtrl); + + String title = translate("details.copy"); + cmc = new CloseableModalController(getWindowControl(), translate("close"), copyCtrl.getInitialComponent(), true, title); + listenTo(cmc); + cmc.activate(); + } + + private void doDelete(UserRequest ureq, AuthoringEntryRow row) { + boolean isOwner = true; + if (!isOwner) throw new OLATSecurityException("Trying to delete, but not allowed: user = " + ureq.getIdentity()); + + //show how many users are currently using this resource + String dialogTitle = translate("del.header", row.getDisplayname()); + Long resId = row.getRepositoryEntryResourceable().getResourceableId(); + OLATResourceable courseRunOres = OresHelper.createOLATResourceableInstance(RunMainController.ORES_TYPE_COURSE_RUN, resId); + int cnt = CoordinatorManager.getInstance().getCoordinator().getEventBus().getListeningIdentityCntFor(courseRunOres); + + boolean corrupted = false; + + String dialogText = translate(corrupted ? "del.confirm.corrupted" : "del.confirm", String.valueOf(cnt)); + deleteDialogCtrl = activateYesNoDialog(ureq, dialogTitle, dialogText, deleteDialogCtrl); + } + + private void doDownload(UserRequest ureq, AuthoringEntryRow row) { + RepositoryHandler typeToDownload = repositoryHandlerFactory.getRepositoryHandler(row.getResourceType()); + if (typeToDownload == null) { + StringBuilder sb = new StringBuilder(translate("error.download")); + sb.append(": No download handler for repository entry: ") + .append(row.getKey()); + showError(sb.toString()); + return; + } + + RepositoryEntry entry = repositoryService.loadByKey(row.getKey()); + OLATResourceable ores = entry.getOlatResource(); + if (ores == null) { + showError("error.download"); + return; + } + + boolean isAlreadyLocked = typeToDownload.isLocked(ores); + try { + lockResult = typeToDownload.acquireLock(ores, ureq.getIdentity()); + if(lockResult == null || (lockResult !=null && lockResult.isSuccess() && !isAlreadyLocked)) { + MediaResource mr = typeToDownload.getAsMediaResource(ores, false); + if(mr!=null) { + repositoryService.incrementDownloadCounter(entry); + ureq.getDispatchResult().setResultingMediaResource(mr); + } else { + showError("error.export"); + fireEvent(ureq, Event.FAILED_EVENT); + } + } else if(lockResult !=null && lockResult.isSuccess() && isAlreadyLocked) { + String fullName = userManager.getUserDisplayName(lockResult.getOwner()); + showInfo("warning.course.alreadylocked.bySameUser", fullName); + lockResult = null; //invalid lock, it was already locked + } else { + String fullName = userManager.getUserDisplayName(lockResult.getOwner()); + showInfo("warning.course.alreadylocked", fullName); + } + } finally { + if((lockResult!=null && lockResult.isSuccess() && !isAlreadyLocked)) { + typeToDownload.releaseLock(lockResult); + lockResult = null; + } + } + } + + private void launchCatalog(UserRequest ureq, AuthoringEntryRow row) { + launch(ureq, row); + } + + private void launchEditDescription(UserRequest ureq, AuthoringEntryRow row) { + launch(ureq, row); + } + private void launch(UserRequest ureq, AuthoringEntryRow row) { try { RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(row.getResourceType()); @@ -561,7 +725,7 @@ public class AuthorListController extends FormBasicController implements Activat } } - private void launchResourceEditor(UserRequest ureq, AuthoringEntryRow row) { + private void launchEditor(UserRequest ureq, AuthoringEntryRow row) { try { String businessPath = "[RepositoryEntry:" + row.getKey() + "][Editor:0]"; NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); @@ -570,25 +734,119 @@ public class AuthorListController extends FormBasicController implements Activat showError("cif.error.corrupted"); } } + + private void launchMembers(UserRequest ureq, AuthoringEntryRow row) { + try { + String businessPath = "[RepositoryEntry:" + row.getKey() + "][MembersMgmt:0]"; + NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); + } catch (CorruptedCourseException e) { + logError("Course corrupted: " + row.getKey() + " (" + row.getOLATResourceable().getResourceableId() + ")", e); + showError("cif.error.corrupted"); + } + } @Override - public void forgeMarkLink(AuthoringEntryRow row) { - FormLink markLink = uifactory.addFormLink("mark_" + row.getKey(), "mark", "", null, null, Link.NONTRANSLATED); + public void forgeLinks(AuthoringEntryRow row) { + //mark + FormLink markLink = uifactory.addFormLink("mark_" + counter.incrementAndGet(), "mark", "", null, null, Link.NONTRANSLATED); markLink.setIconLeftCSS(row.isMarked() ? Mark.MARK_CSS_LARGE : Mark.MARK_ADD_CSS_LARGE); markLink.setTitle(translate(row.isMarked() ? "details.bookmark.remove" : "details.bookmark")); markLink.setUserObject(row); row.setMarkLink(markLink); + //tools + FormLink toolsLink = uifactory.addFormLink("tools_" + counter.incrementAndGet(), "tools", "", null, null, Link.NONTRANSLATED); + toolsLink.setIconLeftCSS("o_icon o_icon_actions o_icon-lg"); + toolsLink.setTitle(" "); + toolsLink.setUserObject(row); + row.setToolsLink(toolsLink); } - protected boolean doMark(AuthoringEntryRow row) { - OLATResourceable item = OresHelper.createOLATResourceableInstance("RepositoryEntry", row.getKey()); - if(markManager.isMarked(item, getIdentity(), null)) { - markManager.removeMark(item, getIdentity(), null); - return false; - } else { - String businessPath = "[RepositoryEntry:" + item.getResourceableId() + "]"; - markManager.setMark(item, getIdentity(), null, businessPath); - return true; + private class ToolsController extends BasicController { + + private final AuthoringEntryRow row; + private final RepositoryEntry entry; + private final VelocityContainer mainVC; + + private boolean isOwner; + private boolean isOlatAdmin; + private boolean isAuthor; + + public ToolsController(UserRequest ureq, WindowControl wControl, AuthoringEntryRow row) { + super(ureq, wControl); + setTranslator(AuthorListController.this.getTranslator()); + this.row = row; + this.entry = repositoryService.loadByKey(row.getKey()); + + Identity identity = getIdentity(); + Roles roles = ureq.getUserSession().getRoles(); + isOlatAdmin = roles.isOLATAdmin(); + boolean isInstitutionalResourceManager = !roles.isGuestOnly() + && RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(identity, roles, entry); + isOwner = isOlatAdmin || repositoryService.hasRole(ureq.getIdentity(), entry, GroupRoles.owner.name()) + | isInstitutionalResourceManager; + isAuthor = isOlatAdmin || roles.isAuthor() | isInstitutionalResourceManager; + + mainVC = createVelocityContainer("tools"); + List<String> links = new ArrayList<>(); + + if(isOwner) { + addLink("tools.edit.description", "description", "o_icon o_icon-fw o_icon_details", links); + if(repositoryModule.isCatalogEnabled()) { + addLink("tools.edit.catalog", "catalog", "o_icon o_icon-fw o_icon_catalog", links); + } + addLink("details.members", "members", "o_icon o_icon-fw o_icon_membersmanagement", links); + } + links.add("-"); + if (isAuthor || isOwner) { + addLink("details.copy", "copy", "o_icon o_icon-fw o_icon_copy", links); + } + addLink("details.download", "download", "o_icon o_icon-fw o_icon_download", links); + if(isOwner) { + links.add("-"); + addLink("details.close.ressoure", "close", "o_icon o_icon-fw o_icon_close_resource", links); + addLink("details.delete", "delete", "o_icon o_icon-fw o_icon_delete_item", links); + } + + mainVC.contextPut("links", links); + putInitialPanel(mainVC); + } + + private void addLink(String name, String cmd, String iconCSS, List<String> links) { + Link link = LinkFactory.createLink(name, cmd, getTranslator(), mainVC, this, Link.LINK); + if(iconCSS != null) { + link.setIconLeftCSS(iconCSS); + } + mainVC.put(name, link); + links.add(name); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + fireEvent(ureq, Event.DONE_EVENT); + if(source instanceof Link) { + Link link = (Link)source; + String cmd = link.getCommand(); + if("description".equals(cmd)) { + launchEditDescription(ureq, row); + } else if("catalog".equals(cmd)) { + launchCatalog(ureq, row); + } else if("members".equals(cmd)) { + launchMembers(ureq, row); + } else if("copy".equals(cmd)) { + doCopy(ureq, row); + } else if("download".equals(cmd)) { + doDownload(ureq, row); + } else if("close".equals(cmd)) { + doCloseResource(ureq, row); + } else if("delete".equals(cmd)) { + doDelete(ureq, row); + } + } + } + + @Override + protected void doDispose() { + // } } } \ No newline at end of file diff --git a/src/main/java/org/olat/repository/ui/author/AuthoringEditEntrySettingsController.java b/src/main/java/org/olat/repository/ui/author/AuthoringEditEntrySettingsController.java index 47eb573c4d1a16d614397507dd686e5ad24e257d..1b70b5ac7ab7aa80c8cdac9f9de88b24055e7e08 100644 --- a/src/main/java/org/olat/repository/ui/author/AuthoringEditEntrySettingsController.java +++ b/src/main/java/org/olat/repository/ui/author/AuthoringEditEntrySettingsController.java @@ -32,7 +32,6 @@ import org.olat.core.util.Util; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.olat.repository.RepositoryService; -import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -70,9 +69,6 @@ public class AuthoringEditEntrySettingsController extends BasicController { tabbedPane.addTab(translate("tab.public"), descriptionCtrl.getInitialComponent()); tabbedPane.addTab(translate("tab.accesscontrol"), accessCtrl.getInitialComponent()); - RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(entry); - handler.addExtendedEditionControllers(ureq, getWindowControl(), this, entry); - putInitialPanel(tabbedPane); stackPanel.pushController(translate("settings.editor"), this); } diff --git a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataModel.java b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataModel.java index 651c3622f5a642d5138ac03b3c9feeb4853a7c5c..3e7f2536c3325f14168823ee98b1d46c24886cf6 100644 --- a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataModel.java +++ b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataModel.java @@ -22,6 +22,7 @@ package org.olat.repository.ui.author; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataSourceModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.repository.handlers.EditionSupport; import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; @@ -84,9 +85,11 @@ class AuthoringEntryDataModel extends DefaultFlexiTableDataSourceModel<Authoring RepositoryHandler handler = handlerFactory.getRepositoryHandler(item.getResourceType()); return (handler != null && handler.supportsLaunch()) ? Boolean.TRUE : Boolean.FALSE; } + case tools: return item.getToolsLink(); case editionSupported: { RepositoryHandler handler = handlerFactory.getRepositoryHandler(item.getResourceType()); - return (handler != null && handler.supportsEdit(item.getOLATResourceable())) ? Boolean.TRUE : Boolean.FALSE; + return (handler == null || handler.supportsEdit(item.getOLATResourceable()) == EditionSupport.no) + ? Boolean.FALSE : Boolean.TRUE; } } return null; @@ -110,7 +113,8 @@ class AuthoringEntryDataModel extends DefaultFlexiTableDataSourceModel<Authoring lastUsage("table.header.lastusage"), mark("table.header.mark"), detailsSupported(""), - editionSupported(""); + tools("table.header.actions"), + editionSupported("table.header.edit"); private final String i18nKey; diff --git a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSource.java b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSource.java index efe6edfd252473d8717ffde6b32b2f61af906fe7..3bbc32c7f25c47a6e34d4d4c92f2bfede450bf87 100644 --- a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSource.java +++ b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSource.java @@ -195,7 +195,7 @@ public class AuthoringEntryDataSource implements FlexiTableDataSourceDelegate<Au row.setAccessTypes(types); } - uifactory.forgeMarkLink(row); + uifactory.forgeLinks(row); items.add(row); } diff --git a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSourceUIFactory.java b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSourceUIFactory.java index 775abd5388a02b48f385705efaaf1fed9c14d83b..ffd4dc4d07be2c0f01471b1c8da628ffb66813f2 100644 --- a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSourceUIFactory.java +++ b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDataSourceUIFactory.java @@ -31,7 +31,7 @@ import org.olat.core.gui.translator.Translator; */ public interface AuthoringEntryDataSourceUIFactory { - public void forgeMarkLink(AuthoringEntryRow row); + public void forgeLinks(AuthoringEntryRow row); public Translator getTranslator(); diff --git a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDetailsController.java b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDetailsController.java index efa4055172e7e0d07258d22b25f3a67a97cff73a..07529804b312c44a0b60520925bf7dfdaadae16c 100644 --- a/src/main/java/org/olat/repository/ui/author/AuthoringEntryDetailsController.java +++ b/src/main/java/org/olat/repository/ui/author/AuthoringEntryDetailsController.java @@ -75,6 +75,7 @@ import org.olat.repository.RepositoryManager; import org.olat.repository.controllers.EntryChangedEvent; import org.olat.repository.controllers.EntryChangedEvent.Change; import org.olat.repository.controllers.WizardCloseResourceController; +import org.olat.repository.handlers.EditionSupport; import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; import org.olat.repository.ui.PriceMethod; @@ -220,7 +221,7 @@ public class AuthoringEntryDetailsController extends RepositoryEntryDetailsContr editLink.setIconLeftCSS("o_icon o_icon_edit"); editLink.setElementCssClass("o_sel_author_edit_entry"); boolean editManaged = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.editcontent); - editLink.setEnabled(handler.supportsEdit(entry.getOlatResource()) && !corrupted && !editManaged); + editLink.setEnabled(handler.supportsEdit(entry.getOlatResource()) != EditionSupport.no && !corrupted && !editManaged); stackPanel.addTool(editLink, Align.left); } diff --git a/src/main/java/org/olat/repository/ui/author/AuthoringEntryRow.java b/src/main/java/org/olat/repository/ui/author/AuthoringEntryRow.java index bb6961b81bfff2dcf911de06276366d22be444e9..557669cb565a8bac2330758290ade2f8e5ee6009 100644 --- a/src/main/java/org/olat/repository/ui/author/AuthoringEntryRow.java +++ b/src/main/java/org/olat/repository/ui/author/AuthoringEntryRow.java @@ -70,6 +70,7 @@ public class AuthoringEntryRow implements RepositoryEntryRef, RepositoryEntryLig private OLATResourceable olatResource; private FormLink markLink; + private FormLink toolsLink; public AuthoringEntryRow(RepositoryEntryAuthorView view, String fullnameAuthor) { key = view.getKey(); @@ -280,4 +281,12 @@ public class AuthoringEntryRow implements RepositoryEntryRef, RepositoryEntryLig public void setMarkLink(FormLink markLink) { this.markLink = markLink; } + + public FormLink getToolsLink() { + return toolsLink; + } + + public void setToolsLink(FormLink toolsLink) { + this.toolsLink = toolsLink; + } } \ No newline at end of file diff --git a/src/main/java/org/olat/repository/ui/author/CatalogSettingsController.java b/src/main/java/org/olat/repository/ui/author/CatalogSettingsController.java index 4fae0a49d84608934e81dbfa86e5b0198c7f9c96..7e22ae35ca3fa301889933570a3f65b61891e199 100644 --- a/src/main/java/org/olat/repository/ui/author/CatalogSettingsController.java +++ b/src/main/java/org/olat/repository/ui/author/CatalogSettingsController.java @@ -57,7 +57,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -class CatalogSettingsController extends FormBasicController { +public class CatalogSettingsController extends FormBasicController { private Link addToCatalogLink; private FlexiTableElement tableEl; @@ -83,7 +83,7 @@ class CatalogSettingsController extends FormBasicController { stackPanel.pushController(translate("details.categoriesheader"), this); } - protected void initToolbar() { + public void initToolbar() { addToCatalogLink = LinkFactory.createToolLink("cat", translate("details.catadd"), this, "o_icon_add"); addToCatalogLink.setElementCssClass("o_sel_repo_add_to_catalog"); addToCatalogLink.setEnabled((entry.getAccess() >= RepositoryEntry.ACC_USERS || entry.isMembersOnly())); diff --git a/src/main/java/org/olat/repository/ui/author/_content/tools.html b/src/main/java/org/olat/repository/ui/author/_content/tools.html new file mode 100644 index 0000000000000000000000000000000000000000..ee364a66e1151caa279c872f06410e111e07e20d --- /dev/null +++ b/src/main/java/org/olat/repository/ui/author/_content/tools.html @@ -0,0 +1,9 @@ +<ul class="o_dropdown list-unstyled"> +#foreach($link in $links) + #if($link == "-") + <li class="divider"></li> + #else + <li>$r.render($link)</li> + #end +#end +</ul> \ No newline at end of file diff --git a/src/main/java/org/olat/repository/ui/list/RepositoryEntryDetailsController.java b/src/main/java/org/olat/repository/ui/list/RepositoryEntryDetailsController.java index b469bb6527f69fb23c650dab68af4c01fa962faf..3f8b0e5053e922ac118b0db9715517913a1dc5c7 100644 --- a/src/main/java/org/olat/repository/ui/list/RepositoryEntryDetailsController.java +++ b/src/main/java/org/olat/repository/ui/list/RepositoryEntryDetailsController.java @@ -422,22 +422,6 @@ public class RepositoryEntryDetailsController extends FormBasicController { authorLinkNames.add(authorLink.getComponent().getComponentName()); } layoutCont.contextPut("authorlinknames", authorLinkNames); - /* - List<Identity> authors = repositoryService.getMembers(entry, GroupRoles.owner.name()); - List<String> authorLinkNames = new ArrayList<String>(authors.size()); - Set<Long> duplicates = new HashSet<>(authors.size() * 2 + 1); - int counter = 0; - for(Identity author:authors) { - if(!duplicates.contains(author.getKey())) { - String authorName = userManager.getUserDisplayName(author); - FormLink authorLink = uifactory.addFormLink("owner-" + ++counter, "owner", authorName, null, formLayout, Link.NONTRANSLATED | Link.LINK); - authorLink.setUserObject(author.getKey()); - authorLinkNames.add(authorLink.getComponent().getComponentName()); - duplicates.add(author.getKey()); - } - } - layoutCont.contextPut("authorlinknames", authorLinkNames); - */ } } diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/AccessRefusedController.java b/src/main/java/org/olat/resource/accesscontrol/ui/AccessRefusedController.java new file mode 100644 index 0000000000000000000000000000000000000000..453fce22d2580efb4441a1a807848211e9e40282 --- /dev/null +++ b/src/main/java/org/olat/resource/accesscontrol/ui/AccessRefusedController.java @@ -0,0 +1,58 @@ +/** + * <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.resource.accesscontrol.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.panel.SimpleStackedPanel; +import org.olat.core.gui.components.panel.StackedPanel; +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; + +/** + * + * Initial date: 14.08.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AccessRefusedController extends BasicController { + + public AccessRefusedController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + + VelocityContainer mainVC = createVelocityContainer("access_refused"); + StackedPanel contentP = new SimpleStackedPanel(""); + contentP.setContent(mainVC); + wControl.setWarning(translate("course.closed")); + putInitialPanel(contentP); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } +} \ No newline at end of file diff --git a/src/main/webapp/static/themes/light/modules/_icons.scss b/src/main/webapp/static/themes/light/modules/_icons.scss index e767f853c52e4bf5b414cc34d6e4266e48b44b77..861e22279b75e14a3226000857092c9148937715 100644 --- a/src/main/webapp/static/themes/light/modules/_icons.scss +++ b/src/main/webapp/static/themes/light/modules/_icons.scss @@ -107,6 +107,7 @@ $fa-css-prefix: "o_icon" !default; .o_icon_italic:before { content: $fa-var-italic; } .o_icon_landingpage:before { content: $fa-var-bullseye; } .o_icon_language:before { content: $fa-var-globe; } +.o_icon_layout:before { content: $fa-var-file-image-o; } .o_icon_link:before { content: $fa-var-link; } .o_icon_link_extern:before { content: $fa-var-external-link; } .o_icon_list:before { content: $fa-var-list; } @@ -139,6 +140,7 @@ $fa-css-prefix: "o_icon" !default; .o_icon_open_tree:before { content: $fa-var-caret-right;} .o_icon_open_togglebox:before { content: $fa-var-caret-right;} .o_icon_openolat:before, %o_icon_openolat {content:"\221E"; font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; font-weight: bold;} +.o_icon_options:before { content: $fa-var-check-square; } .o_icon_passed:before { content: $fa-var-check-circle;} .o_icon_password:before { content: $fa-var-lock;} .o_icon_phone:before { content: $fa-var-phone;} diff --git a/src/main/webapp/static/themes/light/modules/_various_modules.scss b/src/main/webapp/static/themes/light/modules/_various_modules.scss index 0a25c62b3c51265a4d6c93df5ffc0a9fbc51776f..3a36a19526fc5e2fcea6f7b395543d72cb86a89d 100644 --- a/src/main/webapp/static/themes/light/modules/_various_modules.scss +++ b/src/main/webapp/static/themes/light/modules/_various_modules.scss @@ -72,6 +72,22 @@ a.o_chelp { } } +.o_dropdown { + .divider { + @include nav-divider($dropdown-divider-bg); + } + + > li > a { + display: block; + padding: 3px 0; + clear: both; + font-weight: normal; + line-height: $line-height-base; + color: $dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } +} + /* SCORM module */ .badge { /* badge color */ diff --git a/src/main/webapp/static/themes/light/theme.css b/src/main/webapp/static/themes/light/theme.css index 9827b8f9592c2f98e7c9ff35d9c8c363ab5f6931..9e49d9cc5339f1f8bf911f78df5288e2dce3ad45 100644 --- a/src/main/webapp/static/themes/light/theme.css +++ b/src/main/webapp/static/themes/light/theme.css @@ -58,14 +58,14 @@ fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100% @media (max-width: 767px){.hidden-xs{display:none !important}}@media (min-width: 768px) and (max-width: 991px){.hidden-sm{display:none !important}}@media (min-width: 992px) and (max-width: 1199px){.hidden-md{display:none !important}}@media (min-width: 1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}} .visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}} .visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}} -@media print{.hidden-print{display:none !important}}body .modal{position:absolute;overflow:visible}body div.tooltip-inner{max-width:400px}body div.popover{max-width:450px}body .modal-body.alert{border-radius:0}body .progress{margin-bottom:0}.panel-body:nth-child(n+2){border-top:1px solid #ddd}.form-control-feedback{top:10px}.form-horizontal .has-feedback .form-control-feedback{top:10px}.btn.btn-primary.o_disabled{color:#fff !important}@font-face{font-family:'FontAwesome';src:url("../../../font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0");src:url("../../../font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0") format("embedded-opentype"),url("../../../font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0") format("woff"),url("../../../font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0") format("truetype"),url("../../../font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.o_icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.o_icon-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.o_icon-2x{font-size:2em}.o_icon-3x{font-size:3em}.o_icon-4x{font-size:4em}.o_icon-5x{font-size:5em}.o_icon-fw{width:1.28571em;text-align:center}.o_icon-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.o_icon-ul>li{position:relative}.o_icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.o_icon-li.o_icon-lg{left:-1.85714em}.o_icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.o_icon.pull-left{margin-right:.3em}.o_icon.pull-right{margin-left:.3em}.o_icon-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes 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);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.o_icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.o_icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.o_icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.o_icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.o_icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.o_icon-stack-1x,.o_icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.o_icon-stack-1x{line-height:inherit}.o_icon-stack-2x{font-size:2em}.o_icon-inverse{color:#fff}.o_icon_accessibility:before{content:"\f193"}.o_icon_actions:before{content:"\f085"}.o_icon_archive_tool:before{content:"\f019"}.o_icon_assessment_tool:before{content:"\f091"}.o_icon_attempt_limit:before{content:"\f021"}.o_icon_accept:before{content:"\f00c";color:#5cb85c}.o_icon_add:before{content:"\f055"}.o_icon_add_search:before{content:"\f00e"}.o_icon_audio:before{content:"\f028"}.o_icon_back:before{content:"\f053"}.o_icon_back_history:before{content:"\f1da"}.o_icon_bold:before{content:"\f032"}.o_icon_booking:before{content:"\f07a"}.o_icon_bookmark:before{content:"\f02e";color:#bc2d0c}.o_icon_bookmark_add:before{content:"\f097"}.o_icon_bookmark_header:before{content:"\f02e"}.o_icon_browse:before{content:"\f00e"}.o_icon_browsercheck:before{content:"\f164"}.o_icon_busy:before{content:"\f110"}.o_icon_calendar:before{content:"\f073"}.o_icon_calendar_enabled:before{content:"\f05d"}.o_icon_calendar_disabled:before{content:"\f10c"}.o_icon_calendar:before{content:"\f073"}.o_icon_caret:before{content:"\f0d7"}.o_icon_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:"\f014"}.o_icon_delete:before{content:"\f056";color:#A87E7E}.o_icon_details:before{content:"\f0eb"}.o_icon_dev:before{content:"\f188"}.o_icon_disabled:before{content:"\f10c"}.o_icon_download:before{content:"\f019"}.o_icon_edit:before{content:"\f044"}.o_icon_edit_file:before{content:"\f044"}.o_icon_edit_metadata:before{content:"\f013"}.o_icon_enabled:before{content:"\f111"}.o_icon_enlarge:before{content:"\f00e"}.o_icon_eportfolio_add:before{content:"\f12e"}.o_icon_eportfolio_link:before{content:"\f12e"}.o_icon_error:before{content:"\f06a";color:#d9534f}.o_icon_expenditure:before{content:"\f017"}.o_icon_external_link:before{content:"\f08e"}.o_icon_failed:before{content:"\f057"}.o_icon_filter:before{content:"\f0b0"}.o_icon_group:before{content:"\f0c0"}.o_icon_header:before{content:"\f1dc"}.o_icon_help:before{content:"\f059";cursor:help}.o_icon_home:before{content:"\f015"}.o_icon_impress:before{content:"\f05a"}.o_icon_important:before{content:"\f071";color:#f0ad4e}.o_icon_import:before{content:"\f093"}.o_icon_info:before{content:"\f05a";color:#5bc0de}.o_icon_info_msg:before{content:"\f06a";color:#d9534f}.o_icon_inline_editable:before{content:"\f044"}.o_icon_institution:before{content:"\f19c"}.o_icon_italic:before{content:"\f033"}.o_icon_landingpage:before{content:"\f140"}.o_icon_language:before{content:"\f0ac"}.o_icon_link:before{content:"\f0c1"}.o_icon_link_extern:before{content:"\f08e"}.o_icon_list:before{content:"\f03a"}.o_icon_list_num :before{content:"\f0cb"}.o_icon_lifecycle:before{content:"\f073"}.o_icon_locked:before{content:"\f023"}.o_icon_login:before{content:"\f090"}.o_icon_logout:before{content:"\f08b"}.o_icon_mandatory:before{content:"\f069";color:#f0ad4e}.o_icon_managed:before{content:"\f079";color:#777}.o_icon_mail:before{content:"\f003"}.o_icon_math:before{content:"\f198"}.o_icon_membersmanagement:before{content:"\f0c0"}.o_icon_menuhandel:before{content:"\f0c9"}.o_icon_message:before{content:"\f0e0"}.o_icon_move:before{content:"\f047"}.o_icon_move_down:before{content:"\f103"}.o_icon_move_left:before{content:"\f100"}.o_icon_move_right:before{content:"\f101"}.o_icon_move_up:before{content:"\f102"}.o_icon_new:before{content:"\f069";color:#5cb85c}.o_icon_new_document:before{content:"\f15c"}.o_icon_new_folder:before{content:"\f07b"}.o_icon_news:before{content:"\f05a"}.o_icon_next:before{content:"\f138"}.o_icon_next_page:before{content:"\f101"}.o_icon_next_toolbar:before{content:"\f0da"}.o_icon_notes:before{content:"\f040"}.o_icon_notification:before{content:"\f09e"}.o_icon_open_tree:before{content:"\f0da"}.o_icon_open_togglebox:before,.o_togglebox_wrapper .o_opener i:before{content:"\f0da"}.o_icon_openolat:before,.o_icon_provider_olat:before{content:"\221E";font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:bold}.o_icon_passed:before{content:"\f058"}.o_icon_password:before{content:"\f023"}.o_icon_phone:before{content:"\f095"}.o_icon_post:before{content:"\f0e5"}.o_icon_preview:before{content:"\f06e"}.o_icon_previous:before{content:"\f137"}.o_icon_previous_page:before{content:"\f100"}.o_icon_previous_toolbar:before{content:"\f0d9"}.o_icon_print:before{content:"\f02f"}.o_icon_private:before{content:"\f02f"}.o_icon_provider_guest:before{content:"\f1ae"}.o_icon_provider_ldap:before{content:"\f19c"}.o_icon_provider_shibboleth:before{content:"\f19c"}.o_icon_publish:before{content:"\f064"}.o_icon_qrcode:before{content:"\f029"}.o_icon_quickview:before{content:"\f06e"}.o_icon_radio_off:before{content:"\f10c"}.o_icon_radio_on:before{content:"\f05d"}.o_icon_rating_on:before,.o_rating .o_rating_items.o_enabled .o_icon:hover:before{content:"\f005"}.o_icon_rating_off:before{content:"\f006"}.o_icon_read:before{content:"\f10c"}.o_icon_readonly:before{content:"\f044";color:red}.o_icon_readwrite:before{content:"\f044"}.o_icon_recycle:before{content:"\f1b8"}.o_icon_refresh:before{content:"\f021"}.o_icon_reject:before{content:"\f00d";color:#d9534f}.o_icon_remove:before{content:"\f00d"}.o_icon_reply:before{content:"\f112"}.o_icon_reply_with_quote:before{content:"\f122"}.o_icon_response_feedback:before{content:"\f021"}.o_icon_rss:before{content:"\f09e"}.o_icon_rss_unsubscribe:before{content:"\f09e";color:#996633}.o_icon_search:before{content:"\f002"}.o_icon_settings:before{content:"\f085"}.o_icon_share:before{content:"\f064"}.o_icon_show_more:before{content:"\f150"}.o_icon_show_less:before{content:"\f151"}.o_icon_spacer:before{content:"\f07e"}.o_icon_split:before{content:"\f127"}.o_icon_sort:before{content:"\f0dc"}.o_icon_sort_asc:before{content:"\f0de"}.o_icon_sort_desc:before{content:"\f0dd"}.o_icon_sort_menu:before{content:"\f160"}.o_icon_start:before{content:"\f054"}.o_icon_status_available:before{content:"\f111";color:#006633}.o_icon_status_chat:before{content:"\f075"}.o_icon_status_dnd:before{content:"\f192";color:#CCCC33}.o_icon_status_unavailable:before{content:"\f05c";color:#996633}.o_icon_statistics_tool:before{content:"\f080"}.o_icon_table:before{content:"\f0ce"}.o_icon_table_large:before{content:"\f009"}.o_icon_tags:before{content:"\f02c"}.o_icon_timelimit:before{content:"\f1e2"}.o_icon_toggle:before{content:"\f111"}.o_icon_to_read:before{content:"\f111"}.o_icon_tool:before{content:"\f013"}.o_icon_tools:before{content:"\f0ad"}.o_icon_top:before{content:"\f077"}.o_icon_translation_item:before{content:"\f1c9"}.o_icon_translation_package:before{content:"\f115"}.o_icon_user:before{content:"\f007"}.o_icon_user_vip:before{content:"\f19d"}.o_icon_user_anonymous:before{content:"\f128"}.o_icon_upload:before{content:"\f093"}.o_icon_version:before{content:"\f1da"}.o_icon_video:before{content:"\f008"}.o_icon_warn:before{content:"\f071";color:#f0ad4e}.o_icon_wizard:before{content:"\f0d0"}.o_CourseModule_icon:before,.o_course_icon:before{content:"\f1b2"}.o_EPStructuredMapTemplate_icon:before{content:"\f12e"}.o_FileResource-BLOG_icon:before{content:"\f0a1"}.o_FileResource-IMSCP_icon:before{content:"\f187"}.o_FileResource-PODCAST_icon:before{content:"\f03d"}.o_FileResource-SHAREDFOLDER:before{content:"\f08e"}.o_FileResource-SCORMCP_icon:before{content:"\f187"}.o_FileResource-SURVEY_icon:before{content:"\f11a"}.o_FileResource-TEST_icon:before{content:"\f044"}.o_FileResource-WIKI_icon:before{content:"\f0ac"}.o_FileResource-SHAREDFOLDER_icon:before{content:"\f115"}.o_FileResource-GLOSSARY_icon:before{content:"\f19d"}.o_FileResource-PDF_icon:before{content:"\f1c1"}.o_FileResource-XLS_icon:before{content:"\f1c3"}.o_FileResource-PPT_icon:before{content:"\f1c4"}.o_FileResource-DOC_icon:before{content:"\f1c2"}.o_FileResource-ANIM_icon:before{content:"\f1c8"}.o_FileResource-IMAGE_icon:before{content:"\f1c5"}.o_FileResource-SOUND_icon:before{content:"\f1c7"}.o_FileResource-MOVIE_icon:before{content:"\f1c8"}.o_FileResource-FILE_icon:before{content:"\f016"}.o_sp_icon:before{content:"\f0f6"}.o_st_icon:before{content:"\f1b3"}.o_tu_icon:before{content:"\f08e"}.o_bc_icon:before{content:"\f115"}.o_lti_icon:before{content:"\f08e"}.o_cp_icon:before{content:"\f187"}.o_cp_item:before{content:"\f0f6"}.o_scorm_icon:before{content:"\f187"}.o_en_icon:before{content:"\f090"}.o_fo_icon:before{content:"\f0e6"}.o_co_icon:before{content:"\f003"}.o_infomsg_icon:before{content:"\f05a"}.o_cal_icon:before{content:"\f073"}.o_wiki_icon:before{content:"\f0ac"}.o_podcast_icon:before{content:"\f03d"}.o_blog_icon:before{content:"\f0a1"}.o_ep_icon:before{content:"\f12e"}.o_iqtest_icon:before{content:"\f044"}.o_iqself_icon:before{content:"\f044"}.o_iqsurv_icon:before{content:"\f11a"}.o_ta_icon:before{content:"\f0ae"}.o_ms_icon:before{content:"\f087"}.o_dialog_icon:before{content:"\f0c5"}.o_projectbroker_icon:before{content:"\f10c"}.o_ll_icon:before{content:"\f0c1"}.o_den_icon:before{content:"\f133"}.o_cmembers_icon:before{content:"\f0c0"}.o_cl_icon:before{content:"\f046"}.o_vc_icon:before{content:"\f108"}.o_vitero_icon:before{content:"\f108"}.o_openmeetings_icon:before{content:"\f108"}.o_portlet_infomsg_icon:before{content:"\f05a"}.o_portlet_quickstart_icon:before{content:"\f1d9"}.o_portlet_bookmark_icon:before{content:"\f02e"}.o_portlet_groups_icon:before{content:"\f0c0"}.o_portlet_notes_icon:before{content:"\f040"}.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:"\f00d"}.o_icon_pool_public:before{content:"\f00c"}.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_qtisc:before{content:"\f192"}.o_mi_qtimc:before{content:"\f046"}.o_mi_qtikprim:before{content:"\f14a"}.o_mi_qtifib:before{content:"\f141"}.o_mi_qtiessay:before{content:"\f036"}.o_black_led:before{content:"\f111";color:#428bca}.o_green_led:before{content:"\f111";color:#5cb85c}.o_yellow_led:before{content:"\f111";color:#f0ad4e}.o_red_led:before{content:"\f111";color:#d9534f}.o_ac_token_icon:before{content:"\f084"}.o_ac_free_icon:before{content:"\f06b"}.o_ac_group_icon:before{content:"\f0c0"}.o_ac_membersonly_icon:before{content:"\f023"}.o_ac_paypal_icon:before{content:"\f09d"}.o_ac_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_status_succes_icon:before{content:"\f00c";color:#5cb85c}.o_ac_status_waiting_icon:before{content:"\f017";color:#428bca}.o_ac_order_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_order_status_prepayment_icon:before{content:"\f0d6";color:#5bc0de}.o_ac_order_status_payed_icon:before{content:"\f00c";color:#5cb85c}.o_ac_order_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_order_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_order_status_warning_icon:before{content:"\f12a";color:#f0ad4e}.o_scorm_org:before{content:"\f187"}.o_scorm_item:before{content:"\f016"}.o_scorm_completed:before,.o_scorm_passed:before{content:"\f058"}.o_scorm_failed:before{content:"\f071"}.o_scorm_incomplete:before{content:"\f071"}.o_scorm_not_attempted:before{background:none}.o_midpub:before{content:"\f058"}.o_midwarn:before{content:"\f071"}.o_midlock:before{content:"\f023"}.o_miderr:before{content:"\f071"}.o_middel:before{content:"\f12d"}.o_filetype_file:before,.o_filetype_ico:before{content:"\f016"}.o_filetype_folder:before{content:"\f114"}.o_filetype_folder_open:before{content:"\f115"}.o_filetype_zip:before,.o_filetype_gz:before,.o_filetype_tar:before,.o_filetype_tgz:before{content:"\f1c6"}.o_filetype_css:before,.o_filetype_js:before,.o_filetype_java:before,.o_filetype_numbers:before,.o_filetype_ods:before,.o_filetype_xml:before,.o_filetype_xsl:before{content:"\f1c9"}.o_filetype_bat_icon:before,.o_filetype_bat:before,.o_filetype_exe:before,.o_filetype_app:before,.o_filetype_sh:before{content:"\f1c9"}.o_filetype_xls:before,.o_filetype_xlsx:before{content:"\f1c3"}.o_filetype_png:before,.o_filetype_tiff:before,.o_filetype_webp:before,.o_filetype_gif:before,.o_filetype_ico:before,.o_filetype_jpeg:before,.o_filetype_bmp:before,.o_filetype_odg:before,.o_filetype_eps:before,.o_filetype_jpg:before{content:"\f1c5"}.o_filetype_psd:before,.o_filetype_avi:before,.o_filetype_dvi:before,.o_filetype_mp4:before,.o_filetype_m4v:before,.o_filetype_webm:before,.o_filetype_ogg:before,.o_filetype_video:before,.o_filetype_mov:before,.o_filetype_mpeg:before,.o_filetype_mpg:before,.o_filetype_qt:before,.o_filetype_ra:before,.o_filetype_ram:before,.o_filetype_swf:before,.o_filetype_flv:before{content:"\f1c8"}.o_filetype_midi:before,.o_filetype_audio:before,.o_filetype_mp3:before,.o_filetype_m3u:before,.o_filetype_wav:before{content:"\f1c7"}.o_filetype_ps:before,.o_filetype_pdf:before{content:"\f1c1"}.o_filetype_key:before,.o_filetype_odp:before,.o_filetype_ppt:before,.o_filetype_pptx:before{content:"\f1c4"}.o_filetype_odf:before,.o_filetype_rtf:before,.o_filetype_readme:before,.o_filetype_README:before,.o_filetype_log:before,.o_filetype_txt:before,.o_filetype_htm:before,.o_filetype_html:before{content:"\f0f6"}.o_filetype_odt:before,.o_filetype_pages:before,.o_filetype_doc:before,.o_filetype_docx:before{content:"\f1c2"}.o_icon_apple:before{content:"\f179"}.o_icon_facebook:before{content:"\f082"}.o_icon_twitter:before{content:"\f081"}.o_icon_google:before{content:"\f0d4"}.o_icon_delicious:before{content:"\f1a5"}.o_icon_digg:before{content:"\f1a6"}.o_icon_mailto:before{content:"\f199"}.o_icon_link:before{content:"\f0c1"}.o_icon_yahoo:before{content:"\f19e"}a.o_icon:hover,a.o_icon:focus{text-decoration:none}img.o_emoticons_angel{background:url(../light/images/emoticons/smiley-angel.png);width:16px;height:16px}img.o_emoticons_angry{background:url(../light/images/emoticons/smiley-mad.png);width:16px;height:16px}img.o_emoticons_blushing{background:url(../light/images/emoticons/smiley-red.png);width:16px;height:16px}img.o_emoticons_confused{background:url(../light/images/emoticons/smiley-confuse.png);width:16px;height:16px}img.o_emoticons_cool{background:url(../light/images/emoticons/smiley-cool.png);width:16px;height:16px}img.o_emoticons_cry{background:url(../light/images/emoticons/smiley-cry.png);width:16px;height:16px}img.o_emoticons_devil{background:url(../light/images/emoticons/smiley-evil.png);width:16px;height:16px}img.o_emoticons_grin{background:url(../light/images/emoticons/smiley-grin.png);width:16px;height:16px}img.o_emoticons_kiss{background:url(../light/images/emoticons/smiley-kiss.png);width:16px;height:16px}img.o_emoticons_ohoh{background:url(../light/images/emoticons/smiley-eek.png);width:16px;height:16px}img.o_emoticons_sad{background:url(../light/images/emoticons/smiley-sad.png);width:16px;height:16px}img.o_emoticons_sick{background:url(../light/images/emoticons/smiley-sad-blue.png);width:16px;height:16px}img.o_emoticons_smile{background:url(../light/images/emoticons/smiley.png);width:16px;height:16px}img.o_emoticons_tongue{background:url(../light/images/emoticons/smiley-razz.png);width:16px;height:16px}img.o_emoticons_ugly{background:url(../light/images/emoticons/smiley-money.png);width:16px;height:16px}img.o_emoticons_weird{background:url(../light/images/emoticons/smiley-nerd.png);width:16px;height:16px}img.o_emoticons_wink{background:url(../light/images/emoticons/smiley-wink.png);width:16px;height:16px}img.o_emoticons_worried{background:url(../light/images/emoticons/smiley-roll-blue.png);width:16px;height:16px}img.o_emoticons_up{background:url(../light/images/emoticons/thumb-up.png);width:16px;height:16px}img.o_emoticons_down{background:url(../light/images/emoticons/thumb.png);width:16px;height:16px}.o_block_bottom,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry,.o_header_with_buttons,.o_search_result{margin-bottom:1em}.o_block_top,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry{margin-top:1em}.o_block_large_bottom,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-bottom:2em}.o_block_large_top,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-top:2em}.o_scrollblock{overflow-x:auto;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_button_group{text-align:center}.o_button_group a,.o_button_group input,.o_button_group button,.o_button_group .btn-group{margin-right:5px;margin-bottom:0.5em}.o_button_group a:last-child,.o_button_group input:last-child,.o_button_group button:last-child,.o_button_group .btn-group:last-child{margin-right:0}.o_button_group .btn-group a,.o_button_group .btn-group input,.o_button_group .btn-group button{margin-right:0;margin-bottom:0}.o_button_group .dropdown-menu{text-align:left}.o_button_group_left{text-align:left}.o_button_group_right{text-align:right}.o_button_group_top{margin-top:0}.o_header_with_buttons:before,.o_header_with_buttons:after{content:" ";display:table}.o_header_with_buttons:after{clear:both}.o_header_with_buttons h3,.o_header_with_buttons h4,.o_header_with_buttons .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_header_with_buttons h2{display:inline-block}.o_header_with_buttons .o_button_group{margin-top:10px;margin-bottom:0;float:right}.panel-heading.o_header_with_buttons{margin-bottom:0}.o_xsmall{font-size:12px}.o_small,.o_comments .o_comment_wrapper h5,.o_comments .o_comment_wrapper .o_comment,.o_bc_meta,.tooltip,.o_noti,.o_block_with_datecomp .o_meta,.o_togglebox_wrapper div.o_togglebox_content .o_hide,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label,.o_course_run .o_toc .o_entry{font-size:12px}.o_large{font-size:18px}.o_xlarge{font-size:18px}.o_disabled{color:#777 !important;cursor:default}.o_disabled:hover{color:#777 !important}.o_dimmed{opacity:0.4;filter:alpha(opacity=40)}.o_selected{font-weight:bold}.o_deleted{text-decoration:line-through}.o_clickable{cursor:pointer}.o_ochre{color:#c8a959}.o_blue{color:#12223F}.o_undecorated:hover,.o_undecorated:focus,.o_disabled:hover,.o_withEllipsis .o_morelink:hover,.o_withEllipsis .o_lesslink:hover,#o_main_wrapper #o_toplink:hover,#o_footer_wrapper #o_footer_container #o_footer_powered a:hover,#o_share a:hover,.o_toolbar .o_tools_container a:hover,.o_button_toggle:hover,.o_im_message_group .o_im_from:hover,.o_noti .o_label:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover,.o_catalog .o_level .o_meta .o_title a:hover,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover,.o_repo_details .o_social .o_comments:hover,.o_login .o_login_register:hover,.o_disabled:focus,.o_withEllipsis .o_morelink:focus,.o_withEllipsis .o_lesslink:focus,#o_main_wrapper #o_toplink:focus,#o_footer_wrapper #o_footer_container #o_footer_powered a:focus,#o_share a:focus,.o_toolbar .o_tools_container a:focus,.o_button_toggle:focus,.o_im_message_group .o_im_from:focus,.o_noti .o_label:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:focus,.o_catalog .o_level .o_meta .o_title a:focus,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:focus,.o_repo_details .o_social .o_comments:focus,.o_login .o_login_register:focus{text-decoration:none}.o_copy_code{overflow-x:auto;overflow-y:auto;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}.o_nowrap{white-space:nowrap}.o_titled_wrapper .o_content{margin-top:20px}.o_video{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_image{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_with_hyphens{-ms-word-break:break-all;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.o_withEllipsis .o_ellipsis_links{float:right}.o_withEllipsis .o_morelink,.o_withEllipsis .o_lesslink{display:none}.o_withEllipsis.o_hasOverflow .o_morelink{display:block}.o_withEllipsis.o_hasOverflow .o_lesslink{display:none}.o_withEllipsis.o_hasOverflow.o_showOverflow{height:auto !important}.o_withEllipsis.o_hasOverflow.o_showOverflow .o_morelink{display:none}.o_withEllipsis.o_hasOverflow.o_showOverflow .o_lesslink{display:block}h1{color:#428bca}h2{color:#428bca}h3{color:#428bca}h4,.o_cal .fc-header-title h2{color:#428bca}h5{color:#428bca}h5{color:#428bca}fieldset legend{color:#333}html{position:relative;min-height:100%}body{min-height:100%;margin-bottom:70px}#o_main_wrapper{background:#fff;z-index:3}#o_main_wrapper #o_main_container{background:#fff}#o_main_wrapper #o_main_container #o_main_left{float:left;z-index:2;position:relative;background:#fff}#o_main_wrapper #o_main_container #o_main_left #o_main_left_content{padding:0 0 0 15px}#o_main_wrapper #o_main_container #o_main_left #o_main_left_toggle{position:absolute;display:none;right:0;top:70px;margin-right:-30px;font-size:25px;line-height:35px;text-align:center;width:30px;height:35px;z-index:3;border:1px solid #ddd;border-left:none;border-bottom-right-radius:4px;border-top-right-radius:4px;background-color:#fbfbfb;-webkit-box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);color:#428bca}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas{background:#fbfbfb;-webkit-box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);min-width:250px}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas #o_main_left_content{padding:0 0 0 0}#o_main_wrapper #o_main_container #o_main_right{float:right;z-index:2;position:relative;background:inherit}#o_main_wrapper #o_main_container #o_main_right #o_main_right_content{padding:0 15px 0 0}#o_main_wrapper #o_main_container #o_main_center{position:relative;z-index:1;background:inherit}#o_main_wrapper #o_main_container #o_main_center h2:first-child{margin-top:0}@media screen and (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center{margin-left:0 !important}}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:0 15px}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content #o_main_center_content_inner{padding-bottom:15px}#o_main_wrapper #o_toplink{position:absolute;bottom:0;right:15px;text-align:center;z-index:3}@media (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:15px}} +@media print{.hidden-print{display:none !important}}body .modal{position:absolute;overflow:visible}body div.tooltip-inner{max-width:400px}body div.popover{max-width:450px}body .modal-body.alert{border-radius:0}body .progress{margin-bottom:0}.panel-body:nth-child(n+2){border-top:1px solid #ddd}.form-control-feedback{top:10px}.form-horizontal .has-feedback .form-control-feedback{top:10px}.btn.btn-primary.o_disabled{color:#fff !important}@font-face{font-family:'FontAwesome';src:url("../../../font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0");src:url("../../../font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0") format("embedded-opentype"),url("../../../font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0") format("woff"),url("../../../font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0") format("truetype"),url("../../../font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.o_icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.o_icon-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.o_icon-2x{font-size:2em}.o_icon-3x{font-size:3em}.o_icon-4x{font-size:4em}.o_icon-5x{font-size:5em}.o_icon-fw{width:1.28571em;text-align:center}.o_icon-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.o_icon-ul>li{position:relative}.o_icon-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.o_icon-li.o_icon-lg{left:-1.85714em}.o_icon-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.o_icon.pull-left{margin-right:.3em}.o_icon.pull-right{margin-left:.3em}.o_icon-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes 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);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.o_icon-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.o_icon-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.o_icon-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.o_icon-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.o_icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.o_icon-stack-1x,.o_icon-stack-2x{position:absolute;left:0;width:100%;text-align:center}.o_icon-stack-1x{line-height:inherit}.o_icon-stack-2x{font-size:2em}.o_icon-inverse{color:#fff}.o_icon_accessibility:before{content:"\f193"}.o_icon_actions:before{content:"\f085"}.o_icon_archive_tool:before{content:"\f019"}.o_icon_assessment_tool:before{content:"\f091"}.o_icon_attempt_limit:before{content:"\f021"}.o_icon_accept:before{content:"\f00c";color:#5cb85c}.o_icon_add:before{content:"\f055"}.o_icon_add_search:before{content:"\f00e"}.o_icon_audio:before{content:"\f028"}.o_icon_back:before{content:"\f053"}.o_icon_back_history:before{content:"\f1da"}.o_icon_bold:before{content:"\f032"}.o_icon_booking:before{content:"\f07a"}.o_icon_bookmark:before{content:"\f02e";color:#bc2d0c}.o_icon_bookmark_add:before{content:"\f097"}.o_icon_bookmark_header:before{content:"\f02e"}.o_icon_browse:before{content:"\f00e"}.o_icon_browsercheck:before{content:"\f164"}.o_icon_busy:before{content:"\f110"}.o_icon_calendar:before{content:"\f073"}.o_icon_calendar_enabled:before{content:"\f05d"}.o_icon_calendar_disabled:before{content:"\f10c"}.o_icon_calendar:before{content:"\f073"}.o_icon_caret:before{content:"\f0d7"}.o_icon_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:"\f014"}.o_icon_delete:before{content:"\f056";color:#A87E7E}.o_icon_details:before{content:"\f0eb"}.o_icon_dev:before{content:"\f188"}.o_icon_disabled:before{content:"\f10c"}.o_icon_download:before{content:"\f019"}.o_icon_edit:before{content:"\f044"}.o_icon_edit_file:before{content:"\f044"}.o_icon_edit_metadata:before{content:"\f013"}.o_icon_enabled:before{content:"\f111"}.o_icon_enlarge:before{content:"\f00e"}.o_icon_eportfolio_add:before{content:"\f12e"}.o_icon_eportfolio_link:before{content:"\f12e"}.o_icon_error:before{content:"\f06a";color:#d9534f}.o_icon_expenditure:before{content:"\f017"}.o_icon_external_link:before{content:"\f08e"}.o_icon_failed:before{content:"\f057"}.o_icon_filter:before{content:"\f0b0"}.o_icon_group:before{content:"\f0c0"}.o_icon_header:before{content:"\f1dc"}.o_icon_help:before{content:"\f059";cursor:help}.o_icon_home:before{content:"\f015"}.o_icon_impress:before{content:"\f05a"}.o_icon_important:before{content:"\f071";color:#f0ad4e}.o_icon_import:before{content:"\f093"}.o_icon_info:before{content:"\f05a";color:#5bc0de}.o_icon_info_msg:before{content:"\f06a";color:#d9534f}.o_icon_inline_editable:before{content:"\f044"}.o_icon_institution:before{content:"\f19c"}.o_icon_italic:before{content:"\f033"}.o_icon_landingpage:before{content:"\f140"}.o_icon_language:before{content:"\f0ac"}.o_icon_layout:before{content:"\f1c5"}.o_icon_link:before{content:"\f0c1"}.o_icon_link_extern:before{content:"\f08e"}.o_icon_list:before{content:"\f03a"}.o_icon_list_num :before{content:"\f0cb"}.o_icon_lifecycle:before{content:"\f073"}.o_icon_locked:before{content:"\f023"}.o_icon_login:before{content:"\f090"}.o_icon_logout:before{content:"\f08b"}.o_icon_mandatory:before{content:"\f069";color:#f0ad4e}.o_icon_managed:before{content:"\f079";color:#777}.o_icon_mail:before{content:"\f003"}.o_icon_math:before{content:"\f198"}.o_icon_membersmanagement:before{content:"\f0c0"}.o_icon_menuhandel:before{content:"\f0c9"}.o_icon_message:before{content:"\f0e0"}.o_icon_move:before{content:"\f047"}.o_icon_move_down:before{content:"\f103"}.o_icon_move_left:before{content:"\f100"}.o_icon_move_right:before{content:"\f101"}.o_icon_move_up:before{content:"\f102"}.o_icon_new:before{content:"\f069";color:#5cb85c}.o_icon_new_document:before{content:"\f15c"}.o_icon_new_folder:before{content:"\f07b"}.o_icon_news:before{content:"\f05a"}.o_icon_next:before{content:"\f138"}.o_icon_next_page:before{content:"\f101"}.o_icon_next_toolbar:before{content:"\f0da"}.o_icon_notes:before{content:"\f040"}.o_icon_notification:before{content:"\f09e"}.o_icon_open_tree:before{content:"\f0da"}.o_icon_open_togglebox:before,.o_togglebox_wrapper .o_opener i:before{content:"\f0da"}.o_icon_openolat:before,.o_icon_provider_olat:before{content:"\221E";font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:bold}.o_icon_options:before{content:"\f14a"}.o_icon_passed:before{content:"\f058"}.o_icon_password:before{content:"\f023"}.o_icon_phone:before{content:"\f095"}.o_icon_post:before{content:"\f0e5"}.o_icon_preview:before{content:"\f06e"}.o_icon_previous:before{content:"\f137"}.o_icon_previous_page:before{content:"\f100"}.o_icon_previous_toolbar:before{content:"\f0d9"}.o_icon_print:before{content:"\f02f"}.o_icon_private:before{content:"\f02f"}.o_icon_provider_guest:before{content:"\f1ae"}.o_icon_provider_ldap:before{content:"\f19c"}.o_icon_provider_shibboleth:before{content:"\f19c"}.o_icon_publish:before{content:"\f064"}.o_icon_qrcode:before{content:"\f029"}.o_icon_quickview:before{content:"\f06e"}.o_icon_radio_off:before{content:"\f10c"}.o_icon_radio_on:before{content:"\f05d"}.o_icon_rating_on:before,.o_rating .o_rating_items.o_enabled .o_icon:hover:before{content:"\f005"}.o_icon_rating_off:before{content:"\f006"}.o_icon_read:before{content:"\f10c"}.o_icon_readonly:before{content:"\f044";color:red}.o_icon_readwrite:before{content:"\f044"}.o_icon_recycle:before{content:"\f1b8"}.o_icon_refresh:before{content:"\f021"}.o_icon_reject:before{content:"\f00d";color:#d9534f}.o_icon_remove:before{content:"\f00d"}.o_icon_reply:before{content:"\f112"}.o_icon_reply_with_quote:before{content:"\f122"}.o_icon_response_feedback:before{content:"\f021"}.o_icon_rss:before{content:"\f09e"}.o_icon_rss_unsubscribe:before{content:"\f09e";color:#996633}.o_icon_search:before{content:"\f002"}.o_icon_settings:before{content:"\f085"}.o_icon_share:before{content:"\f064"}.o_icon_show_more:before{content:"\f150"}.o_icon_show_less:before{content:"\f151"}.o_icon_spacer:before{content:"\f07e"}.o_icon_split:before{content:"\f127"}.o_icon_sort:before{content:"\f0dc"}.o_icon_sort_asc:before{content:"\f0de"}.o_icon_sort_desc:before{content:"\f0dd"}.o_icon_sort_menu:before{content:"\f160"}.o_icon_start:before{content:"\f054"}.o_icon_status_available:before{content:"\f111";color:#006633}.o_icon_status_chat:before{content:"\f075"}.o_icon_status_dnd:before{content:"\f192";color:#CCCC33}.o_icon_status_unavailable:before{content:"\f05c";color:#996633}.o_icon_statistics_tool:before{content:"\f080"}.o_icon_table:before{content:"\f0ce"}.o_icon_table_large:before{content:"\f009"}.o_icon_tags:before{content:"\f02c"}.o_icon_timelimit:before{content:"\f1e2"}.o_icon_toggle:before{content:"\f111"}.o_icon_to_read:before{content:"\f111"}.o_icon_tool:before{content:"\f013"}.o_icon_tools:before{content:"\f0ad"}.o_icon_top:before{content:"\f077"}.o_icon_translation_item:before{content:"\f1c9"}.o_icon_translation_package:before{content:"\f115"}.o_icon_user:before{content:"\f007"}.o_icon_user_vip:before{content:"\f19d"}.o_icon_user_anonymous:before{content:"\f128"}.o_icon_upload:before{content:"\f093"}.o_icon_version:before{content:"\f1da"}.o_icon_video:before{content:"\f008"}.o_icon_warn:before{content:"\f071";color:#f0ad4e}.o_icon_wizard:before{content:"\f0d0"}.o_CourseModule_icon:before,.o_course_icon:before{content:"\f1b2"}.o_EPStructuredMapTemplate_icon:before{content:"\f12e"}.o_FileResource-BLOG_icon:before{content:"\f0a1"}.o_FileResource-IMSCP_icon:before{content:"\f187"}.o_FileResource-PODCAST_icon:before{content:"\f03d"}.o_FileResource-SHAREDFOLDER:before{content:"\f08e"}.o_FileResource-SCORMCP_icon:before{content:"\f187"}.o_FileResource-SURVEY_icon:before{content:"\f11a"}.o_FileResource-TEST_icon:before{content:"\f044"}.o_FileResource-WIKI_icon:before{content:"\f0ac"}.o_FileResource-SHAREDFOLDER_icon:before{content:"\f115"}.o_FileResource-GLOSSARY_icon:before{content:"\f19d"}.o_FileResource-PDF_icon:before{content:"\f1c1"}.o_FileResource-XLS_icon:before{content:"\f1c3"}.o_FileResource-PPT_icon:before{content:"\f1c4"}.o_FileResource-DOC_icon:before{content:"\f1c2"}.o_FileResource-ANIM_icon:before{content:"\f1c8"}.o_FileResource-IMAGE_icon:before{content:"\f1c5"}.o_FileResource-SOUND_icon:before{content:"\f1c7"}.o_FileResource-MOVIE_icon:before{content:"\f1c8"}.o_FileResource-FILE_icon:before{content:"\f016"}.o_sp_icon:before{content:"\f0f6"}.o_st_icon:before{content:"\f1b3"}.o_tu_icon:before{content:"\f08e"}.o_bc_icon:before{content:"\f115"}.o_lti_icon:before{content:"\f08e"}.o_cp_icon:before{content:"\f187"}.o_cp_item:before{content:"\f0f6"}.o_scorm_icon:before{content:"\f187"}.o_en_icon:before{content:"\f090"}.o_fo_icon:before{content:"\f0e6"}.o_co_icon:before{content:"\f003"}.o_infomsg_icon:before{content:"\f05a"}.o_cal_icon:before{content:"\f073"}.o_wiki_icon:before{content:"\f0ac"}.o_podcast_icon:before{content:"\f03d"}.o_blog_icon:before{content:"\f0a1"}.o_ep_icon:before{content:"\f12e"}.o_iqtest_icon:before{content:"\f044"}.o_iqself_icon:before{content:"\f044"}.o_iqsurv_icon:before{content:"\f11a"}.o_ta_icon:before{content:"\f0ae"}.o_ms_icon:before{content:"\f087"}.o_dialog_icon:before{content:"\f0c5"}.o_projectbroker_icon:before{content:"\f10c"}.o_ll_icon:before{content:"\f0c1"}.o_den_icon:before{content:"\f133"}.o_cmembers_icon:before{content:"\f0c0"}.o_cl_icon:before{content:"\f046"}.o_vc_icon:before{content:"\f108"}.o_vitero_icon:before{content:"\f108"}.o_openmeetings_icon:before{content:"\f108"}.o_portlet_infomsg_icon:before{content:"\f05a"}.o_portlet_quickstart_icon:before{content:"\f1d9"}.o_portlet_bookmark_icon:before{content:"\f02e"}.o_portlet_groups_icon:before{content:"\f0c0"}.o_portlet_notes_icon:before{content:"\f040"}.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:"\f00d"}.o_icon_pool_public:before{content:"\f00c"}.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_qtisc:before{content:"\f192"}.o_mi_qtimc:before{content:"\f046"}.o_mi_qtikprim:before{content:"\f14a"}.o_mi_qtifib:before{content:"\f141"}.o_mi_qtiessay:before{content:"\f036"}.o_black_led:before{content:"\f111";color:#428bca}.o_green_led:before{content:"\f111";color:#5cb85c}.o_yellow_led:before{content:"\f111";color:#f0ad4e}.o_red_led:before{content:"\f111";color:#d9534f}.o_ac_token_icon:before{content:"\f084"}.o_ac_free_icon:before{content:"\f06b"}.o_ac_group_icon:before{content:"\f0c0"}.o_ac_membersonly_icon:before{content:"\f023"}.o_ac_paypal_icon:before{content:"\f09d"}.o_ac_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_status_succes_icon:before{content:"\f00c";color:#5cb85c}.o_ac_status_waiting_icon:before{content:"\f017";color:#428bca}.o_ac_order_status_new_icon:before{content:"\f069";color:#428bca}.o_ac_order_status_prepayment_icon:before{content:"\f0d6";color:#5bc0de}.o_ac_order_status_payed_icon:before{content:"\f00c";color:#5cb85c}.o_ac_order_status_canceled_icon:before{content:"\f068";color:#f0ad4e}.o_ac_order_status_error_icon:before{content:"\f00d";color:#d9534f}.o_ac_order_status_warning_icon:before{content:"\f12a";color:#f0ad4e}.o_scorm_org:before{content:"\f187"}.o_scorm_item:before{content:"\f016"}.o_scorm_completed:before,.o_scorm_passed:before{content:"\f058"}.o_scorm_failed:before{content:"\f071"}.o_scorm_incomplete:before{content:"\f071"}.o_scorm_not_attempted:before{background:none}.o_midpub:before{content:"\f058"}.o_midwarn:before{content:"\f071"}.o_midlock:before{content:"\f023"}.o_miderr:before{content:"\f071"}.o_middel:before{content:"\f12d"}.o_filetype_file:before,.o_filetype_ico:before{content:"\f016"}.o_filetype_folder:before{content:"\f114"}.o_filetype_folder_open:before{content:"\f115"}.o_filetype_zip:before,.o_filetype_gz:before,.o_filetype_tar:before,.o_filetype_tgz:before{content:"\f1c6"}.o_filetype_css:before,.o_filetype_js:before,.o_filetype_java:before,.o_filetype_numbers:before,.o_filetype_ods:before,.o_filetype_xml:before,.o_filetype_xsl:before{content:"\f1c9"}.o_filetype_bat_icon:before,.o_filetype_bat:before,.o_filetype_exe:before,.o_filetype_app:before,.o_filetype_sh:before{content:"\f1c9"}.o_filetype_xls:before,.o_filetype_xlsx:before{content:"\f1c3"}.o_filetype_png:before,.o_filetype_tiff:before,.o_filetype_webp:before,.o_filetype_gif:before,.o_filetype_ico:before,.o_filetype_jpeg:before,.o_filetype_bmp:before,.o_filetype_odg:before,.o_filetype_eps:before,.o_filetype_jpg:before{content:"\f1c5"}.o_filetype_psd:before,.o_filetype_avi:before,.o_filetype_dvi:before,.o_filetype_mp4:before,.o_filetype_m4v:before,.o_filetype_webm:before,.o_filetype_ogg:before,.o_filetype_video:before,.o_filetype_mov:before,.o_filetype_mpeg:before,.o_filetype_mpg:before,.o_filetype_qt:before,.o_filetype_ra:before,.o_filetype_ram:before,.o_filetype_swf:before,.o_filetype_flv:before{content:"\f1c8"}.o_filetype_midi:before,.o_filetype_audio:before,.o_filetype_mp3:before,.o_filetype_m3u:before,.o_filetype_wav:before{content:"\f1c7"}.o_filetype_ps:before,.o_filetype_pdf:before{content:"\f1c1"}.o_filetype_key:before,.o_filetype_odp:before,.o_filetype_ppt:before,.o_filetype_pptx:before{content:"\f1c4"}.o_filetype_odf:before,.o_filetype_rtf:before,.o_filetype_readme:before,.o_filetype_README:before,.o_filetype_log:before,.o_filetype_txt:before,.o_filetype_htm:before,.o_filetype_html:before{content:"\f0f6"}.o_filetype_odt:before,.o_filetype_pages:before,.o_filetype_doc:before,.o_filetype_docx:before{content:"\f1c2"}.o_icon_apple:before{content:"\f179"}.o_icon_facebook:before{content:"\f082"}.o_icon_twitter:before{content:"\f081"}.o_icon_google:before{content:"\f0d4"}.o_icon_delicious:before{content:"\f1a5"}.o_icon_digg:before{content:"\f1a6"}.o_icon_mailto:before{content:"\f199"}.o_icon_link:before{content:"\f0c1"}.o_icon_yahoo:before{content:"\f19e"}a.o_icon:hover,a.o_icon:focus{text-decoration:none}img.o_emoticons_angel{background:url(../light/images/emoticons/smiley-angel.png);width:16px;height:16px}img.o_emoticons_angry{background:url(../light/images/emoticons/smiley-mad.png);width:16px;height:16px}img.o_emoticons_blushing{background:url(../light/images/emoticons/smiley-red.png);width:16px;height:16px}img.o_emoticons_confused{background:url(../light/images/emoticons/smiley-confuse.png);width:16px;height:16px}img.o_emoticons_cool{background:url(../light/images/emoticons/smiley-cool.png);width:16px;height:16px}img.o_emoticons_cry{background:url(../light/images/emoticons/smiley-cry.png);width:16px;height:16px}img.o_emoticons_devil{background:url(../light/images/emoticons/smiley-evil.png);width:16px;height:16px}img.o_emoticons_grin{background:url(../light/images/emoticons/smiley-grin.png);width:16px;height:16px}img.o_emoticons_kiss{background:url(../light/images/emoticons/smiley-kiss.png);width:16px;height:16px}img.o_emoticons_ohoh{background:url(../light/images/emoticons/smiley-eek.png);width:16px;height:16px}img.o_emoticons_sad{background:url(../light/images/emoticons/smiley-sad.png);width:16px;height:16px}img.o_emoticons_sick{background:url(../light/images/emoticons/smiley-sad-blue.png);width:16px;height:16px}img.o_emoticons_smile{background:url(../light/images/emoticons/smiley.png);width:16px;height:16px}img.o_emoticons_tongue{background:url(../light/images/emoticons/smiley-razz.png);width:16px;height:16px}img.o_emoticons_ugly{background:url(../light/images/emoticons/smiley-money.png);width:16px;height:16px}img.o_emoticons_weird{background:url(../light/images/emoticons/smiley-nerd.png);width:16px;height:16px}img.o_emoticons_wink{background:url(../light/images/emoticons/smiley-wink.png);width:16px;height:16px}img.o_emoticons_worried{background:url(../light/images/emoticons/smiley-roll-blue.png);width:16px;height:16px}img.o_emoticons_up{background:url(../light/images/emoticons/thumb-up.png);width:16px;height:16px}img.o_emoticons_down{background:url(../light/images/emoticons/thumb.png);width:16px;height:16px}.o_block_bottom,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry,.o_header_with_buttons,.o_search_result{margin-bottom:1em}.o_block_top,.o_block,.o_button_group,.o_block_with_datecomp .o_content,.o_course_run .o_toc .o_entry{margin-top:1em}.o_block_large_bottom,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-bottom:2em}.o_block_large_top,.o_block_large,.o_block_with_datecomp,.o_login .o_login_footer_wrapper,.o_portlet{margin-top:2em}.o_scrollblock{overflow-x:auto;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_button_group{text-align:center}.o_button_group a,.o_button_group input,.o_button_group button,.o_button_group .btn-group{margin-right:5px;margin-bottom:0.5em}.o_button_group a:last-child,.o_button_group input:last-child,.o_button_group button:last-child,.o_button_group .btn-group:last-child{margin-right:0}.o_button_group .btn-group a,.o_button_group .btn-group input,.o_button_group .btn-group button{margin-right:0;margin-bottom:0}.o_button_group .dropdown-menu{text-align:left}.o_button_group_left{text-align:left}.o_button_group_right{text-align:right}.o_button_group_top{margin-top:0}.o_header_with_buttons:before,.o_header_with_buttons:after{content:" ";display:table}.o_header_with_buttons:after{clear:both}.o_header_with_buttons h3,.o_header_with_buttons h4,.o_header_with_buttons .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_header_with_buttons h2{display:inline-block}.o_header_with_buttons .o_button_group{margin-top:10px;margin-bottom:0;float:right}.panel-heading.o_header_with_buttons{margin-bottom:0}.o_xsmall{font-size:12px}.o_small,.o_comments .o_comment_wrapper h5,.o_comments .o_comment_wrapper .o_comment,.o_bc_meta,.tooltip,.o_noti,.o_block_with_datecomp .o_meta,.o_togglebox_wrapper div.o_togglebox_content .o_hide,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_state,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_score,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_bookings .o_label,.o_course_run .o_toc .o_entry{font-size:12px}.o_large{font-size:18px}.o_xlarge{font-size:18px}.o_disabled{color:#777 !important;cursor:default}.o_disabled:hover{color:#777 !important}.o_dimmed{opacity:0.4;filter:alpha(opacity=40)}.o_selected{font-weight:bold}.o_deleted{text-decoration:line-through}.o_clickable{cursor:pointer}.o_ochre{color:#c8a959}.o_blue{color:#12223F}.o_undecorated:hover,.o_undecorated:focus,.o_disabled:hover,.o_withEllipsis .o_morelink:hover,.o_withEllipsis .o_lesslink:hover,#o_main_wrapper #o_toplink:hover,#o_footer_wrapper #o_footer_container #o_footer_powered a:hover,#o_share a:hover,.o_toolbar .o_tools_container a:hover,.o_button_toggle:hover,.o_im_message_group .o_im_from:hover,.o_noti .o_label:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:hover,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover,.o_catalog .o_level .o_meta .o_title a:hover,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover,.o_repo_details .o_social .o_comments:hover,.o_login .o_login_register:hover,.o_disabled:focus,.o_withEllipsis .o_morelink:focus,.o_withEllipsis .o_lesslink:focus,#o_main_wrapper #o_toplink:focus,#o_footer_wrapper #o_footer_container #o_footer_powered a:focus,#o_share a:focus,.o_toolbar .o_tools_container a:focus,.o_button_toggle:focus,.o_im_message_group .o_im_from:focus,.o_noti .o_label:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_social .o_comments:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_book:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_details:focus,.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:focus,.o_catalog .o_level .o_meta .o_title a:focus,.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:focus,.o_repo_details .o_social .o_comments:focus,.o_login .o_login_register:focus{text-decoration:none}.o_copy_code{overflow-x:auto;overflow-y:auto;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}.o_nowrap{white-space:nowrap}.o_titled_wrapper .o_content{margin-top:20px}.o_video{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_image{display:inline-block;width:100% \9;max-width:100%;height:auto}.o_with_hyphens{-ms-word-break:break-all;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.o_withEllipsis .o_ellipsis_links{float:right}.o_withEllipsis .o_morelink,.o_withEllipsis .o_lesslink{display:none}.o_withEllipsis.o_hasOverflow .o_morelink{display:block}.o_withEllipsis.o_hasOverflow .o_lesslink{display:none}.o_withEllipsis.o_hasOverflow.o_showOverflow{height:auto !important}.o_withEllipsis.o_hasOverflow.o_showOverflow .o_morelink{display:none}.o_withEllipsis.o_hasOverflow.o_showOverflow .o_lesslink{display:block}h1{color:#428bca}h2{color:#428bca}h3{color:#428bca}h4,.o_cal .fc-header-title h2{color:#428bca}h5{color:#428bca}h5{color:#428bca}fieldset legend{color:#333}html{position:relative;min-height:100%}body{min-height:100%;margin-bottom:70px}#o_main_wrapper{background:#fff;z-index:3}#o_main_wrapper #o_main_container{background:#fff}#o_main_wrapper #o_main_container #o_main_left{float:left;z-index:2;position:relative;background:#fff}#o_main_wrapper #o_main_container #o_main_left #o_main_left_content{padding:0 0 0 15px}#o_main_wrapper #o_main_container #o_main_left #o_main_left_toggle{position:absolute;display:none;right:0;top:70px;margin-right:-30px;font-size:25px;line-height:35px;text-align:center;width:30px;height:35px;z-index:3;border:1px solid #ddd;border-left:none;border-bottom-right-radius:4px;border-top-right-radius:4px;background-color:#fbfbfb;-webkit-box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);box-shadow:2px 0px 4px 1px rgba(0,0,0,0.15);color:#428bca}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas{background:#fbfbfb;-webkit-box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);box-shadow:0px 0px 6px 1px rgba(0,0,0,0.2);min-width:250px}#o_main_wrapper #o_main_container #o_main_left.o_offcanvas #o_main_left_content{padding:0 0 0 0}#o_main_wrapper #o_main_container #o_main_right{float:right;z-index:2;position:relative;background:inherit}#o_main_wrapper #o_main_container #o_main_right #o_main_right_content{padding:0 15px 0 0}#o_main_wrapper #o_main_container #o_main_center{position:relative;z-index:1;background:inherit}#o_main_wrapper #o_main_container #o_main_center h2:first-child{margin-top:0}@media screen and (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center{margin-left:0 !important}}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:0 15px}#o_main_wrapper #o_main_container #o_main_center #o_main_center_content #o_main_center_content_inner{padding-bottom:15px}#o_main_wrapper #o_toplink{position:absolute;bottom:0;right:15px;text-align:center;z-index:3}@media (max-width: 767px){#o_main_wrapper #o_main_container #o_main_center #o_main_center_content{padding:15px}} #o_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_wrapper #o_footer_container{position:relative;padding-top:10px;min-height:70px;background:#f5f5f5}#o_footer_wrapper #o_footer_container #o_footer_user #o_counter{white-space:nowrap}#o_footer_wrapper #o_footer_container #o_footer_user #o_username{white-space:nowrap;margin-right:1em}#o_footer_wrapper #o_footer_container #o_footer_version{position:absolute;right:15px;top:10px;text-align:right}@media (max-width: 767px){#o_footer_wrapper #o_footer_container #o_footer_version{padding-top:10px;text-align:left}}#o_footer_wrapper #o_footer_container #o_footer_powered{position:absolute;top:30px;right:15px}#o_footer_wrapper #o_footer_container #o_footer_powered img{opacity:0.6;filter:alpha(opacity=60)}#o_footer_wrapper #o_footer_container #o_footer_powered img:hover{opacity:1;filter:alpha(opacity=100)}@media (max-width: 767px){#o_footer_wrapper #o_footer_container #o_counter,#o_footer_wrapper #o_footer_container #o_footer_version{display:none}#o_footer_wrapper #o_footer_container #o_footer_powered{top:10px}#o_footer_wrapper #o_footer_container #o_footer_powered a:after{content:"\221E";font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:bold;font-size:14px}#o_footer_wrapper #o_footer_container #o_footer_powered img{display:none}} #o_share{margin-top:10px;font-size:14px}#o_share a{margin:0 3px 0 0;opacity:0.6;filter:alpha(opacity=60)}#o_share a:hover{opacity:1;filter:alpha(opacity=100)}#o_navbar_wrapper{z-index:4;border-top:1px solid #e7e7e7;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1)}#o_navbar_wrapper #o_navbar_container{position:relative}.o_navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid #e7e7e7;background-color:#f8f8f8}.o_navbar:before,.o_navbar:after{content:" ";display:table}.o_navbar:after{clear:both}.o_navbar .o_navbar_tabs li a{padding-right:30px}.o_navbar .o_navbar_tabs .o_navbar_tab_close{position:absolute;top:15px;right:0.5em;padding:0;width:1em;height:1em}.o_navbar .o_navbar_tabs .o_navbar_tab_close i:before{color:#d9534f}.o_navbar .o_navbar_tabs .o_navbar_tab_close:hover i:before{color:#c9302c}.o_navbar #o_navbar_tools_permanent #o_navbar_langchooser{color:#777;padding:7px 15px}.o_navbar #o_navbar_tools_permanent #o_navbar_langchooser form span+div{display:inline}.o_navbar #o_navbar_tools_permanent #o_navbar_help a i{margin-right:0.4em}.o_navbar #o_navbar_tools_permanent #o_navbar_print a,.o_navbar #o_navbar_tools_permanent #o_navbar_impress a{padding-right:0}@media (max-width: 767px){.o_navbar #o_navbar_tools_permanent #o_navbar_impress a span{display:none}}.o_navbar #o_navbar_tools_personal .o_navbar_tool a{padding-right:5px}.o_navbar #o_navbar_tools_personal #o_navbar_my_menu .dropdown-toggle{padding-left:45px}.o_navbar #o_navbar_tools_personal #o_navbar_my_menu .o_portrait{position:absolute;left:7px;top:10px}.o_navbar #o_navbar_tools_personal .o_logout{color:#d9534f}.o_navbar.o_navbar-offcanvas .o_navbar_tab_close{top:10px;right:10px}.o_navbar.o_navbar-offcanvas .o_navbar_tool{display:none}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a{color:#777}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a:hover,.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a:focus{color:#fff;background-color:transparent}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a.o_logout{color:#d9534f}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a.o_logout:hover,.o_navbar.o_navbar-offcanvas #o_navbar_my_menu a.o_logout:focus{color:#c9302c}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-header{padding-left:15px}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-toggle{display:none}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-menu{box-shadow:none;position:relative;top:0;left:0;display:block;float:none;background-color:#222;color:#777;font-size:14px}.o_navbar.o_navbar-offcanvas #o_navbar_my_menu .dropdown-menu .divider{background:none}.o_navbar.o_navbar-offcanvas .o_navbar-nav a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav a:focus{background-color:transparent;color:#fff}.o_navbar.o_navbar-offcanvas .o_navbar-nav .active a,.o_navbar.o_navbar-offcanvas .o_navbar-nav .active a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .active a:focus{background-color:#090909;color:#fff}.o_navbar.o_navbar-offcanvas .o_navbar-nav .o_navbar-link{color:#777}.o_navbar.o_navbar-offcanvas .o_navbar-nav .o_navbar-link:hover{color:#fff}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>li>a{color:#777}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>li>a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.active>a,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.active>a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.disabled>a,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.disabled>a:hover,.o_navbar.o_navbar-offcanvas .o_navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}.o_navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;-webkit-overflow-scrolling:touch}.o_navbar-collapse:before,.o_navbar-collapse:after{content:" ";display:table}.o_navbar-collapse:after{clear:both}.o_navbar-collapse.o_collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.o_navbar-offcanvas .o_navbar-collapse{width:auto;box-shadow:none;margin-top:10px;margin-right:-15px;margin-left:-15px}.o_navbar-brand{float:left;font-size:18px;line-height:20px;height:50px;color:#777}.o_navbar-brand:hover,.o_navbar-brand:focus{text-decoration:none;color:#5e5e5e;background-color:transparent}.o_navbar-toggle{position:relative;margin-right:15px;margin-left:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;border:1px solid #ddd;border-radius:4px;background-color:transparent;background-image:none}.o_navbar-toggle:hover,.o_navbar-toggle:focus{outline:none;background-color:#ddd}.o_navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px;background-color:#888}.o_navbar-toggle .icon-bar+.icon-bar{margin-top:4px}#o_navbar_left-toggle{float:left}#o_navbar_right-toggle{float:right}.o_navbar-link{color:#777}.o_navbar-link:hover{color:#333}.o_navbar-nav{margin:7.5px -15px}.o_navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px;color:#777}.o_navbar-nav>li>a:hover,.o_navbar-nav>li>a:focus{color:#333;background-color:transparent}.o_navbar-nav>.active>a,.o_navbar-nav>.active>a:hover,.o_navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.o_navbar-nav>.disabled>a,.o_navbar-nav>.disabled>a:hover,.o_navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.o_navbar-nav>.open>a,.o_navbar-nav>.open>a:hover,.o_navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}.o_collapse .o_navbar-nav{float:left;margin:0}.o_collapse .o_navbar-nav>li{float:left}.o_collapse .o_navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.o_collapse .o_navbar-nav.o_navbar-right:last-child{margin-right:-15px}.o_collapse.o_navbar-collapse .o_navbar-left{float:left !important}.o_collapse.o_navbar-collapse .o_navbar-right{float:right !important}.o_navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid #e7e7e7;border-bottom:1px solid #e7e7e7;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@media (max-width: 767px){.o_navbar-form .form-group{margin-bottom:5px}} .o_collapse .o_navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.o_collapse .o_navbar-form.o_navbar-right:last-child{margin-right:-15px}.o_navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.o_navbar-fixed-bottom .o_navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.o_navbar-btn{margin-top:8px;margin-bottom:8px}.o_navbar-btn.btn-sm,.btn-group-sm>.o_navbar-btn.btn{margin-top:10px;margin-bottom:10px}.o_navbar-btn.btn-xs,.btn-group-xs>.o_navbar-btn.btn{margin-top:14px;margin-bottom:14px}.o_navbar-text{margin-top:15px;margin-bottom:15px;color:#777}.o_collapse .o_navbar-text{float:left;margin-left:15px;margin-right:15px}.o_collapse .o_navbar-text.o_navbar-right:last-child{margin-right:0}.o_toolbar{position:relative;margin-bottom:20px;margin-top:-10px;border:1px solid #e7e7e7}.o_toolbar:before,.o_toolbar:after{content:" ";display:table}.o_toolbar:after{clear:both}@media (min-width: 768px){.o_toolbar{border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px}}.o_toolbar .o_breadcrumb:before,.o_toolbar .o_breadcrumb:after{content:" ";display:table}.o_toolbar .o_breadcrumb:after{clear:both}.o_toolbar .o_breadcrumb .breadcrumb{margin-bottom:0;padding:5px 9px;font-size:11px;line-height:15px;border-radius:0;background:#f5f5f5;border-top-right-radius:4px;border-top-left-radius:4px}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close{float:right;position:relative;margin:0 0 0 15px;vertical-align:middle}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a{line-height:15px;color:#d9534f}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a:hover{color:#b52b27}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a i{font-size:16px}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close a span{display:none}.o_toolbar .o_breadcrumb .breadcrumb .o_breadcrumb_close:before{content:none}.o_toolbar .o_tools_container{text-align:center;min-height:37px;position:realtive;background-color:#f8f8f8;border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-top:1px solid #e7e7e7}.o_toolbar .o_tools_container:before,.o_toolbar .o_tools_container:after{content:" ";display:table}.o_toolbar .o_tools_container:after{clear:both}@media (max-width: 991px){.o_toolbar .o_tools_container{min-height:35px}}@media (max-width: 767px){.o_toolbar .o_tools_container{min-height:22px;text-align:left}}.o_toolbar .o_tools_container a{color:#777;display:inline-block}.o_toolbar .o_tools_container a:hover{color:#333}.o_toolbar .o_tools_container a.o_disabled{color:#aaa !important}.o_toolbar .o_tools_container a.o_disabled:hover{color:#aaa !important}.o_toolbar .o_tools_container a.active{color:#428bca;background-color:transparent}.o_toolbar .o_tools_container .dropdown-menu a{display:block}.o_toolbar .o_tools_container .dropdown-menu a.active{color:#428bca;background-color:transparent}.o_toolbar .o_tools{margin-top:8px;margin-bottom:5px}.o_toolbar .o_tool,.o_toolbar .o_text{position:relative;margin:0 10px}.o_toolbar .o_tool a i,.o_toolbar .o_tool .o_disabled i,.o_toolbar .o_text a i,.o_toolbar .o_text .o_disabled i{font-size:18px}.o_toolbar .o_tool a span,.o_toolbar .o_tool .o_disabled span,.o_toolbar .o_text a span,.o_toolbar .o_text .o_disabled span{display:block;font-size:12px}.o_toolbar .o_tool a span.badge,.o_toolbar .o_tool .o_disabled span.badge,.o_toolbar .o_text a span.badge,.o_toolbar .o_text .o_disabled span.badge{position:absolute;right:50%;top:-18px;margin-right:-12px;font-size:13px}@media (min-width: 767px) and (max-width: 991px){.o_toolbar .o_tool a i,.o_toolbar .o_tool .o_disabled i,.o_toolbar .o_text a i,.o_toolbar .o_text .o_disabled i{font-size:16px}.o_toolbar .o_tool a span,.o_toolbar .o_tool .o_disabled span,.o_toolbar .o_text a span,.o_toolbar .o_text .o_disabled span{font-size:11px}.o_toolbar .o_tool a span.badge,.o_toolbar .o_tool .o_disabled span.badge,.o_toolbar .o_text a span.badge,.o_toolbar .o_text .o_disabled span.badge{top:-16.5px;margin-right:-11px;font-size:12px}}@media (max-width: 767px){.o_toolbar .o_tool a i,.o_toolbar .o_tool .o_disabled i,.o_toolbar .o_text a i,.o_toolbar .o_text .o_disabled i{font-size:20px}.o_toolbar .o_tool a span,.o_toolbar .o_tool .o_disabled span,.o_toolbar .o_text a span,.o_toolbar .o_text .o_disabled span{display:none}.o_toolbar .o_tool a span.badge,.o_toolbar .o_tool .o_disabled span.badge,.o_toolbar .o_text a span.badge,.o_toolbar .o_text .o_disabled span.badge{display:block;position:relative;top:0;left:0}}.o_toolbar .o_tool .o_chelp,.o_toolbar .o_text .o_chelp{position:relative;top:-1em;vertical-align:top}.o_toolbar .o_tool_next,.o_toolbar .o_tool_previous{padding:0;margin-top:5px;border:1px solid #ccc;background-color:#eee}.o_toolbar .o_tool_next a,.o_toolbar .o_tool_previous a{color:#777}.o_toolbar .o_tool_next a:hover,.o_toolbar .o_tool_previous a:hover{color:#333}.o_toolbar .o_tool_next a.o_disabled,.o_toolbar .o_tool_previous a.o_disabled{color:#aaa !important}.o_toolbar .o_tool_next a.o_disabled:hover,.o_toolbar .o_tool_previous a.o_disabled:hover{color:#aaa !important}.o_toolbar .o_tool_next i,.o_toolbar .o_tool_previous i{font-size:21px}@media (min-width: 767px) and (max-width: 991px){.o_toolbar .o_tool_next,.o_toolbar .o_tool_previous{margin-top:4px}.o_toolbar .o_tool_next i,.o_toolbar .o_tool_previous i{font-size:18px}}@media (max-width: 767px){.o_toolbar .o_tool_next,.o_toolbar .o_tool_previous{margin-top:0}.o_toolbar .o_tool_next i,.o_toolbar .o_tool_previous i{font-size:20px}}.o_toolbar .o_tool_previous{margin-left:10px;border-bottom-left-radius:4px;border-top-left-radius:4px;border-right:0}.o_toolbar .o_tool_next{border-bottom-right-radius:4px;border-top-right-radius:4px}.o_toolbar .o_tool_dropdown{position:relative}.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:18px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span{display:block;font-size:12px}.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{position:absolute;right:50%;top:4px;margin-right:-20px;font-size:14px}@media (min-width: 767px) and (max-width: 991px){.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:16px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span,.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{font-size:11px}.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{top:4px;margin-right:-18px;font-size:12px}}@media (max-width: 767px){.o_toolbar .o_tool_dropdown a.dropdown-toggle i{font-size:20px}.o_toolbar .o_tool_dropdown a.dropdown-toggle span,.o_toolbar .o_tool_dropdown a.dropdown-toggle .o_icon_caret{display:none}}.o_toolbar .o_tool_dropdown .dropdown-menu{text-align:left}.o_toolbar .o_tools_left{float:left}.o_toolbar .o_tools_right{float:right}.o_toolbar .o_tools_right_edge{float:right}@media (max-width: 991px){.o_toolbar .o_tools{margin-top:6px;margin-bottom:4px}.o_toolbar .o_tool span{max-width:10em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media (max-width: 767px){.o_toolbar .o_tools{margin-top:6px;margin-bottom:4px}.o_toolbar .o_tools .o_chelp{top:0;vertical-align:top}.o_toolbar .o_tools_right,.o_toolbar .o_tools_right,.o_toolbar .o_tools_center{float:left}} body{overflow-x:hidden}.o_container_offcanvas{position:relative;max-width:1324px;-webkit-transition:all .25s ease-in-out;-moz-transition:all .25s ease-in-out;-o-transition:all .25s ease-in-out;-m-transition:all .25s ease-in-out;transition:all .25s ease-in-out}@media screen and (min-width: 1324px) and (max-width: 1574px){body.o_offcanvas_right_visible .o_container_offcanvas{left:-125px;max-width:1074px}}@media screen and (min-width: 1574px) and (max-width: 1824px){body.o_offcanvas_right_visible .o_container_offcanvas{left:-125px}}#o_offcanvas_right{position:absolute;top:0;right:-250px;width:250px;padding:15px 15px;background-color:#222;color:#777;border:1px solid #090909;-webkit-box-shadow:0px 0px 4px 3px rgba(0,0,0,0.25);box-shadow:0px 0px 4px 3px rgba(0,0,0,0.25);min-height:100%;z-index:10;display:none}#o_offcanvas_right:before,#o_offcanvas_right:after{content:" ";display:table}#o_offcanvas_right:after{clear:both}@media screen and (max-width: 767px){.row-offcanvas{position:relative;-webkit-transition:all .25s ease-out;-moz-transition:all .25s ease-out;transition:all .25s ease-out}.row-offcanvas-right{right:0}.row-offcanvas-right .sidebar-offcanvas{right:-50%}.row-offcanvas-right.active{right:50%}.row-offcanvas-left{left:0}.row-offcanvas-left .sidebar-offcanvas{left:-50%}.row-offcanvas-left.active{left:50%}.sidebar-offcanvas{position:absolute;top:0;width:50%}}.o_info,.o_form .o_info,.o_togglebox_wrapper div.o_togglebox_content,div.o_qti_item_itemfeedback{margin:20px 0;padding:20px;border-left:3px solid #777;background-color:#eee}.o_info h2,.o_info h3,.o_info h4,.o_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_info h2,.o_info h5,.o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback h2,.o_form .o_info h3,.o_togglebox_wrapper div.o_togglebox_content h3,div.o_qti_item_itemfeedback h3,.o_form .o_info h4,.o_togglebox_wrapper div.o_togglebox_content h4,div.o_qti_item_itemfeedback h4,.o_form .o_info .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_info h2,.o_togglebox_wrapper div.o_togglebox_content .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_togglebox_wrapper div.o_togglebox_content h2,div.o_qti_item_itemfeedback .o_cal .fc-header-title h2,.o_cal .fc-header-title div.o_qti_item_itemfeedback h2,.o_form .o_info h5,.o_togglebox_wrapper div.o_togglebox_content h5,div.o_qti_item_itemfeedback h5{color:#777}.o_note,.o_form .o_desc,.o_course_run .o_statusinfo,.o_course_stats .o_desc{margin:20px 0;padding:20px;border-left:3px solid #31708f;background-color:#d9edf7}.o_note h2,.o_note h3,.o_note h4,.o_note .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_note h2,.o_note h5,.o_form .o_desc h2,.o_course_run .o_statusinfo h2,.o_course_stats .o_desc h2,.o_form .o_desc h3,.o_course_run .o_statusinfo h3,.o_course_stats .o_desc h3,.o_form .o_desc h4,.o_course_run .o_statusinfo h4,.o_course_stats .o_desc h4,.o_form .o_desc .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_desc h2,.o_course_run .o_statusinfo .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_run .o_statusinfo h2,.o_course_stats .o_desc .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_stats .o_desc h2,.o_form .o_desc h5,.o_course_run .o_statusinfo h5,.o_course_stats .o_desc h5{color:#31708f}.o_important,.o_bc_empty,.o_course_run .o_no_scoreinfo{margin:20px 0;padding:20px;border-left:3px solid #f4d000;background-color:#fff1a4}.o_important h2,.o_important h3,.o_important h4,.o_important .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_important h2,.o_important h5,.o_bc_empty h2,.o_course_run .o_no_scoreinfo h2,.o_bc_empty h3,.o_course_run .o_no_scoreinfo h3,.o_bc_empty h4,.o_course_run .o_no_scoreinfo h4,.o_bc_empty .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_bc_empty h2,.o_course_run .o_no_scoreinfo .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_course_run .o_no_scoreinfo h2,.o_bc_empty h5,.o_course_run .o_no_scoreinfo h5{color:#f4d000}.o_success{margin:20px 0;padding:20px;border-left:3px solid #3c763d;background-color:#dff0d8}.o_success h2,.o_success h3,.o_success h4,.o_success .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_success h2,.o_success h5{color:#3c763d}.o_warning,.o_form .o_warning{margin:20px 0;padding:20px;border-left:3px solid #8a6d3b;background-color:#fcf8e3}.o_warning h2,.o_warning h3,.o_warning h4,.o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_warning h2,.o_warning h5,.o_form .o_warning h2,.o_form .o_warning h3,.o_form .o_warning h4,.o_form .o_warning .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_form .o_warning h2,.o_form .o_warning h5{color:#8a6d3b}.o_error{margin:20px 0;padding:20px;border-left:3px solid #a94442;background-color:#f2dede}.o_error h2,.o_error h3,.o_error h4,.o_error .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_error h2,.o_error h5{color:#a94442}div.o_callout_overlay{position:fixed;top:0;left:0;width:100%;height:100%;zoom:1;background:#000;opacity:0;filter:alpha(opacity=0)}.o_alert_info{position:fixed;top:-100%;left:0;display:none;z-index:2000;width:100%;text-align:center}.o_alert_info .alert{position:relative;width:auto;margin:0 auto;text-align:left;-webkit-box-shadow:0px 1px 5px -1px rgba(0,0,0,0.15);box-shadow:0px 1px 5px -1px rgba(0,0,0,0.15)}.o_alert_info .alert .o_icon_close{float:right;color:#777}.o_alert_info .alert .o_icon_close:hover{color:#555}@media (min-width: 768px){.o_alert_info .alert{width:600px}} #o_msg_sticky,#o_msg_sticky_preview{position:relative;color:#a94442;background-color:#f2dede;border:1px solid #ebccd1;padding:10px 16px 10px 60px;min-height:40px;margin:-20px 0 20px 0}#o_msg_sticky .o_icon_info_msg,#o_msg_sticky_preview .o_icon_info_msg{position:absolute;left:10px;top:5px;font-size:40px}@media (min-width: 768px){.modal .o_modal_fullwidth{width:90%}}@media (min-width: 992px){.modal .o_modal_fullwidth{width:80%}}.modal .modal-header h4,.modal .modal-header .o_cal .fc-header-title h2,.o_cal .fc-header-title .modal .modal-header h2{color:#428bca;font-weight:500;font-family:inherit;line-height:1.1}.o_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_tree a{color:#777;background-color:none}.o_tree a:hover,.o_tree a:focus{color:#333;background-color:#f8f8f8}.o_tree ul{margin:0;padding:0;list-style-type:none}.o_tree ul li{margin:0;padding:0;white-space:nowrap}.o_tree ul li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_tree ul li div a.o_tree_oc_l0{position:absolute;top:10px;left:-4px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l0,.o_tree ul .o_tree_level_close.b_tree_oc_l0{z-index:10}.o_tree ul li div a.o_tree_oc_l1{position:absolute;top:10px;left:11px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l1,.o_tree ul .o_tree_level_close.b_tree_oc_l1{z-index:10}.o_tree ul li div a.o_tree_oc_l2{position:absolute;top:10px;left:26px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l2,.o_tree ul .o_tree_level_close.b_tree_oc_l2{z-index:10}.o_tree ul li div a.o_tree_oc_l3{position:absolute;top:10px;left:41px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l3,.o_tree ul .o_tree_level_close.b_tree_oc_l3{z-index:10}.o_tree ul li div a.o_tree_oc_l4{position:absolute;top:10px;left:56px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l4,.o_tree ul .o_tree_level_close.b_tree_oc_l4{z-index:10}.o_tree ul li div a.o_tree_oc_l5{position:absolute;top:10px;left:71px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l5,.o_tree ul .o_tree_level_close.b_tree_oc_l5{z-index:10}.o_tree ul li div a.o_tree_oc_l6{position:absolute;top:10px;left:86px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l6,.o_tree ul .o_tree_level_close.b_tree_oc_l6{z-index:10}.o_tree ul li div a.o_tree_oc_l7{position:absolute;top:10px;left:101px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l7,.o_tree ul .o_tree_level_close.b_tree_oc_l7{z-index:10}.o_tree ul li div a.o_tree_oc_l8{position:absolute;top:10px;left:116px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l8,.o_tree ul .o_tree_level_close.b_tree_oc_l8{z-index:10}.o_tree ul li div a.o_tree_oc_l9{position:absolute;top:10px;left:131px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l9,.o_tree ul .o_tree_level_close.b_tree_oc_l9{z-index:10}.o_tree ul li div a.o_tree_oc_l10{position:absolute;top:10px;left:146px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l10,.o_tree ul .o_tree_level_close.b_tree_oc_l10{z-index:10}.o_tree ul li div a.o_tree_oc_l11{position:absolute;top:10px;left:161px;z-index:9}.o_tree ul .o_tree_level_open.b_tree_oc_l11,.o_tree ul .o_tree_level_close.b_tree_oc_l11{z-index:10}.o_tree ul li div a.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_tree ul li div a.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_tree ul li div a.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_tree ul li div a.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_tree ul li div a.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_tree ul li div a.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_tree ul li div a.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_tree ul li div a.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_tree ul li div a.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_tree ul li div a.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_tree ul li div a.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_tree ul li div a.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_tree ul span.o_tree_leaf{display:none}.o_tree ul li .badge{float:right;font-size:70%}.o_tree ul li .badge:before{content:none}.o_tree ul li div.o_dnd_sibling{margin:0;padding:0;border-bottom:none}.o_tree ul li a.active{color:#428bca;background-color:none;font-weight:bold}.o_tree ul li a.active:hover,.o_tree ul li a.active:focus{color:#2a6496;background-color:#eee}.o_tree ul li a.active_parent{color:#777;font-weight:bold}.o_tree ul li a.active_parent:hover,.o_tree ul li a.active_parent:focus{color:#333}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l0{left:6px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l1{left:21px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l2{left:36px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l3{left:51px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l4{left:66px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l5{left:81px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l6{left:96px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l7{left:111px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l8{left:126px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l9{left:141px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l10{left:156px}.o_tree.o_tree_root_hidden ul li div a.o_tree_oc_l11{left:171px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l0{padding:10px 2px 10px 20px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l1{padding:10px 2px 10px 35px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l2{padding:10px 2px 10px 50px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l3{padding:10px 2px 10px 65px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l4{padding:10px 2px 10px 80px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l5{padding:10px 2px 10px 95px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l6{padding:10px 2px 10px 110px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l7{padding:10px 2px 10px 125px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l8{padding:10px 2px 10px 140px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l9{padding:10px 2px 10px 155px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l10{padding:10px 2px 10px 170px}.o_tree.o_tree_root_hidden ul li div a.o_tree_l11{padding:10px 2px 10px 185px}.o_tree .o_dnd_item{cursor:move;z-index:100}.o_tree .o_dnd_proxy{opacity:0.4;filter:alpha(opacity=40);background-color:yellow}.o_tree .o_dnd_item.o_dnd_over{background-color:#ffff60}.o_tree .o_dnd_sibling{height:3px;width:100%}.o_tree .o_dnd_sibling.o_dnd_over{background:transparent url(../light/images/arrow_dd.png) top left no-repeat}.o_tree .o_dnd_l1{margin-left:0 !important}.o_tree .o_dnd_l2{margin-left:1em !important}.o_tree .o_dnd_l3{margin-left:2em !important}.o_tree .o_dnd_l4{margin-left:3em !important}.o_tree .o_dnd_l5{margin-left:4em !important}.o_tree .o_dnd_l6{margin-left:5em !important}.o_tree .o_dnd_l7{margin-left:6em !important}.o_tree .o_dnd_l8{margin-left:7em !important}.o_tree .o_dnd_l9{margin-left:8em !important}.o_tree .o_dnd_l10{margin-left:9em !important}.o_tree .o_dnd_l11{margin-left:10em !important}.o_offcanvas .o_tree{border:0}.o_selection_tree{position:relative;display:block;background-color:none;border:1px solid #ddd;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden;font-size:14px}.o_selection_tree ul{margin:0;padding:0;list-style-type:none}.o_selection_tree li{margin:0;padding:0;white-space:nowrap}.o_selection_tree li div{position:relative;margin-bottom:-1px;border-bottom:1px solid #ddd}.o_selection_tree li>div>span.o_tree_l0,.o_selection_tree li>div>div.checkbox.o_tree_l0,.o_selection_tree li>div>div.radio.o_tree_l0{display:block;padding:10px 2px 10px 10px;z-index:9}.o_selection_tree li>div>span.o_tree_l1,.o_selection_tree li>div>div.checkbox.o_tree_l1,.o_selection_tree li>div>div.radio.o_tree_l1{display:block;padding:10px 2px 10px 25px;z-index:9}.o_selection_tree li>div>span.o_tree_l2,.o_selection_tree li>div>div.checkbox.o_tree_l2,.o_selection_tree li>div>div.radio.o_tree_l2{display:block;padding:10px 2px 10px 40px;z-index:9}.o_selection_tree li>div>span.o_tree_l3,.o_selection_tree li>div>div.checkbox.o_tree_l3,.o_selection_tree li>div>div.radio.o_tree_l3{display:block;padding:10px 2px 10px 55px;z-index:9}.o_selection_tree li>div>span.o_tree_l4,.o_selection_tree li>div>div.checkbox.o_tree_l4,.o_selection_tree li>div>div.radio.o_tree_l4{display:block;padding:10px 2px 10px 70px;z-index:9}.o_selection_tree li>div>span.o_tree_l5,.o_selection_tree li>div>div.checkbox.o_tree_l5,.o_selection_tree li>div>div.radio.o_tree_l5{display:block;padding:10px 2px 10px 85px;z-index:9}.o_selection_tree li>div>span.o_tree_l6,.o_selection_tree li>div>div.checkbox.o_tree_l6,.o_selection_tree li>div>div.radio.o_tree_l6{display:block;padding:10px 2px 10px 100px;z-index:9}.o_selection_tree li>div>span.o_tree_l7,.o_selection_tree li>div>div.checkbox.o_tree_l7,.o_selection_tree li>div>div.radio.o_tree_l7{display:block;padding:10px 2px 10px 115px;z-index:9}.o_selection_tree li>div>span.o_tree_l8,.o_selection_tree li>div>div.checkbox.o_tree_l8,.o_selection_tree li>div>div.radio.o_tree_l8{display:block;padding:10px 2px 10px 130px;z-index:9}.o_selection_tree li>div>span.o_tree_l9,.o_selection_tree li>div>div.checkbox.o_tree_l9,.o_selection_tree li>div>div.radio.o_tree_l9{display:block;padding:10px 2px 10px 145px;z-index:9}.o_selection_tree li>div>span.o_tree_l10,.o_selection_tree li>div>div.checkbox.o_tree_l10,.o_selection_tree li>div>div.radio.o_tree_l10{display:block;padding:10px 2px 10px 160px;z-index:9}.o_selection_tree li>div>span.o_tree_l11,.o_selection_tree li>div>div.checkbox.o_tree_l11,.o_selection_tree li>div>div.radio.o_tree_l11{display:block;padding:10px 2px 10px 175px;z-index:9}.o_breadcrumb{position:relative}.o_breadcrumb .o_breadcrumb_close{float:right;position:relative;margin:0 0 0 15px;vertical-align:middle}.o_breadcrumb .o_breadcrumb_close a{line-height:15px;color:#d9534f}.o_breadcrumb .o_breadcrumb_close a:hover{color:#b52b27}.o_breadcrumb .o_breadcrumb_close a i{font-size:16px}.o_breadcrumb .o_breadcrumb_close a span{display:none}.o_breadcrumb .o_breadcrumb_close:before{content:none}.o_form .o_icon_mandatory{position:relative;right:0;line-height:inherit;margin-left:0.25em}.o_form .o_error{margin-top:1px;margin-bottom:0;padding:10px}.o_form hr.o_spacer_noline{border-top:1px solid transparent}.o_form .o_date{position:relative;padding-right:34px}.o_form .o_date.form-inline .form-group,.o_form .o_date.navbar-form .form-group,.o_form .o_date.o_navbar-form .form-group{margin-left:0}.o_form input.o_date_ms{width:3em}.o_form .o_date.form-inline .o_date_ms.form-group,.o_form .o_date.navbar-form .o_date_ms.form-group,.o_form .o_date.o_navbar-form .o_date_ms.form-group{margin-left:25px}.o_form .input-group.o_date_picker{width:16em}.o_form .o_filepreview{margin-bottom:10px}.o_form .o_fileinput{cursor:pointer;position:relative}.o_form .o_fileinput .o_fakechooser{position:relative;z-index:1}.o_form .o_fileinput .o_realchooser{position:absolute;top:0;left:0;z-index:2;opacity:0;filter:alpha(opacity=0)}.o_choice_checkrow,.o_choice_textrow{vertical-align:text-top;padding-bottom:2px}.o_choice_textrow{padding-left:1em}.o_togglecheck a{white-space:nowrap}.o_catalog .o_catalog_delete_img{position:relative;top:-0.5em}.o_button_dirty{color:#fff;background-color:#f0ad4e;border-color:#eea236}.o_button_dirty:hover,.o_button_dirty:focus,.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.o_button_dirty:active,.o_button_dirty.active,.open>.o_button_dirty.dropdown-toggle{background-image:none}.o_button_dirty.disabled,.o_button_dirty.disabled:hover,.o_button_dirty.disabled:focus,.o_button_dirty.disabled:active,.o_button_dirty.disabled.active,.o_button_dirty[disabled],.o_button_dirty[disabled]:hover,.o_button_dirty[disabled]:focus,.o_button_dirty[disabled]:active,.o_button_dirty[disabled].active,fieldset[disabled] .o_button_dirty,fieldset[disabled] .o_button_dirty:hover,fieldset[disabled] .o_button_dirty:focus,fieldset[disabled] .o_button_dirty:active,fieldset[disabled] .o_button_dirty.active{background-color:#f0ad4e;border-color:#eea236}.o_button_dirty .badge{color:#f0ad4e;background-color:#fff}.o_button_toggle{border:1px solid #777;border-top-right-radius:9px;border-top-left-radius:9px;border-bottom-right-radius:9px;border-bottom-left-radius:9px;background:#eee;display:inline-block;height:18px;line-height:16px;font-size:16px;text-align:left;padding:0 0.5em 0 0;margin:0}.o_button_toggle i{color:#777;text-shadow:1px 0 2px rgba(0,0,0,0.25)}.o_button_toggle span{line-height:16px;vertical-align:top;font-size:60%;color:#777;text-transform:uppercase}.o_button_toggle.o_on{text-align:right;padding:0 0 0 0.5em}.o_button_toggle.o_on i{color:#428bca;text-shadow:-1px 0 2px rgba(0,0,0,0.25)}.o_table_wrapper{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.o_table_wrapper.o_table_flexi .o_table_body{margin-top:20px}.o_table_wrapper.o_table_flexi .table{margin-top:20px}.o_table_wrapper.o_table_flexi .table td ul{margin:0}.o_table_wrapper .o_table_search{max-width:50em}.o_table_wrapper .o_table_footer .o_table_pagination{text-align:center}.o_table_wrapper .o_row_selected td{background-color:#dff0d8 !important}.o_table_wrapper .o_table{margin-bottom:0}.o_table_wrapper .o_marked{font-weight:bold}.o_table_wrapper .table{margin-bottom:0}.o_table_wrapper th{color:#333}a.o_orderby,a.o_orderby:hover{color:#333;text-decoration:none}a.o_orderby.o_orderby_asc,a.o_orderby.o_orderby_desc,a.o_orderby:hover.o_orderby_asc,a.o_orderby:hover.o_orderby_desc{border-bottom:1px solid #ddd}.o_table_row_count{padding-top:6px;padding-bottom:6px;vertical-align:middle}.o_table_row_details td{background-color:white !important}.o_table_config{font-size:12px}.o_table_buttons{text-align:center}.o_table_buttons input{margin-right:1em}.o_table_buttons input:last-child{margin-right:0}.o_table_tools{margin-left:6px}.o_table_tools_indications{margin-left:10px;padding-top:3px;font-size:66%}.o_table_count{max-width:20em;float:left;padding:0 15px}.o_info .table-bordered td,o_note .table-bordered td,o_important .table-bordered td,o_warning .table-bordered td,o_error .table-bordered td{border-color:#333}.panel .o_table_layout{border-top:1px solid #ddd;padding-top:6px}.panel .o_table_count{padding:0 15px}#o_navbar_imclient #o_im_message,#o_navbar_imclient #o_im_status,#o_navbar_imclient #o_im_summary{float:left;position:relative;padding:15px 3px}#o_navbar_imclient #o_im_summary{padding-right:15px}#o_navbar_imclient #o_im_status div.o_chelp_wrapper{right:0.5em}.o_im_load_history{margin-bottom:6px}.o_im_load_history .o_label{font-size:12px;padding-right:0.5em;line-height:1.5em;color:#777}.o_im_chat_history{height:170px;font-size:90%;border:1px solid #eee;margin:0 0 1em 0;overflow:scroll;overflow-x:auto}.o_im_message_group{padding:3px 3px 3px 40px;min-height:40px;position:relative;border-top:1px solid #eee;background:#fff}.o_im_message_group.o_odd{background:#f4f4f4}.o_im_message_group .o_portrait{position:absolute;top:3px;left:3px}.o_im_message_group .o_im_from{color:#777;font-size:12px;font-weight:bold}.o_im_message_group .o_im_from:hover{color:#5e5e5e}.o_im_message_group div.o_im_body{padding:3px 0 3px 0;font-size:12px}.o_im_message_group div.o_im_body .o_date{float:right;color:#777;font-size:9px}.o_groupchat_roster{font-size:12px}.o_groupchat_roster li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333}.o_groupchat_roster li.o_vip{color:#3c763d}.o_groupchat_roster li.o_anonymous{color:#31708f}.o_im_buddieslist .o_im_buddieslist_toggler .btn{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_im_buddieslist ul{font-size:12px}.o_im_buddieslist ul ul{padding-left:1em}.o_im_buddieslist ul a{color:#428bca}.o_im_buddieslist ul a:hover{color:#2a6496}.o_flag{position:relative;top:1px;display:inline-block;line-height:1;width:16px;height:16px;background-repeat:no-repeat;background-position:0 100%}option.o_with_flag{padding-left:23px;min-height:16px;background-repeat:no-repeat;background-position:2px 50%}.o_flag_en{background-image:url("../light/images/flags/gb.png")}.o_flag_de{background-image:url("../light/images/flags/de.png")}.o_flag_fr{background-image:url("../light/images/flags/fr.png")}.o_flag_it{background-image:url("../light/images/flags/it.png")}.o_flag_es{background-image:url("../light/images/flags/es.png")}.o_flag_da{background-image:url("../light/images/flags/dk.png")}.o_flag_cs{background-image:url("../light/images/flags/cz.png")}.o_flag_el{background-image:url("../light/images/flags/gr.png")}.o_flag_ee{background-image:url("../light/images/flags/ee.png")}.o_flag_ru{background-image:url("../light/images/flags/ru.png")}.o_flag_pl{background-image:url("../light/images/flags/pl.png")}.o_flag_zh_CN{background-image:url("../light/images/flags/cn.png")}.o_flag_zh_TW{background-image:url("../light/images/flags/tw.png")}.o_flag_lt{background-image:url("../light/images/flags/lt.png")}.o_flag_fa{background-image:url("../light/images/flags/ir.png")}.o_flag_pt_PT{background-image:url("../light/images/flags/pt.png")}.o_flag_pt_BR{background-image:url("../light/images/flags/br.png")}.o_flag_tr{background-image:url("../light/images/flags/tr.png")}.o_flag_hu{background-image:url("../light/images/flags/hu.png")}.o_flag_sq{background-image:url("../light/images/flags/al.png")}.o_flag_in{background-image:url("../light/images/flags/id.png")}.o_flag_ar{background-image:url("../light/images/flags/eg.png")}.o_flag_rm{background-image:url("../light/images/flags/rm.png")}.o_flag_af{background-image:url("../light/images/flags/za.png")}.o_flag_vi{background-image:url("../light/images/flags/vn.png")}.o_flag_mn{background-image:url("../light/images/flags/mn.png")}.o_flag_iw{background-image:url("../light/images/flags/il.png")}.o_flag_ko{background-image:url("../light/images/flags/kr.png")}.o_flag_nl_NL{background-image:url("../light/images/flags/nl.png")}.o_flag_jp{background-image:url("../light/images/flags/jp.png")}.o_flag_nb_NO{background-image:url("../light/images/flags/no.png")}.o_flag_et_EE{background-image:url("../light/images/flags/ee.png")}.o_flag_bg{background-image:url("../light/images/flags/bg.png")}.o_flag_hi_IN_ASIA{background-image:url("../light/images/flags/in.png")}.o_flag_ar_LB{background-image:url("../light/images/flags/lb.png")}.o_flag_gl_ES{background-image:url("../light/images/flags/galicia.png")}.o_rating .o_rating_title{font-size:12px}.o_rating .o_rating_items{white-space:nowrap}.o_rating .o_rating_items .o_icon{color:#f0ad4e}.o_rating .o_rating_items .o_icon:hover{color:#428bca}.o_rating .o_rating_items .o_legend{margin-left:1em;font-size:12px;line-height:1em}.o_rating .o_rating_explanation{font-size:12px;color:#777}@media (max-width: 991px){.o_rating .o_rating_title,.o_rating .o_rating_explanation{display:none}} .o_comments .o_comment_wrapper .o_avatar{float:left;margin:0 1em 0 0}.o_comments .o_comment_wrapper .o_reply,.o_comments .o_comment_wrapper .o_delete{float:right}.o_ratings_and_comments .o_rating_wrapper{vertical-align:middle;display:inline-block}.o_ratings_and_comments a.o_comments{margin-left:10px;position:relative;top:0.1em}.d3chart .bar{shape-rendering:crispEdges}.d3chart .bar_default_light{fill:#7eb0db}.d3chart .bar_default{fill:#428bca}.d3chart .bar_default_dark{fill:#2a6496}.d3chart .axis{font:12px sans-serif}.d3chart .axis path,.d3chart .axis line{fill:none;stroke:#000;shape-rendering:crispEdges}.o_forum_peekview .o_quote_wrapper,.o_forum_peekview .b_quote_wrapper{display:none}.o_forum_thread_sticky{font-weight:bold}.o_forum_switch{font-size:12px}.o_forum_toolbar{margin-bottom:6px;float:left}.o_forum_fulltextsearch{float:right}@media (max-width: 767px){.o_forum_fulltextsearch{float:left}}.o_forum .o_mark,.o_forum .o_ep_collect{float:right;position:relative;width:2em;margin-left:12px}.o_forum .o_portrait{float:left;margin-right:16px}.o_forum .o_portrait_avatar{width:70px;height:70px}.o_forum .o_newindicator{font-size:10px;color:#5cb85c;text-transform:uppercase;padding-left:1em;vertical-align:text-top;white-space:nowrap}.o_forum .o_author,.o_forum .o_date{display:inline-block;color:#777}.o_forum .o_date{font-size:12px}.o_forum .o_modified{color:#8a6d3b;font-size:12px;font-style:italic}.o_forum .o_forum_message{margin-bottom:20px;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1)}.o_forum .o_forum_message_new{-webkit-box-shadow:0 1px 10px rgba(92,184,92,0.3);box-shadow:0 1px 10px rgba(92,184,92,0.3)}.o_forum .o_forum_message_highlight{-webkit-box-shadow:0 1px 10px rgba(240,173,78,0.5);box-shadow:0 1px 10px rgba(240,173,78,0.5)}.o_forum .o_forum_message_header{padding:10px 15px;border-bottom:1px solid #ddd;background-color:#f5f5f5;border-top-right-radius:3px;border-top-left-radius:3px}.o_forum .o_forum_message_title{margin-top:0}.o_forum .o_forum_message_body{padding:10px 15px}.o_forum .o_forum_message_attachments{border-top:1px solid #ddd;padding:10px 15px;font-size:12px;background-color:#f7f7f9}.o_forum .o_attachment{position:relative;max-width:250px;vertical-align:top;margin:6px 12px 10px 0}.o_forum .o_attachment img{margin-top:6px}.o_forum .o_filename{max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.o_forum .o_icon_enlarge{position:absolute;left:1em;bottom:1em;text-shadow:1px 1px 2px #fff, -1px 1px 2px #fff, 1px -1px 2px #fff, -1px -1px 2px #fff}@media (min-width: 768px) and (max-width: 991px){.o_forum .o_attachments{font-size:10px}.o_forum .o_attachment{max-width:200px}.o_forum .o_attachment img{max-width:150px}.o_forum .o_filename{max-width:200px}}@media (max-width: 767px){.o_forum .o_attachments{font-size:9px}.o_forum .o_attachment{max-width:150px}.o_forum .o_attachment img{max-width:100px}.o_forum .o_filename{max-width:150px}} -.o_quote_wrapper,.b_quote_wrapper{position:relative;margin:10px 0}.o_quote_author,.b_quote_author{color:#777;font-size:12px}.o_quote_author:before,.b_quote_author:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f10e";font-size:21px;padding-right:0.5em}blockquote.o_quote,blockquote.b_quote{color:#555;font-size:12px;margin-top:6px;padding:0 12px}a.o_chelp{display:inline-block;padding:1px 3px;text-align:center;vertical-align:middle;white-space:nowrap;font-size:10px;font-weight:normal;line-height:15px;color:#fff;background-color:#428bca;border:1px solid #357ebd;border-radius:2px;cursor:help;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}a.o_chelp:active,a.o_chelp:hover,a.o_chelp:focus{text-decoration:none;color:#fff;background-color:#3276b1;border-color:#285e8e}a.o_chelp i{font-size:10px !important}.o_chelp_wrapper{position:relative;float:right;display:inline-block;line-height:1em}.o_iframedisplay iframe{width:100%}.o_singlepage .o_edit{position:absolute;top:10px;right:37px}.o_content_popup{position:absolute;top:10px;right:12px}.o_module_cp_wrapper .o_tools{position:absolute;top:10px;right:12px;text-align:right;vertical-align:middle}.o_module_cp_wrapper .o_tools .o_search_wrapper{display:inline-block;position:relative;top:-2px}.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 .o_thumbnail,.tooltip .o_thumbnail{width:200px;height:200px}.o_htmleditor .o_lastmodified{color:#777}.o_htmleditor #o_save{margin-top:10px;text-align:center}.o_htmleditor #o_save input{margin-right:1em}.o_htmleditor #o_save input:last-child{margin-right:0}.o_notifications_news_wrapper .o_notifications_news_subscription{margin:10px 0}.o_notifications_news_wrapper .o_notifications_news_subscription h4 i,.o_notifications_news_wrapper .o_notifications_news_subscription .o_cal .fc-header-title h2 i,.o_cal .fc-header-title .o_notifications_news_wrapper .o_notifications_news_subscription h2 i{display:none}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_context{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content{margin-left:1.5em;position:relative}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_icon{position:absolute;left:-1.5em;line-height:1.5em;top:0}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_date{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_url{margin-left:1.5em}.o_noti{margin:6px 0 6px 12px;float:right;color:#777}.o_noti .o_label{color:#777;cursor:help}@media (max-width: 767px){.o_noti .o_label span{display:none}} +.o_quote_wrapper,.b_quote_wrapper{position:relative;margin:10px 0}.o_quote_author,.b_quote_author{color:#777;font-size:12px}.o_quote_author:before,.b_quote_author:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f10e";font-size:21px;padding-right:0.5em}blockquote.o_quote,blockquote.b_quote{color:#555;font-size:12px;margin-top:6px;padding:0 12px}a.o_chelp{display:inline-block;padding:1px 3px;text-align:center;vertical-align:middle;white-space:nowrap;font-size:10px;font-weight:normal;line-height:15px;color:#fff;background-color:#428bca;border:1px solid #357ebd;border-radius:2px;cursor:help;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}a.o_chelp:active,a.o_chelp:hover,a.o_chelp:focus{text-decoration:none;color:#fff;background-color:#3276b1;border-color:#285e8e}a.o_chelp i{font-size:10px !important}.o_chelp_wrapper{position:relative;float:right;display:inline-block;line-height:1em}.o_iframedisplay iframe{width:100%}.o_singlepage .o_edit{position:absolute;top:10px;right:37px}.o_content_popup{position:absolute;top:10px;right:12px}.o_module_cp_wrapper .o_tools{position:absolute;top:10px;right:12px;text-align:right;vertical-align:middle}.o_module_cp_wrapper .o_tools .o_search_wrapper{display:inline-block;position:relative;top:-2px}.o_dropdown .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.o_dropdown>li>a{display:block;padding:3px 0;clear:both;font-weight:normal;line-height:1.42857;color:#333;white-space:nowrap}.badge.o_scorm_completed{background-color:#3c763d}.badge.o_scorm_failed{background-color:#a94442}.badge.o_scorm_incomplete{background-color:#8a6d3b}.badge.o_scorm_not_attempted{background:none}.o_bc_meta .o_thumbnail,.tooltip .o_thumbnail{width:200px;height:200px}.o_htmleditor .o_lastmodified{color:#777}.o_htmleditor #o_save{margin-top:10px;text-align:center}.o_htmleditor #o_save input{margin-right:1em}.o_htmleditor #o_save input:last-child{margin-right:0}.o_notifications_news_wrapper .o_notifications_news_subscription{margin:10px 0}.o_notifications_news_wrapper .o_notifications_news_subscription h4 i,.o_notifications_news_wrapper .o_notifications_news_subscription .o_cal .fc-header-title h2 i,.o_cal .fc-header-title .o_notifications_news_wrapper .o_notifications_news_subscription h2 i{display:none}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_context{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content{margin-left:1.5em;position:relative}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_icon{position:absolute;left:-1.5em;line-height:1.5em;top:0}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_content .o_date{color:#777}.o_notifications_news_wrapper .o_notifications_news_subscription .o_notifications_news_url{margin-left:1.5em}.o_noti{margin:6px 0 6px 12px;float:right;color:#777}.o_noti .o_label{color:#777;cursor:help}@media (max-width: 767px){.o_noti .o_label span{display:none}} .panel-body .o_noti{margin:0}.o_portrait{display:inline-block}.o_portrait img{border-radius:50%;border:none;background-color:#eee;background-position:50% 50%;background-repeat:no-repeat;background-size:cover}.o_portrait_name{margin-top:6px}.o_portrait_avatar,.o_portrait_dummy,.o_portrait_dummy_female_big,.o_portrait_dummy_male_big,.o_portrait_anonymous{width:100px;height:100px}.o_portrait_dummy{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_dummy_female_big{background-image:url("../light/images/portrait/dummy_female_big.png")}.o_portrait_dummy_male_big{background-image:url("../light/images/portrait/dummy_male_big.png")}.o_portrait_anonymous{background-image:url("../light/images/portrait/dummy.png")}.o_portrait_avatar_small,.o_portrait_dummy_small,.o_portrait_dummy_female_small,.o_portrait_dummy_male_small,.o_portrait_anonymous_small{width:30px;height:30px}.o_portrait_dummy_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_portrait_dummy_female_small{background-image:url("../light/images/portrait/dummy_female_small.png")}.o_portrait_dummy_male_small{background-image:url("../light/images/portrait/dummy_male_small.png")}.o_portrait_anonymous_small{background-image:url("../light/images/portrait/dummy_small.png")}.o_datecomp{position:relative;width:40px;height:52px;border:1px solid #555;margin-right:12px;text-align:center;vertical-align:middle}.o_datecomp div.o_year{position:absolute;left:0;width:100%;top:-20px;height:20px;line-height:20px;font-size:10px}.o_datecomp div.o_month{height:20px;line-height:20px;font-size:12px;background-color:#428bca;color:#fff}.o_datecomp div.o_day{height:30px;line-height:30px;font-size:18px;border-top:1px solid #555;background-color:#fff;color:#333}.o_block_with_datecomp .o_head{position:relative;padding-left:52px}.o_block_with_datecomp .o_datecomp{position:absolute;top:0.2em;left:0}.o_block_with_datecomp .o_title{margin-top:0}.o_block_with_datecomp .o_meta{color:#777}.o_block_with_datecomp .o_content{border-left:5px solid #eee;padding:0 20px}.o_block_with_datecomp .o_block_footer{padding-left:25px}.o_cal_toptoolbar{margin-bottom:6px}.o_cal_toptoolbar .o_cal_toptoolbar_sub,.o_cal_toptoolbar .o_cal_toptoolbar_help{float:left;margin-right:12px}.o_feed .o_date,.o_feed .o_author{color:#777}.o_feed .o_subscription a{margin-right:1.5em}.o_feed .o_subscription form{margin-top:6px}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper{float:left}.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_title,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_rating_explanation,.o_feed .o_blog_posts .o_ratings_and_comments .o_rating_wrapper .o_legend{display:none}.o_feed .o_blog_posts .o_ratings_and_comments a.o_comments span{display:none}.o_segments_content{margin-top:20px}.o_tabbed_pane .o_tabbed_pane_content{padding:20px 0 6px 0}.o_togglebox_wrapper .o_opener{position:relative;left:-0.5em}.o_togglebox_wrapper div.o_togglebox_content{position:relative;margin:0}.o_togglebox_wrapper div.o_togglebox_content .o_hide{position:absolute;bottom:0.5em;right:1em}.o_toolboxes ul{margin:0 0 1.5em 0;padding:0 0 0 1.5em}.o_qrcode{width:256px;height:256px}#o_ajax_busy{position:absolute;left:50%;top:20em;margin-left:-2.5em;height:5em;width:5em;color:#fff;z-index:1201;display:none}#o_body.o_ajax_busy{cursor:busy}.o_exception .o_visual{position:relative;background-image:url("../light/images/lion-500x333.jpg");filter:grayscale(50%);-webkit-filter:grayscale(50%);-moz-filter:grayscale(50%);-ms-filter:grayscale(50%);-o-filter:grayscale(50%);width:500px;height:333px;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;margin:0 0 10px 16px}@media (min-width: 768px) and (max-width: 991px){.o_exception .o_visual{width:375px;height:249px}}@media (min-width: 500px) and (max-width: 767px){.o_exception .o_visual{width:250px;height:166px}}@media (max-width: 500px){.o_exception .o_visual{background-size:cover}}.o_exception .jumbotron h1,.o_exception .o_repo_details .o_lead h1,.o_repo_details .o_exception .o_lead h1{color:#d9534f}.tt-input{width:400px}.tt-dropdown-menu{width:400px;margin-top:6px;padding:0 0 0;color:#555;background-color:#fff;border:1px solid #66afe9;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;-webkit-box-shadow:0 0 8px rgba(102,175,233,0.6);box-shadow:0 0 8px rgba(102,175,233,0.6)}.tt-suggestion{padding:6px 12px;font-size:14px;line-height:1.42857}.tt-suggestion.tt-cursor{color:#fff;background-color:#428bca}.tt-suggestion p{margin:0}.o_search_link_extended,.o_search_link_simple{margin-top:12px;display:inline-block}.o_search_results_stats{color:#777;padding-left:1.5em}.o_search_highlight{margin-left:12px;font-size:12px}.o_search_result_title h4,.o_search_result_title .o_cal .fc-header-title h2,.o_cal .fc-header-title .o_search_result_title h2{display:inline-block;margin-right:12px;margin-bottom:6px}.o_search_result_highlight{font-weight:bold}.o_search_result_context{color:#3c763d}.o_search_result_excerpt{color:#555}.o_search_result_details .o_togglebox_wrapper.o_block{margin-top:0;margin-bottom:0}.o_search_result_details .o_togglebox_wrapper .o_togglebox_content{color:#777;font-size:12px;background:#fff;padding:6px 12px}@media (max-width: 767px){.o_search_result_details{display:none}} .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_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:"\f00c"}#o_cal_colorchooser div{border:1px solid #428bca;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}.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 #428bca;margin-bottom:10px}.o_coursetable.o_rendertype_custom .o_table_row .o_visual{border-right:1px solid #428bca}.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 #428bca;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:#428bca;border-color:#357ebd}.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: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:#3071a9;border-color:#285e8e}.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: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]: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:active,fieldset[disabled] .o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start.active{background-color:#428bca;border-color:#357ebd}.o_coursetable.o_rendertype_custom .o_table_row .o_access .o_start .badge{color:#428bca;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: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: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]: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: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: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: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]: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: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:#428bca}.o_coursetable.o_rendertype_custom .o_table_row .o_meta .o_title a:hover{color:#3071a9}.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:#428bca}.o_coursetable.o_rendertype_classic .o_rating_explanation{display:none}.o_coursetable.o_rendertype_classic .o_start,.o_coursetable.o_rendertype_classic .o_book{white-space:nowrap}.o_coursetable.o_rendertype_classic .o_repoentry_type{color:#555}.o_coursetable.o_rendertype_classic .o_repoentry_ac{color:#555}.o_catalog .o_level{position:relative;margin-bottom:10px;padding:0;border-top:1px solid #428bca;border-bottom:1px solid #428bca}.o_catalog .o_level .o_visual{height:180px}.o_catalog .o_level .o_meta{position:relative;min-height:180px;height:180px;overflow:hidden;margin:0 0 0 180px;padding:1em 0.5em 0.5em 2em}.o_catalog .o_level .o_meta .o_title{margin:0}.o_catalog .o_level .o_meta .o_title a{display:block;color:#428bca}.o_catalog .o_level .o_meta .o_title a:hover{color:#3071a9}.o_catalog .o_level .o_meta .o_desc{padding:1em 0 0.5em 0}@media (min-width: 768px) and (max-width: 991px){.o_catalog .o_level .o_visual{height:120px}.o_catalog .o_level .o_meta{min-height:120px;height:120px;margin:0 0 0 120px}}@media (max-width: 767px){.o_catalog .o_level .o_visual{height:75px}.o_catalog .o_level .o_meta{min-height:75px;height:75px;margin:0 0 0 75px;padding:0 0 0 1em}.o_catalog .o_level .o_meta .o_title{line-height:75px}.o_catalog .o_level .o_meta .o_desc{display:none}}.o_catalog .o_sublevels{position:relative;margin-bottom:20px}.o_catalog .o_sublevels:before,.o_catalog .o_sublevels:after{content:" ";display:table}.o_catalog .o_sublevels:after{clear:both}.o_catalog .o_sublevels .o_sublevel{position:relative;float:left;margin:0 20px 20px 0;width:180px}.o_catalog .o_sublevels .o_sublevel:last-child{margin-right:0}.o_catalog .o_sublevels .o_sublevel .o_visual{border:1px solid #428bca;position:relative;height:180px}.o_catalog .o_sublevels .o_sublevel .o_meta{position:absolute;left:0;bottom:0;width:100%;border:1px solid #428bca;border-top:0;background-color:rgba(255,255,255,0.8)}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title{margin:0;text-align:center;line-height:2em;height:2em;width:100%;overflow:hidden}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a{display:block;color:#428bca}.o_catalog .o_sublevels .o_sublevel .o_meta .o_title a:hover{color:#3071a9}@media (min-width: 768px) and (max-width: 991px){.o_catalog .o_sublevels .o_sublevel{width:120px;margin:0 10px 10px 0}.o_catalog .o_sublevels .o_sublevel .o_visual{height:120px}.o_catalog .o_sublevels .o_sublevel .o_title{font-size:90%}}@media (max-width: 767px){.o_catalog .o_sublevels .o_sublevel{width:120px;margin:0 1px 1px 0}.o_catalog .o_sublevels .o_sublevel .o_visual{height:120px;width:120px}.o_catalog .o_sublevels .o_sublevel .o_title{font-size:90%}} .o_repo_details{position:relative}.o_repo_details .o_lead .o_media{float:right;margin-left:2em;margin-bottom:2em}.o_repo_details .o_lead h1 i{display:none}.o_repo_details .o_overview i{margin-right:0.5em}.o_repo_details .o_overview div{margin-bottom:0.25em}.o_repo_details .o_start,.o_repo_details .o_book{margin:2em 0}.o_repo_details .o_social:before,.o_repo_details .o_social:after{content:" ";display:table}.o_repo_details .o_social:after{clear:both}.o_repo_details .o_social .o_rating_wrapper{float:left}.o_repo_details .o_social .o_comments{margin-left:1em}@media (max-width: 767px){.o_repo_details .o_lead p{font-size:16px}.o_repo_details .o_lead .o_media{margin-top:0}}@media (max-width: 613px){.o_repo_details .o_subcolumn{width:100%}}