From b1c7e520561c9d603ae8475c07f6ed4567337d5b Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 30 Sep 2011 11:43:08 +0200 Subject: [PATCH] FXOLAT-282: initial import of BPS virtual classroom course building block and skeleton of Vitero connector --HG-- branch : FXOLAT-282-virtualclassroom --- .../vitero/ViteroBookingConfiguration.java | 23 + .../vitero/ViteroBookingProvider.java | 131 +++ .../vitero/ViteroConfigController.java | 50 ++ .../vitero/ViteroDisplayController.java | 104 +++ .../vc/provider/vitero/_content/edit.html | 1 + .../olat/vc/provider/vitero/_content/run.html | 1 + .../provider/vitero/_spring/viteroContext.xml | 15 + .../de/bps/course/nodes/VCCourseNode.java | 201 +++++ .../nodes/vc/DefaultVCConfiguration.java | 89 ++ .../de/bps/course/nodes/vc/MeetingDate.java | 77 ++ .../bps/course/nodes/vc/VCConfiguration.java | 41 + .../nodes/vc/VCCourseNodeConfiguration.java | 88 ++ .../nodes/vc/VCDatesTableDataModel.java | 70 ++ .../bps/course/nodes/vc/VCEditController.java | 261 ++++++ .../de/bps/course/nodes/vc/VCEditForm.java | 295 +++++++ .../java/de/bps/course/nodes/vc/VCModul.java | 77 ++ .../bps/course/nodes/vc/VCRunController.java | 125 +++ .../bps/course/nodes/vc/VCSelectionForm.java | 88 ++ .../course/nodes/vc/_chelp/ced-vc-config.html | 5 + .../de/bps/course/nodes/vc/_content/edit.html | 14 + .../course/nodes/vc/_content/editForm.html | 53 ++ .../de/bps/course/nodes/vc/_content/run.html | 22 + .../nodes/vc/_i18n/LocalStrings_de.properties | 54 ++ .../nodes/vc/_i18n/LocalStrings_en.properties | 54 ++ .../nodes/vc/_spring/buildingblockContext.xml | 18 + .../bps/course/nodes/vc/_spring/vcContext.xml | 100 +++ .../course/nodes/vc/provider/VCProvider.java | 191 ++++ .../nodes/vc/provider/VCProviderFactory.java | 81 ++ .../provider/adobe/AdobeConfigController.java | 71 ++ .../adobe/AdobeConnectCleanupJob.java | 171 ++++ .../adobe/AdobeConnectConfiguration.java | 57 ++ .../provider/adobe/AdobeConnectProvider.java | 773 +++++++++++++++++ .../adobe/AdobeDisplayController.java | 218 +++++ .../vc/provider/adobe/AdobeEditForm.java | 92 ++ .../adobe/AdobeEditTableDataModel.java | 71 ++ .../provider/adobe/_chelp/ced-vc-config.html | 4 + .../vc/provider/adobe/_content/edit.html | 5 + .../nodes/vc/provider/adobe/_content/run.html | 28 + .../adobe/_i18n/LocalStrings_de.properties | 34 + .../adobe/_i18n/LocalStrings_en.properties | 34 + .../provider/adobe/_spring/adobeContext.xml | 20 + .../nodes/vc/provider/wimba/StatusCode.java | 67 ++ .../wimba/WimbaClassroomConfiguration.java | 183 ++++ .../wimba/WimbaClassroomProvider.java | 812 ++++++++++++++++++ .../provider/wimba/WimbaConfigController.java | 113 +++ .../wimba/WimbaDisplayController.java | 388 +++++++++ .../vc/provider/wimba/WimbaEditForm.java | 78 ++ .../vc/provider/wimba/WimbaResponse.java | 92 ++ .../provider/wimba/_chelp/ced-vc-config.html | 5 + .../vc/provider/wimba/_content/edit.html | 7 + .../nodes/vc/provider/wimba/_content/run.html | 58 ++ .../wimba/_i18n/LocalStrings_de.properties | 61 ++ .../wimba/_i18n/LocalStrings_en.properties | 60 ++ .../provider/wimba/_spring/wimbaContext.xml | 31 + .../static/themes/default/images/olat/vc.png | Bin 0 -> 412 bytes .../themes/default/images/olat/vc_over.png | Bin 0 -> 463 bytes 56 files changed, 5862 insertions(+) create mode 100644 src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingConfiguration.java create mode 100644 src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingProvider.java create mode 100644 src/main/java/com/frentix/olat/vc/provider/vitero/ViteroConfigController.java create mode 100644 src/main/java/com/frentix/olat/vc/provider/vitero/ViteroDisplayController.java create mode 100644 src/main/java/com/frentix/olat/vc/provider/vitero/_content/edit.html create mode 100644 src/main/java/com/frentix/olat/vc/provider/vitero/_content/run.html create mode 100644 src/main/java/com/frentix/olat/vc/provider/vitero/_spring/viteroContext.xml create mode 100644 src/main/java/de/bps/course/nodes/VCCourseNode.java create mode 100644 src/main/java/de/bps/course/nodes/vc/DefaultVCConfiguration.java create mode 100644 src/main/java/de/bps/course/nodes/vc/MeetingDate.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCConfiguration.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCCourseNodeConfiguration.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCDatesTableDataModel.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCEditController.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCEditForm.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCModul.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCRunController.java create mode 100644 src/main/java/de/bps/course/nodes/vc/VCSelectionForm.java create mode 100644 src/main/java/de/bps/course/nodes/vc/_chelp/ced-vc-config.html create mode 100644 src/main/java/de/bps/course/nodes/vc/_content/edit.html create mode 100644 src/main/java/de/bps/course/nodes/vc/_content/editForm.html create mode 100644 src/main/java/de/bps/course/nodes/vc/_content/run.html create mode 100644 src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_de.properties create mode 100644 src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_en.properties create mode 100644 src/main/java/de/bps/course/nodes/vc/_spring/buildingblockContext.xml create mode 100644 src/main/java/de/bps/course/nodes/vc/_spring/vcContext.xml create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/VCProvider.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/VCProviderFactory.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConfigController.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectCleanupJob.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectConfiguration.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectProvider.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeDisplayController.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditForm.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditTableDataModel.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/_chelp/ced-vc-config.html create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/edit.html create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/run.html create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_de.properties create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_en.properties create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/adobe/_spring/adobeContext.xml create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/StatusCode.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomConfiguration.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomProvider.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaConfigController.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaDisplayController.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaEditForm.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaResponse.java create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/_chelp/ced-vc-config.html create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/edit.html create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/run.html create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_de.properties create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_en.properties create mode 100644 src/main/java/de/bps/course/nodes/vc/provider/wimba/_spring/wimbaContext.xml create mode 100755 src/main/webapp/static/themes/default/images/olat/vc.png create mode 100755 src/main/webapp/static/themes/default/images/olat/vc_over.png diff --git a/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingConfiguration.java b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingConfiguration.java new file mode 100644 index 00000000000..e92f1cd775e --- /dev/null +++ b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingConfiguration.java @@ -0,0 +1,23 @@ +package com.frentix.olat.vc.provider.vitero; + +import de.bps.course.nodes.vc.DefaultVCConfiguration; + +/** + * + * Description:<br> + * TODO: srosse Class Description for ViteroBookingConfiguration + * + * <P> + * Initial Date: 26 sept. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class ViteroBookingConfiguration extends DefaultVCConfiguration { + + @Override + public boolean isConfigValid() { + return true; + } + + + +} diff --git a/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingProvider.java b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingProvider.java new file mode 100644 index 00000000000..4d4a3b422ac --- /dev/null +++ b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroBookingProvider.java @@ -0,0 +1,131 @@ +package com.frentix.olat.vc.provider.vitero; + +import java.net.URL; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.Identity; +import org.olat.core.logging.LogDelegator; + +import de.bps.course.nodes.vc.VCConfiguration; +import de.bps.course.nodes.vc.provider.VCProvider; + +/** + * + * Description:<br> + * Implementation of the Virtual Classroom for the Vitero Booking System + * + * <P> + * Initial Date: 26 sept. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class ViteroBookingProvider extends LogDelegator implements VCProvider { + + private boolean enabled; + + @Override + public VCProvider newInstance() { + return new ViteroBookingProvider(); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String getProviderId() { + return "vitero"; + } + + @Override + public String getDisplayName() { + return "Vitero"; + } + + @Override + public Map<String, String> getTemplates() { + return new HashMap<String,String>(); + } + + @Override + public boolean isProviderAvailable() { + return true; + } + + @Override + public boolean createClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config) { + return false; + } + + @Override + public boolean updateClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config) { + return false; + } + + @Override + public boolean removeClassroom(String roomId, VCConfiguration config) { + return false; + } + + @Override + public URL createClassroomUrl(String roomId, Identity identity, VCConfiguration config) { + return null; + } + + @Override + public URL createClassroomGuestUrl(String roomId, Identity identity, VCConfiguration config) { + return null; + } + + @Override + public boolean existsClassroom(String roomId, VCConfiguration config) { + return false; + } + + @Override + public boolean login(Identity identity, String password) { + return false; + } + + @Override + public boolean createModerator(Identity identity, String roomId) { + return false; + } + + @Override + public boolean createUser(Identity identity, String roomId) { + return false; + } + + @Override + public boolean createGuest(Identity identity, String roomId) { + return false; + } + + @Override + public Controller createDisplayController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, + boolean isModerator, VCConfiguration config) { + return new ViteroDisplayController(ureq, wControl, roomId, name, description, isModerator, (ViteroBookingConfiguration)config, this); + } + + @Override + public Controller createConfigController(UserRequest ureq, WindowControl wControl, String roomId, VCConfiguration config) { + return new ViteroConfigController(ureq, wControl, roomId, this, (ViteroBookingConfiguration)config); + } + + @Override + public VCConfiguration createNewConfiguration() { + return new ViteroBookingConfiguration(); + } + + +} diff --git a/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroConfigController.java b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroConfigController.java new file mode 100644 index 00000000000..05be2166f6c --- /dev/null +++ b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroConfigController.java @@ -0,0 +1,50 @@ + +package com.frentix.olat.vc.provider.vitero; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; + +/** + * + * Description:<br> + * TODO: srosse Class Description for ViteroConfigController + * + * <P> + * Initial Date: 26 sept. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class ViteroConfigController extends BasicController { + + private VelocityContainer editVC; + + + protected ViteroConfigController(UserRequest ureq, WindowControl wControl, String roomId, ViteroBookingProvider adobe, ViteroBookingConfiguration config) { + super(ureq, wControl); + + + editVC = createVelocityContainer("edit"); + + putInitialPanel(editVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // nothing to do + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + // + } + + @Override + protected void doDispose() { + + } + +} \ No newline at end of file diff --git a/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroDisplayController.java b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroDisplayController.java new file mode 100644 index 00000000000..0d8d44aec5d --- /dev/null +++ b/src/main/java/com/frentix/olat/vc/provider/vitero/ViteroDisplayController.java @@ -0,0 +1,104 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package com.frentix.olat.vc.provider.vitero; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; + +import de.bps.course.nodes.vc.MeetingDate; +import de.bps.course.nodes.vc.provider.VCProvider; + +/** + * + * Description:<br> + * TODO: srosse Class Description for ViteroDisplayController + * + * <P> + * Initial Date: 26 sept. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class ViteroDisplayController extends BasicController { + + + + // objects for run view + private VelocityContainer runVC; + private String roomId; + + + // data + private List<MeetingDate> dateList = new ArrayList<MeetingDate>(); + private ViteroBookingConfiguration config; + private MeetingDate meeting; + private Date allBegin, allEnd; + + private VCProvider vitero; + + public ViteroDisplayController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, boolean isModerator, ViteroBookingConfiguration config, VCProvider provider) { + super(ureq, wControl); + this.roomId = roomId; + this.vitero = provider; + this.config = config; + + // The dates Table to the Course odes + if(config.getMeetingDates() != null) dateList.addAll(config.getMeetingDates()); + + // select actual meeting + if(config.isUseMeetingDates()) { + Date now = new Date((new Date()).getTime() + 15*60*1000); // allow to start meetings about 15 minutes before begin + for(MeetingDate date : dateList) { + Date begin = date.getBegin(); + Date end = date.getEnd(); + if(now.after(begin) & now.before(end)) { + meeting = date; + } + allBegin = allBegin == null ? begin : begin.before(allBegin) ? begin : allBegin; + allEnd = allEnd == null ? end : end.after(allEnd) ? end : allEnd; + } + } else { + allBegin = new Date(); + allEnd = new Date(allBegin.getTime() + 365*24*60*60*1000); // preset one year + meeting = new MeetingDate(); + meeting.setBegin(allBegin); + meeting.setEnd(allEnd); + meeting.setTitle(name); + meeting.setDescription(description); + } + + runVC = createVelocityContainer("run"); + + + + putInitialPanel(runVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void doDispose() { + // nothing to dispose + } +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/com/frentix/olat/vc/provider/vitero/_content/edit.html b/src/main/java/com/frentix/olat/vc/provider/vitero/_content/edit.html new file mode 100644 index 00000000000..802992c4220 --- /dev/null +++ b/src/main/java/com/frentix/olat/vc/provider/vitero/_content/edit.html @@ -0,0 +1 @@ +Hello world diff --git a/src/main/java/com/frentix/olat/vc/provider/vitero/_content/run.html b/src/main/java/com/frentix/olat/vc/provider/vitero/_content/run.html new file mode 100644 index 00000000000..445a34418c4 --- /dev/null +++ b/src/main/java/com/frentix/olat/vc/provider/vitero/_content/run.html @@ -0,0 +1 @@ +Hello world (by Vitero) \ No newline at end of file diff --git a/src/main/java/com/frentix/olat/vc/provider/vitero/_spring/viteroContext.xml b/src/main/java/com/frentix/olat/vc/provider/vitero/_spring/viteroContext.xml new file mode 100644 index 00000000000..89ec5c1cace --- /dev/null +++ b/src/main/java/com/frentix/olat/vc/provider/vitero/_spring/viteroContext.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> + + + <!-- Definition of the providers --> + <bean id="viteroProvider" class="com.frentix.olat.vc.provider.vitero.ViteroBookingProvider"> + <property name="enabled" value="${vc.vitero.enabled}" /> + </bean> + + +</beans> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/VCCourseNode.java b/src/main/java/de/bps/course/nodes/VCCourseNode.java new file mode 100644 index 00000000000..c4beed3ed89 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/VCCourseNode.java @@ -0,0 +1,201 @@ +// <OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes; + +import java.util.List; + +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.tabbable.TabbableController; +import org.olat.core.id.Roles; +import org.olat.core.util.Util; +import org.olat.core.util.ValidationStatus; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.condition.ConditionEditController; +import org.olat.course.editor.CourseEditorEnv; +import org.olat.course.editor.NodeEditController; +import org.olat.course.editor.StatusDescription; +import org.olat.course.nodes.AbstractAccessableCourseNode; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.StatusDescriptionHelper; +import org.olat.course.nodes.TitledWrapperHelper; +import org.olat.course.run.navigation.NodeRunConstructionResult; +import org.olat.course.run.userview.NodeEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; + +import de.bps.course.nodes.vc.VCConfiguration; +import de.bps.course.nodes.vc.VCEditController; +import de.bps.course.nodes.vc.VCRunController; +import de.bps.course.nodes.vc.provider.VCProvider; +import de.bps.course.nodes.vc.provider.VCProviderFactory; + +/** + * Description:<br> + * date list course node. + * + * <P> + * Initial Date: 19.07.2010 <br> + * + * @author Jens Lindner (jlindne4@hs-mittweida.de) + * @author skoeber + */ +public class VCCourseNode extends AbstractAccessableCourseNode { + + private static final String TYPE = "vc"; + + // configuration + public static final String CONF_VC_CONFIGURATION = "vc_configuration"; + public final static String CONF_PROVIDER_ID = "vc_provider_id"; + + public VCCourseNode() { + super(TYPE); + } + + /** + * To support different virtual classroom implementations it's necessary to + * check whether the persisted configuration suits to the actual virtual + * classroom implementation or not. If not a new one will be created and + * persisted. + * + * @param provider + * @return the persisted configuration or a fresh one + */ + private VCConfiguration handleConfig(final VCProvider provider) { + getModuleConfiguration().setStringValue(CONF_PROVIDER_ID, provider.getProviderId()); + VCConfiguration config = (VCConfiguration) getModuleConfiguration().get(CONF_VC_CONFIGURATION); + if (config == null || config.getProviderId() == null || !config.getProviderId().equals(provider.getProviderId())) { + config = provider.createNewConfiguration(); + } + getModuleConfiguration().set(CONF_VC_CONFIGURATION, config); + return config; + } + + @Override + public void updateModuleConfigDefaults(boolean isNewNode) { + // no update to default config necessary + } + + @Override + public TabbableController createEditController(UserRequest ureq, WindowControl wControl, ICourse course, + UserCourseEnvironment userCourseEnv) { + updateModuleConfigDefaults(false); + CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(userCourseEnv.getCourseEditorEnv().getCurrentCourseNodeId()); + // load and check configuration + String providerId = getModuleConfiguration().getStringValue(CONF_PROVIDER_ID); + VCProvider provider = providerId == null ? VCProviderFactory.createDefaultProvider() : VCProviderFactory.createProvider(providerId); + VCConfiguration config = handleConfig(provider); + // create room if configured to do it immediately + if(config.isCreateMeetingImmediately()) { + Long key = course.getResourceableId(); + // here, the config is empty in any case, thus there are no start and end dates + provider.createClassroom(key + "_" + this.getIdent(), this.getShortName(), this.getLongTitle(), null, null, config); + } + // create edit controller + VCEditController childTabCntrllr = new VCEditController(ureq, wControl, this, course, userCourseEnv, provider, config); + NodeEditController nodeEditCtr = new NodeEditController(ureq, wControl, course.getEditorTreeModel(), course, chosenNode, course.getCourseEnvironment() + .getCourseGroupManager(), userCourseEnv, childTabCntrllr); + nodeEditCtr.addControllerListener(childTabCntrllr); + return nodeEditCtr; + } + + @Override + public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl, + UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) { + updateModuleConfigDefaults(false); + // check if user is moderator of the virtual classroom + Roles roles = ureq.getUserSession().getRoles(); + boolean moderator = roles.isOLATAdmin(); + Long key = userCourseEnv.getCourseEnvironment().getCourseResourceableId(); + if (!moderator) { + if(roles.isInstitutionalResourceManager() | roles.isAuthor()) { + RepositoryManager rm = RepositoryManager.getInstance(); + ICourse course = CourseFactory.loadCourse(key); + RepositoryEntry re = rm.lookupRepositoryEntry(course, false); + if (re != null) { + moderator = rm.isOwnerOfRepositoryEntry(ureq.getIdentity(), re); + if(!moderator) { + moderator = rm.isInstitutionalRessourceManagerFor(re, ureq.getIdentity()); + } + } + } + } + // load configuration + final String providerId = getModuleConfiguration().getStringValue(CONF_PROVIDER_ID); + VCProvider provider = providerId == null ? VCProviderFactory.createDefaultProvider() : VCProviderFactory.createProvider(providerId); + VCConfiguration config = handleConfig(provider); + // create run controller + Controller runCtr = new VCRunController(ureq, wControl, key + "_" + this.getIdent(), this.getShortName(), this.getLongTitle(), config, provider, moderator); + Controller controller = TitledWrapperHelper.getWrapper(ureq, wControl, runCtr, this, "o_vc_icon"); + return new NodeRunConstructionResult(controller); + } + + @Override + public Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, + NodeEvaluation ne) { + return null; + } + + @Override + public StatusDescription[] isConfigValid(CourseEditorEnv cev) { + String translatorStr = Util.getPackageName(ConditionEditController.class); + List<StatusDescription> statusDescs = isConfigValidWithTranslator(cev, translatorStr, getConditionExpressions()); + return StatusDescriptionHelper.sort(statusDescs); + } + + public RepositoryEntry getReferencedRepositoryEntry() { + return null; + } + + public StatusDescription isConfigValid() { + if (oneClickStatusCache != null) { return oneClickStatusCache[0]; } + StatusDescription status = StatusDescription.NOERROR; + + // load configuration + final String providerId = getModuleConfiguration().getStringValue(CONF_PROVIDER_ID); + VCProvider provider = providerId == null ? VCProviderFactory.createDefaultProvider() : VCProviderFactory.createProvider(providerId); + VCConfiguration config = handleConfig(provider); + boolean invalid = !config.isConfigValid(); + if (invalid) { + String[] params = new String[] { this.getShortTitle() }; + String shortKey = "error.config.short"; + String longKey = "error.config.long"; + String translationPackage = VCEditController.class.getPackage().getName(); + status = new StatusDescription(ValidationStatus.ERROR, shortKey, longKey, params, translationPackage); + status.setDescriptionForUnit(getIdent()); + status.setActivateableViewIdentifier(VCEditController.PANE_TAB_VCCONFIG); + } + + return status; + } + + public boolean needsReferenceToARepositoryEntry() { + return false; + } + + @Override + public void cleanupOnDelete(ICourse course) { + // load configuration + final String providerId = getModuleConfiguration().getStringValue(CONF_PROVIDER_ID); + VCProvider provider = providerId == null ? VCProviderFactory.createDefaultProvider() : VCProviderFactory.createProvider(providerId); + VCConfiguration config = handleConfig(provider); + // remove meeting + provider.removeClassroom(course.getResourceableId() + "_" + this.getIdent(), config); + } + +} +// </OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/DefaultVCConfiguration.java b/src/main/java/de/bps/course/nodes/vc/DefaultVCConfiguration.java new file mode 100644 index 00000000000..4d3a5af5887 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/DefaultVCConfiguration.java @@ -0,0 +1,89 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.io.Serializable; +import java.util.List; + + +/** + * + * Description:<br> + * Standard configuration object, each provider implementation must override this class + * and extend it with it's specific configuration values. + * + * <P> + * Initial Date: 18.01.2011 <br> + * @author skoeber + */ +public abstract class DefaultVCConfiguration implements VCConfiguration, Serializable { + + public static String DEFAULT_TEMPLATE = "default"; + + private String providerId; + private String templateKey; + private List<MeetingDate> meetingDatas; + private boolean useMeetingDates; + private boolean createMeetingImmediately; + + @Override + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + @Override + public String getTemplateKey() { + return templateKey; + } + + public void setTemplateKey(String templateKey) { + this.templateKey = templateKey; + } + + @Override + public boolean isUseMeetingDates() { + return useMeetingDates; + } + + public void setUseMeetingDates(boolean useMeetingDates) { + this.useMeetingDates = useMeetingDates; + } + + @Override + public List<MeetingDate> getMeetingDates() { + return meetingDatas; + } + + public void setMeetingDatas(List<MeetingDate> meetingDatas) { + this.meetingDatas = meetingDatas; + } + + public void setCreateMeetingImmediately(boolean createMeetingImmediately) { + this.createMeetingImmediately = createMeetingImmediately; + } + + @Override + public boolean isCreateMeetingImmediately() { + return createMeetingImmediately; + } + + @Override + public abstract boolean isConfigValid(); + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/MeetingDate.java b/src/main/java/de/bps/course/nodes/vc/MeetingDate.java new file mode 100644 index 00000000000..7e6bce2f99f --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/MeetingDate.java @@ -0,0 +1,77 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.io.Serializable; +import java.util.Date; + +/** + * Description:<br> + * Virtual Classroom appointment model to be used in course module configuration. + * Initial Date: 30.08.2010 <br> + * + * @author Jens Lindner (jlindne4@hs-mittweida.de) + * @author skoeber + */ +public class MeetingDate implements Serializable { + + private String title; + private String description; + private Date start; + private Date end; + + public MeetingDate() { + // nothing to do + } + + public MeetingDate(final String title, final String description, Date start, Date end) { + this.title = title; + this.description = description; + this.start= start; + this.end = end; + } + + public final String getTitle() { + return title; + } + + public final void setTitle(final String title) { + this.title = title; + } + + public final String getDescription() { + return description; + } + + public final void setDescription(final String description) { + this.description = description; + } + + public Date getBegin() { + return start; + } + + public void setBegin(Date start) { + this.start = start; + } + + public Date getEnd() { + return end; + } + + public void setEnd(Date end) { + this.end = end; + } +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/VCConfiguration.java b/src/main/java/de/bps/course/nodes/vc/VCConfiguration.java new file mode 100644 index 00000000000..3953e8c9474 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCConfiguration.java @@ -0,0 +1,41 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.io.Serializable; +import java.util.List; + + +/** + * + * Description:<br> + * Each virtual classroom implementation must persist it's configuration + * by using this interface. The course node is able to differentiate for + * which implementation the stored configuration is intended (necessary to + * support multiple virtual classroom implementations in one OLAT instance). + * + * <P> + * Initial Date: 20.12.2010 <br> + * @author skoeber + */ +public interface VCConfiguration extends Serializable { + + public String getProviderId(); + public String getTemplateKey(); + public boolean isUseMeetingDates(); + public List<MeetingDate> getMeetingDates(); + public boolean isCreateMeetingImmediately(); + public boolean isConfigValid(); +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/VCCourseNodeConfiguration.java b/src/main/java/de/bps/course/nodes/vc/VCCourseNodeConfiguration.java new file mode 100644 index 00000000000..e37f9ab08ce --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCCourseNodeConfiguration.java @@ -0,0 +1,88 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.util.List; +import java.util.Locale; + +import org.olat.core.extensions.ExtensionResource; +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.Util; +import org.olat.course.nodes.AbstractCourseNodeConfiguration; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.CourseNodeConfiguration; + +import de.bps.course.nodes.VCCourseNode; + +/** + * Description:<br> + * Configuration for date lists - Virtual Classroom dates. + * + * <P> + * Initial Date: 04.07.2010 <br> + * + * @author Jens Lindner(jlindne4@hs-mittweida.de) + * @author skoeber + */ +public class VCCourseNodeConfiguration extends AbstractCourseNodeConfiguration implements CourseNodeConfiguration { + + public String getAlias() { + return "vc"; + } + + public String getIconCSSClass() { + return "o_vc_icon"; + } + + public CourseNode getInstance() { + return new VCCourseNode(); + } + + public String getLinkCSSClass() { + return "o_vc_icon"; + } + + public String getLinkText(Locale locale) { + Translator fallback = Util.createPackageTranslator(CourseNodeConfiguration.class, locale); + Translator translator = Util.createPackageTranslator(this.getClass(), locale, fallback); + return translator.translate("title_vc"); + } + + @Override + public boolean isEnabled() { + return super.isEnabled(); + } + + public ExtensionResource getExtensionCSS() { + return null; + } + + public List getExtensionResources() { + return null; + } + + public String getName() { + return getAlias(); + } + + public void setup() { + // no special setup necessary + } + + public void tearDown() { + // no special tear down necessary + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/VCDatesTableDataModel.java b/src/main/java/de/bps/course/nodes/vc/VCDatesTableDataModel.java new file mode 100644 index 00000000000..67416c96917 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCDatesTableDataModel.java @@ -0,0 +1,70 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.util.List; + +import org.olat.core.gui.components.table.DefaultTableDataModel; + + +/** + * + * Description:<br> + * Table model for run view of vc course node. Summarizes all planned dates for meetings. + * + * <P> + * Initial Date: 19.01.2011 <br> + * @author skoeber + */ +public class VCDatesTableDataModel extends DefaultTableDataModel { + + //title, description, begin, end + private static final int COLUMN_COUNT = 4; + + public VCDatesTableDataModel(List objects) { + super(objects); + } + + @Override + public int getColumnCount() { + return COLUMN_COUNT; + } + + @Override + public Object getValueAt(int row, int col) { + final MeetingDate model = ((MeetingDate) objects.get(row)); + + switch (col) { + case 0: + return model.getTitle(); + case 1: + return model.getDescription(); + case 2: + return model.getBegin(); + case 3: + return model.getEnd(); + default: + return "error"; + } + } + + public MeetingDate getEntryAt(int row) { + return (MeetingDate) objects.get(row); + } + + public void setEntries(List newEntries) { + this.objects = newEntries; + } +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/VCEditController.java b/src/main/java/de/bps/course/nodes/vc/VCEditController.java new file mode 100644 index 00000000000..2cc0abe80c1 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCEditController.java @@ -0,0 +1,261 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.util.Date; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.tabbedpane.TabbedPane; +import org.olat.core.gui.components.velocity.VelocityContainer; +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.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController; +import org.olat.course.ICourse; +import org.olat.course.assessment.AssessmentHelper; +import org.olat.course.condition.Condition; +import org.olat.course.condition.ConditionEditController; +import org.olat.course.editor.NodeEditController; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.modules.ModuleConfiguration; + +import de.bps.course.nodes.VCCourseNode; +import de.bps.course.nodes.vc.provider.VCProvider; +import de.bps.course.nodes.vc.provider.VCProviderFactory; + +/** + * Description:<br> + * Edit controller for dates list course nodes - Virtual Classroom dates . + * + * <P> + * Initial Date: 30.08.2010 <br> + * + * @author Jens Lindner (jlindne4@hs-mittweida.de) + * @author skoeber + */ +public class VCEditController extends ActivateableTabbableDefaultController implements ControllerEventListener { + + private static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility"; + public static final String PANE_TAB_VCCONFIG = "pane.tab.vcconfig"; + final static String[] paneKeys = { PANE_TAB_VCCONFIG, PANE_TAB_ACCESSIBILITY }; + + // GUI + private VelocityContainer editVc; + private ConditionEditController accessibilityCondContr; + private TabbedPane tabPane; + private Controller configCtr; + private VCSelectionForm selForm; + private VCEditForm editForm; + private DialogBoxController yesNoUpdate; + private DialogBoxController yesNoDelete; + + // runtime data + private VCCourseNode courseNode; + private VCConfiguration config; + private VCProvider provider; + private String roomId; + + public VCEditController(UserRequest ureq, WindowControl wControl, VCCourseNode courseNode, + ICourse course, UserCourseEnvironment userCourseEnv, VCProvider provider, VCConfiguration config) { + super(ureq, wControl); + this.courseNode = courseNode; + this.config = config; + this.provider = provider; + + editVc = this.createVelocityContainer("edit"); + + Condition accessCondition = courseNode.getPreConditionAccess(); + accessibilityCondContr = new ConditionEditController(ureq, wControl, course.getCourseEnvironment().getCourseGroupManager(), + accessCondition, "accessabilityConditionForm", AssessmentHelper.getAssessableNodes(course.getEditorTreeModel(), courseNode), + userCourseEnv); + this.listenTo(accessibilityCondContr); + + // show selection form when there is more than one registered virtual classroom provider + List<VCProvider> registeredProviders = VCProviderFactory.getProviders(); + if(registeredProviders.size() > 1) { + selForm = new VCSelectionForm(ureq, wControl, provider.getProviderId()); + listenTo(selForm); + editVc.put("VCSelectionForm", selForm.getInitialComponent()); + } + + editForm = new VCEditForm(ureq, wControl, provider.getTemplates(), (DefaultVCConfiguration) config); + listenTo(editForm); + editVc.put("editForm", editForm.getInitialComponent()); + + roomId = course.getResourceableId() + "_" + courseNode.getIdent(); + + configCtr = provider.createConfigController(ureq, wControl, roomId, config); + listenTo(configCtr); + editVc.put("configCtr", configCtr.getInitialComponent()); + } + + @Override + public String[] getPaneKeys() { + return paneKeys; + } + + @Override + public TabbedPane getTabbedPane() { + return tabPane; + } + + @Override + protected void doDispose() { + if(configCtr != null) { + removeAsListenerAndDispose(configCtr); + configCtr = null; + } + if(editForm != null) { + removeAsListenerAndDispose(editForm); + editForm = null; + } + if(selForm != null) { + removeAsListenerAndDispose(selForm); + selForm = null; + } + if(yesNoDelete != null) { + removeAsListenerAndDispose(yesNoDelete); + yesNoDelete = null; + } + if(yesNoUpdate != null) { + removeAsListenerAndDispose(yesNoUpdate); + yesNoUpdate = null; + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // nothing to do + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == accessibilityCondContr) { + if (event == Event.CHANGED_EVENT) { + Condition cond = accessibilityCondContr.getCondition(); + courseNode.setPreConditionAccess(cond); + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + } else if (source == configCtr | source == editForm) { + courseNode.getModuleConfiguration().set(VCCourseNode.CONF_VC_CONFIGURATION, config); + courseNode.getModuleConfiguration().setStringValue(VCCourseNode.CONF_PROVIDER_ID, config.getProviderId()); + /* + if(provider.existsClassroom(roomId, config)) { + removeAsListenerAndDispose(yesNoUpdate); + yesNoUpdate = DialogBoxUIFactory.createYesNoDialog(ureq, getWindowControl(), translate("sync.meeting.title"), translate("sync.meeting.text")); + listenTo(yesNoUpdate); + yesNoUpdate.activate(); + } + */ + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } else if (source == selForm) { + /* + * If classroom already exists and the user changes the provider, + * the existing room has to be deleted for cleanup purposes. Ask + * the user if this is intended. + */ + if(provider.existsClassroom(roomId, config)) { + removeAsListenerAndDispose(yesNoDelete); + yesNoDelete = DialogBoxUIFactory.createYesNoDialog(ureq, getWindowControl(), translate("delete.meeting.title"), translate("delete.meeting.text")); + listenTo(yesNoDelete); + yesNoDelete.activate(); + } else { + reset(ureq); + } + } else if (source == yesNoDelete) { + if(DialogBoxUIFactory.isYesEvent(event)) { + provider.removeClassroom(roomId, config); + reset(ureq); + } + } else if(source == yesNoUpdate) { + if(DialogBoxUIFactory.isYesEvent(event)) { + Date allBegin = null, allEnd = null; + if(config.getMeetingDates() != null) { + for(MeetingDate date : config.getMeetingDates()) { + Date begin = date.getBegin(); + Date end = date.getEnd(); + allBegin = allBegin == null ? begin : begin.before(allBegin) ? begin : allBegin; + allEnd = allEnd == null ? end : end.after(allEnd) ? end : allEnd; + } + } + boolean success = provider.updateClassroom(roomId, courseNode.getShortTitle(), courseNode.getLongTitle(), allBegin, allEnd, config); + if(success) { + getWindowControl().setInfo(translate("success.update.room")); + } else { + getWindowControl().setError(translate("error.update.room")); + } + } + } else if(event == NodeEditController.NODECONFIG_CHANGED_EVENT) { + // something has changed, maybe the title or description, thus ask to update + if(provider.existsClassroom(roomId, config)) { + removeAsListenerAndDispose(yesNoUpdate); + yesNoUpdate = DialogBoxUIFactory.createYesNoDialog(ureq, getWindowControl(), translate("sync.meeting.title"), translate("sync.meeting.text")); + listenTo(yesNoUpdate); + yesNoUpdate.activate(); + } + } + } + + private void reset(UserRequest ureq) { + removeAsListenerAndDispose(editForm); + removeAsListenerAndDispose(configCtr); + // prepare new edit view + String providerId = selForm.getSelectedProvider(); + provider = VCProviderFactory.createProvider(providerId); + config = provider.createNewConfiguration(); + // create room if configured to do it immediately + if(config.isCreateMeetingImmediately()) { + // here, the config is empty in any case, thus there are no start and end dates + provider.createClassroom(roomId, courseNode.getShortName(), courseNode.getLongTitle(), null, null, config); + } + editForm = new VCEditForm(ureq, getWindowControl(), provider.getTemplates(), (DefaultVCConfiguration) config); + listenTo(editForm); + editVc.put("editForm", editForm.getInitialComponent()); + configCtr = provider.createConfigController(ureq, getWindowControl(), roomId, config); + listenTo(configCtr); + editVc.put("configCtr", configCtr.getInitialComponent()); + editVc.setDirty(true); + // save the minimal config + courseNode.getModuleConfiguration().set(VCCourseNode.CONF_VC_CONFIGURATION, config); + courseNode.getModuleConfiguration().setStringValue(VCCourseNode.CONF_PROVIDER_ID, config.getProviderId()); + + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + + public void addTabs(TabbedPane tabbedPane) { + tabPane = tabbedPane; + tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), + accessibilityCondContr.getWrappedDefaultAccessConditionVC(translate("condition.accessibility.title"))); + tabbedPane.addTab(translate(PANE_TAB_VCCONFIG), editVc); + + } + + public static boolean isConfigValid(ModuleConfiguration moduleConfig) { + List<MeetingDate> dateList = (List<MeetingDate>) moduleConfig.get(VCCourseNode.CONF_VC_CONFIGURATION); + if (dateList != null) { + for (MeetingDate date : dateList) { + if (date.getTitle().isEmpty() || date.getDescription().isEmpty()) { return false; } + + } + return true; + } + return false; + } +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/VCEditForm.java b/src/main/java/de/bps/course/nodes/vc/VCEditForm.java new file mode 100644 index 00000000000..54fdda35a80 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCEditForm.java @@ -0,0 +1,295 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.TimeZone; + +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.DateChooser; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.FormLinkImpl; +import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.editor.NodeEditController; + + +/** + * + * Description:<br> + * Form for standard options of the virtual classroom course node. + * + * <P> + * Initial Date: 18.01.2011 <br> + * @author skoeber + */ +public class VCEditForm extends FormBasicController { + + // GUI + private FormLayoutContainer editVC; + private FormSubmit submit; + private List<TextElement> vcTitleInputList; + private List<TextElement> vcDescriptionInputList; + private List<DateChooser> vcCalenderbeginInputList; + private List<TextElement> vcDurationInputList; + private List<FormLink> vcDelButtonList; + private List<FormLink> vcAddButtonList; + private SingleSelection vcTemplate; + private MultipleSelectionElement multiSelectOptions; + private static String OPTION_DATES = "vc.access.dates"; + + // data + private DefaultVCConfiguration config; + private Map<String,String> templates = new HashMap<String, String>(); + private List<MeetingDate> dateList = new ArrayList<MeetingDate>(); + + private int counter = 0; + + public VCEditForm(UserRequest ureq, WindowControl wControl, Map<String, String> templates, DefaultVCConfiguration config) { + super(ureq, wControl, FormBasicController.LAYOUT_VERTICAL); + this.config = config; + this.templates.putAll(templates); + + // read existing dates from config + if(config.getMeetingDates() != null) dateList.addAll(config.getMeetingDates()); + + this.vcTitleInputList = new ArrayList<TextElement>(dateList.size()); + this.vcDescriptionInputList = new ArrayList<TextElement>(dateList.size()); + this.vcCalenderbeginInputList = new ArrayList<DateChooser>(dateList.size()); + this.vcDurationInputList = new ArrayList<TextElement>(dateList.size()); + this.vcAddButtonList = new ArrayList<FormLink>(dateList.size()); + this.vcDelButtonList = new ArrayList<FormLink>(dateList.size()); + + initForm(this.flc, this, ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + editVC = FormLayoutContainer.createCustomFormLayout("titleLayout", getTranslator(), velocity_root + "/editForm.html"); + formLayout.add(editVC); + + // template chooser + String[] keys = new String[templates.size() + 1]; + keys[0] = DefaultVCConfiguration.DEFAULT_TEMPLATE; + String[] values = new String[templates.size() + 1]; + values[0] = ""; + int index = 1; + for(String key:templates.keySet()) { + keys[index] = key; + values[index] = templates.get(key); + index++; + } + boolean hasTemplates = templates.size() > 0; + if(hasTemplates) { + vcTemplate = uifactory.addDropdownSingleselect("vc.template.choose", "vc.template.choose.label", editVC, keys, values, null); + String templateKey = config.getTemplateKey(); + vcTemplate.select(templateKey == null ? DefaultVCConfiguration.DEFAULT_TEMPLATE : templateKey, true); + } + editVC.contextPut("hasTemplates", hasTemplates); + + // meeting options + boolean useDates = !dateList.isEmpty() | config.isUseMeetingDates(); + String[] accessKeys = new String[] {OPTION_DATES}; + String[] accessVals = new String[] {translate(OPTION_DATES)}; + multiSelectOptions = uifactory.addCheckboxesVertical("vc.options", "vc.options.label", editVC, accessKeys, accessVals, null, 1); + multiSelectOptions.select(OPTION_DATES, useDates); + multiSelectOptions.addActionListener(this, FormEvent.ONCHANGE); + + // create gui elements for all meetings + editVC.contextPut("useDates", useDates); + if(useDates) addDates(); + + editVC.contextPut("dateList", dateList); + editVC.contextPut("vcTitleInputList", vcTitleInputList); + editVC.contextPut("vcDescriptionInputList", vcDescriptionInputList); + editVC.contextPut("vcCalenderbeginInputList", vcCalenderbeginInputList); + editVC.contextPut("vcDurationInputList", vcDurationInputList); + editVC.contextPut("vcAddButtonList", vcAddButtonList); + editVC.contextPut("vcDelButtonList", vcDelButtonList); + + submit = new FormSubmit("subm", "submit"); + + formLayout.add(submit); + } + + private void addDates() { + if (dateList.isEmpty()) { + MeetingDate meetingData = new MeetingDate(); + meetingData.setBegin(new Date()); + meetingData.setEnd(new Date(meetingData.getBegin().getTime() + 1000*60*60)); + dateList.add(meetingData); + } + for (int i = 0; i < dateList.size(); i++) { + MeetingDate date = dateList.get(i); + addRow(i, date); + } + } + + private void removeDates() { + for (int i = 0; i < dateList.size(); i++) { + removeRow(i); + } + } + + @Override + protected void doDispose() { + // nothing to dispose + } + + @Override + protected void formOK(UserRequest ureq) { + // read data from form elements + for (int i = 0; i < dateList.size(); i++) { + MeetingDate date = dateList.get(i); + String dateValue = vcTitleInputList.get(i).getValue(); + date.setTitle(dateValue); + + StringTokenizer strTok = new StringTokenizer(vcDurationInputList.get(i).getValue(), ":", false); + long dur = 1000 * 60 * 60 * Long.parseLong(strTok.nextToken()) + 1000 * 60 * Long.parseLong(strTok.nextToken()); + + date.setBegin(vcCalenderbeginInputList.get(i).getDate()); + date.setEnd(new Date(date.getBegin().getTime() + dur)); + date.setDescription(vcDescriptionInputList.get(i).getValue()); + } + boolean useDates = multiSelectOptions.getSelectedKeys().contains(OPTION_DATES); + config.setUseMeetingDates(useDates); + if(useDates) config.setMeetingDatas(dateList); + if(!templates.isEmpty() && vcTemplate.isOneSelected()) config.setTemplateKey(vcTemplate.getSelectedKey()); + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source.getComponent() instanceof Link) { + if (vcAddButtonList.contains(source)) { + Long row = new Long(vcAddButtonList.indexOf(source)); + int index = row.intValue() + 1; + MeetingDate meetingData = new MeetingDate(); + meetingData.setBegin(new Date()); + meetingData.setEnd(new Date(meetingData.getBegin().getTime() + 1000*60*60)); + dateList.add(index, meetingData); + addRow(index, meetingData); + } else if (vcDelButtonList.contains(source)) { + // special case: only one line existent + if (dateList.size() == 1) { + // clear this line + vcTitleInputList.get(0).setValue(""); + vcDescriptionInputList.get(0).setValue(""); + vcCalenderbeginInputList.get(0).setDate(new Date()); + vcDurationInputList.get(0).setValue("01:00"); + } else { + int row = vcDelButtonList.indexOf(source); + removeRow(row); + } + } + } else if(source == multiSelectOptions) { + boolean useDates = multiSelectOptions.getSelectedKeys().contains(OPTION_DATES); + if(useDates) addDates(); + else removeDates(); + editVC.contextRemove("useDates"); + editVC.contextPut("useDates", useDates); + editVC.setDirty(true); + } + super.formInnerEvent(ureq, source, event); + } + + private void addRow(int index, final MeetingDate date) { + // title + TextElement vcTitle = uifactory.addTextElement("title" + counter, null, -1, date.getTitle(), editVC); + vcTitle.setDisplaySize(30); + vcTitle.setMandatory(true); + vcTitle.setNotEmptyCheck("vc.table.title.empty"); + vcTitleInputList.add(index, vcTitle); + + // description + TextElement vcDescription = uifactory.addTextElement("description" + counter, null, -1, date.getDescription(), editVC); + vcDescription.setDisplaySize(20); + vcDescription.setNotEmptyCheck("vc.table.description.empty"); + vcDescription.setMandatory(true); + vcDescriptionInputList.add(index, vcDescription); + + // begin + DateChooser vcScheduleDate = uifactory.addDateChooser("begin" + counter, "vc.table.begin", "", editVC); + vcScheduleDate.setNotEmptyCheck("vc.table.begin.empty"); + vcScheduleDate.setValidDateCheck("vc.table.begin.error"); + vcScheduleDate.setMandatory(true); + vcScheduleDate.setDisplaySize(20); + vcScheduleDate.setDateChooserTimeEnabled(true); + vcScheduleDate.setDate(date.getBegin()); + vcCalenderbeginInputList.add(index, vcScheduleDate); + + // add date duration + SimpleDateFormat sdDuration = new SimpleDateFormat("HH:mm"); + TimeZone tz = TimeZone.getTimeZone("Etc/GMT+0"); + sdDuration.setTimeZone(tz); + + TextElement vcDuration = uifactory.addTextElement("duration" + counter, "vc.table.duration", 5, String.valueOf(0), editVC); + vcDuration.setDisplaySize(5); + vcDuration.setValue(sdDuration.format(new Date(date.getEnd().getTime() - date.getBegin().getTime()))); + vcDuration.setRegexMatchCheck("\\d{1,2}:\\d\\d", "form.error.format"); + vcDuration.setExampleKey("vc.table.duration.example", null); + vcDuration.setNotEmptyCheck("vc.table.duration.empty"); + vcDuration.setErrorKey("vc.table.duration.error", null); + vcDuration.setMandatory(true); + vcDuration.showExample(true); + vcDuration.showError(false); + this.vcDurationInputList.add(index, vcDuration); + + // add row button + FormLink addButton = new FormLinkImpl("add" + counter, "add" + counter, "vc.table.add", Link.BUTTON_SMALL); + editVC.add(addButton); + vcAddButtonList.add(index, addButton); + + // remove row button + FormLink delButton = new FormLinkImpl("delete" + counter, "delete" + counter, "vc.table.delete", Link.BUTTON_SMALL); + editVC.add(delButton); + vcDelButtonList.add(index, delButton); + + // increase the counter to enable unique component names + counter++; + } + + private void removeRow(int row) { + // remove date from model list + if(dateList.get(row) != null) dateList.remove(row); + + editVC.remove(vcTitleInputList.remove(row)); + editVC.remove(vcDescriptionInputList.remove(row)); + editVC.remove(vcDurationInputList.remove(row)); + editVC.remove(vcCalenderbeginInputList.remove(row)); + editVC.remove(vcAddButtonList.remove(row)); + editVC.remove(vcDelButtonList.remove(row)); + + // decrease the counter for unique component names + counter--; + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/VCModul.java b/src/main/java/de/bps/course/nodes/vc/VCModul.java new file mode 100644 index 00000000000..4a2081fcd25 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCModul.java @@ -0,0 +1,77 @@ +// <OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.logging.activity.LogModule; + +/** + * Description:<br> + * Virtual classroom configuration, setup by spring + * + * <P> + * Initial Date: 28.08.2010 <br> + * + * @author Jens Lindner (jlindne4@hs-mittweida.de) + * @author skoeber + */ +public class VCModul { + + static final OLog log = Tracing.createLoggerFor(LogModule.class); + + private String baseUrl, adminLogin, adminPassword, connectID, version; + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getAdminLogin() { + return adminLogin; + } + + public void setAdminLogin(String adminLogin) { + this.adminLogin = adminLogin; + } + + public String getAdminPassword() { + return adminPassword; + } + + public void setAdminPassword(String adminPassword) { + this.adminPassword = adminPassword; + } + + public String getConnectID() { + return connectID; + } + + public void setConnectID(String connectID) { + this.connectID = connectID; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + +} +// </OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/VCRunController.java b/src/main/java/de/bps/course/nodes/vc/VCRunController.java new file mode 100644 index 00000000000..b8a2244d4b8 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCRunController.java @@ -0,0 +1,125 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.table.DefaultColumnDescriptor; +import org.olat.core.gui.components.table.TableController; +import org.olat.core.gui.components.table.TableGuiConfiguration; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; + +import de.bps.course.nodes.vc.provider.VCProvider; + +/** + * + * Description:<br> + * Run view for virtual classroom course node + * + * <P> + * Initial Date: 18.01.2011 <br> + * @author skoeber + */ +public class VCRunController extends BasicController { + + //objects for run view + private VelocityContainer runVC; + private Controller displayCtr; + private TableController tableCtr; + + // data + private VCProvider provider; + private VCConfiguration config; + private VCDatesTableDataModel tableData; + private List<MeetingDate> dateList = new ArrayList<MeetingDate>(); + private MeetingDate meeting; + + public VCRunController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, VCConfiguration config, VCProvider provider, boolean isModerator) { + super(ureq, wControl); + this.provider = provider; + this.config = config; + + if(this.config.getMeetingDates() != null) dateList.addAll(this.config.getMeetingDates()); + + // select actual meeting + if(config.isUseMeetingDates()) { + Date now = new Date((new Date()).getTime() + 15*60*1000); // allow to start meetings about 15 minutes before begin + for(MeetingDate date : dateList) { + Date begin = date.getBegin(); + Date end = date.getEnd(); + if(now.after(begin) & now.before(end)) { + meeting = date; + } + } + } + + tableData = new VCDatesTableDataModel(dateList); + + TableGuiConfiguration tableConfig = new TableGuiConfiguration(); + tableConfig.setTableEmptyMessage("<b>"+translate("vc.table.empty")+"</b>"); + tableConfig.setColumnMovingOffered(true); + tableConfig.setSortingEnabled(true); + tableCtr = new TableController(tableConfig, ureq, wControl, getTranslator()); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("vc.table.title", 0, null, ureq.getLocale())); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("vc.table.description", 1, null, ureq.getLocale())); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("vc.table.begin", 2, null, ureq.getLocale())); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("vc.table.end", 3, null, ureq.getLocale())); + tableCtr.setTableDataModel(tableData); + tableCtr.setSortColumn(2, true);// timeframe + listenTo(tableCtr); + + runVC = createVelocityContainer("run"); + runVC.put("datesTable", tableCtr.getInitialComponent()); + + runVC.contextPut("isModerator", isModerator); + boolean isUseDates = config.isUseMeetingDates(); + runVC.contextPut("useDates", isUseDates); + boolean isMeeting = !isUseDates | meeting != null; + runVC.contextPut("isMeeting", isMeeting); + boolean show = isModerator | (isUseDates & isMeeting) | !isUseDates; + runVC.contextPut("show", show); + + displayCtr = this.provider.createDisplayController(ureq, wControl, roomId, name, description, isModerator, config); + runVC.put("displayCtr", displayCtr.getInitialComponent()); + + putInitialPanel(runVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // nothing to do + } + + @Override + protected void doDispose() { + if(tableCtr != null) { + removeAsListenerAndDispose(tableCtr); + tableCtr = null; + } + if(displayCtr != null) { + removeAsListenerAndDispose(displayCtr); + displayCtr = null; + } + } + +} +//</OLATCE-103> diff --git a/src/main/java/de/bps/course/nodes/vc/VCSelectionForm.java b/src/main/java/de/bps/course/nodes/vc/VCSelectionForm.java new file mode 100644 index 00000000000..40410483ab1 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/VCSelectionForm.java @@ -0,0 +1,88 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc; + +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.SingleSelection; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; + +import de.bps.course.nodes.vc.provider.VCProvider; +import de.bps.course.nodes.vc.provider.VCProviderFactory; + +/** + * + * Description:<br> + * Support selection of a virtual classroom, if there are multiple ones registered. + * + * <P> + * Initial Date: 07.01.2011 <br> + * @author skoeber + */ +public class VCSelectionForm extends FormBasicController { + + private SingleSelection selVC; + private String selectedProvider; + + public VCSelectionForm(UserRequest ureq, WindowControl wControl, String selectedProvider) { + super(ureq, wControl); + this.selectedProvider = selectedProvider; + initForm(flc, this, ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + int num = VCProviderFactory.getProviders().size(); + String[] keys = new String[num]; + String[] vals = new String[num]; + int i = 0; + for(VCProvider provider : VCProviderFactory.getProviders()) { + keys[i] = provider.getProviderId(); + vals[i] = provider.getDisplayName(); + i++; + } + + selVC = uifactory.addDropdownSingleselect("config.select.vc", flc, keys, vals, null); + selVC.select(selectedProvider, true); + selVC.addActionListener(this, FormEvent.ONCHANGE); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == selVC) { + fireEvent(ureq, Event.CHANGED_EVENT); + } + } + + public String getSelectedProvider() { + return selVC.getSelectedKey(); + } + + @Override + protected void formOK(UserRequest ureq) { + // nothing to do + } + + @Override + protected void doDispose() { + // nothing to dispose + } + +} +//<OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_chelp/ced-vc-config.html b/src/main/java/de/bps/course/nodes/vc/_chelp/ced-vc-config.html new file mode 100644 index 00000000000..adc858afa76 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_chelp/ced-vc-config.html @@ -0,0 +1,5 @@ +<!-- <OLATCE-103> --> +<p>$r.translate("chelp.vc1")</p> +<p>$r.translate("chelp.vc2")</p> +<p>$r.translate("chelp.vc3")</p> +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_content/edit.html b/src/main/java/de/bps/course/nodes/vc/_content/edit.html new file mode 100644 index 00000000000..13aac3e5fa5 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_content/edit.html @@ -0,0 +1,14 @@ +<!-- <OLATCE-103> --> +<fieldset> + <legend>$r.translate("config.header")</legend> + $r.contextHelpWithWrapper("de.bps.course.nodes.vc","ced-vc-config.html","chelp.hover.vc.config") + #if($r.available("VCSelectionForm")) + $r.render("VCSelectionForm") + #end + $r.render("editForm") +</fieldset> +<fieldset> + <legend>$r.translate("config.header.extended")</legend> + $r.render("configCtr") +</fieldset> +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_content/editForm.html b/src/main/java/de/bps/course/nodes/vc/_content/editForm.html new file mode 100644 index 00000000000..9e9932a8bfc --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_content/editForm.html @@ -0,0 +1,53 @@ +<!-- <OLATCE-103> --> +#if($hasTemplates) + <strong>$r.translate("vc.template.choose.intro")</strong> + $r.render("vc.template.choose") +#else + <p>$r.translate("vc.template.empty")</p> +#end +<br/> +<strong>$r.translate("vc.options.intro")</strong> +$r.render("vc.options") +<br/> +#if($useDates) + <strong>$r.translate("vc.meetings.intro")</strong> + <table> + <tr> + <td>$r.translate("vc.table.title")</td> + <td>$r.translate("vc.table.description")</td> + <td>$r.translate("vc.table.begin")</td> + <td colspan="3">$r.translate("vc.table.duration")</td> + </tr> + + #foreach($date in $dateList) + #set( $iter = $velocityCount - 1) + #set( $title = $vcTitleInputList.get($iter).getName() ) + #set( $titleErr = $title + "_ERROR" ) + #set( $descr = $vcDescriptionInputList.get($iter).getName() ) + #set( $descrErr = $descr + "_ERROR" ) + #set( $begin = $vcCalenderbeginInputList.get($iter).getName() ) + #set( $beginErr = $begin + "_ERROR" ) + #set( $duration = $vcDurationInputList.get($iter).getName() ) + #set( $durationErr = $duration + "_ERROR" ) + <tr> + <td><div class="b_form_error">#if($f.hasError($title))$r.render($titleErr)#end</div></td> + <td><div class="b_form_error">#if($f.hasError($descr))$r.render($descrErr)#end</div></td> + <td><div class="b_form_error">#if($f.hasError($begin))$r.render($beginErr)#end</div></td> + <td><div class="b_form_error">#if($f.hasError($duration))$r.render($durationErr)#end</div></td> + <td></td> + <td></td> + </tr> + <tr> + <td>$r.render($title)</td> + <td>$r.render($descr)</td> + <td>$r.render($begin)</td> + <td>$r.render($duration)</td> + <td style="white-space: nowrap;"> + $r.render($vcAddButtonList.get($iter).getName()) + $r.render($vcDelButtonList.get($iter).getName()) + </td> + </tr> + #end + </table> +#end +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_content/run.html b/src/main/java/de/bps/course/nodes/vc/_content/run.html new file mode 100644 index 00000000000..2cb55c0f24f --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_content/run.html @@ -0,0 +1,22 @@ +<!-- <OLATCE-103> --> +#if($useDates) + <p>$r.translate("vc.table.intro")</p> + $r.render("datesTable") + <br/> +#end + +#if($isModerator) + #if(!$isMeeting) + <p>$r.translate("no.meeting.moderator")</p> + #end +#else + #if(!$show) + <p>$r.translate("no.meeting.learner")</p> + #end +#end + +#if($show) + $r.render("displayCtr") +#end + +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_de.properties b/src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_de.properties new file mode 100644 index 00000000000..a3faa4c8fe7 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_de.properties @@ -0,0 +1,54 @@ +#<OLATCE-103> +pane.tab.accessibility=Zugang +pane.tab.vcconfig=Konfiguration +condition.accessibility.title=Zugang +title_vc=Virtuelles Klassenzimmer +config.header=Konfiguration des virtuellen Klassenzimmers +config.header.extended=Erweiterte Konfiguration +config.select.vc=System f\u00fcr virtuellen Klassenraum +error.config.short=Konfiguration von "{0}" fehlerhaft +error.config.long=Die Konfiguration des Kursbausteins "{0}" ist fehlerhaft oder unvollst\u00e4ndig. + +error.update.room=Das virtuelle Klassenzimmer konnte nicht mit den aktuellen Einstellungen synchronisiert werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. + +delete.meeting.title=Virtuelles Klassenzimmers l\u00f6schen +delete.meeting.text=Sie haben ein anderes System f\u00fcr das virtuelle Klassenzimmer ausgew\u00e4hlt. Das bestehende Virtuelle Klassenzimmer wird bei dieser \u00c4nderung gel\u00f6scht. Wollen Sie das System wirklich wechseln? + +no.meeting.learner=Derzeit findet kein Meeting statt, dem Sie beitreten k\u00f6nnten. +no.meeting.moderator=Derzeit findet kein Meeting statt. Teilnehmer k\u00f6nnen das virtuelle Klassenzimmer 15 Minuten vor einem geplanten Meeting betreten. + +sync.meeting.title=Virtuelles Klassenzimmer synchronisieren +sync.meeting.text=Sie haben die Einstellungen f\u00fcr diese virtuelle Klassenzimmer ge\u00e4ndert, nachdem dieses er\u00f6ffnet wurde. Die \u00c4nderungen werden erst wirksam, wenn Sie das virtuelle Klassenzimmer mit den neuen Einstellungen synchronisieren. Die Einstellungen k\u00f6nnen auch jederzeit direkt im Kurs synchronisiert werden. M\u00f6chten Sie die Einstellungen jetzt synchronisieren? +success.update.room=Das virtuelle Klassenzimmer wurde erfolgreich synchronisiert. Bitte vergessen Sie nicht, die \u00c4nderungen zu publizieren. + +vc.access.dates=Virtuelles Klassenzimmer soll nur zu bestimmten Terminen betreten werden k\u00f6nnen +vc.meetings.intro=Termine f\u00fcr Meetings +vc.table.add=+ +vc.table.begin=Start +vc.table.begin.empty=Datumsangabe fehlt +vc.table.begin.error=Fehlerhafte Eingabe +vc.table.description=Beschreibung +vc.table.description.empty=Beschreibung fehlt +vc.table.delete=- +vc.table.duration=Dauer +vc.table.duration.empty=Dauer fehlt +vc.table.duration.error=Falsches Format +vc.table.duration.example=hh\:mm +vc.table.empty=Derzeit sind keine Meetings geplant. +vc.table.end=Ende +vc.table.intro=Hier finden Sie eine Liste aller geplanten Meetings: +vc.table.title=Titel +vc.table.title.empty=Titel fehlt +vc.template.choose.intro=Vorlage f\u00fcr virtuelles Klassenzimmer +vc.template.choose.label=Vorlage +vc.template.empty=Es stehen keine Templates zur Verf\u00fcgung +vc.options=Zutrittsberechtigung +vc.options.intro=Zutrittsberechtigung +vc.options.label=Zutrittsberechtigung + +chelp.ced-vc-config.title=Virtuelles Klassenzimmer konfigurieren +chelp.hover.vc.config=Hilfe zur Konfiguration des Virtuellen Klassenzimmers +chelp.vc1=Richten Sie ein virtuelles Klassenzimmer zur Online-Kommunikation (Pr\u00e4sentationen, Diskussionen, Whiteboard, Desktop-Sharing uvm.) mit Ihren Kurs-Teilnehmern ein. Jedem Kursbaustein wird genau ein virtuelles Klassenzimmer zugeordnet. W\u00e4hlen Sie Vorkonfigurationen f\u00fcr diesen Raum und definieren Sie ggf., zu welchen Terminen der Raum f\u00fcr Nutzer betretbar ist. Um Ressourcen zu sparen, wird ein Raum nicht automatisch mit Erstellen des Kursbausteins er\u00f6ffnet, sondern muss je nach Konfiguration (siehe <i>Zutrittsberechtigung</i>) vor dem ersten Betreten durch einen Moderator oder Teilnehmer <i>er\u00f6ffnet</i> werden. +chelp.vc2=<b>Vorlage f\u00fcr virtuelles Klassenzimmer:</b> Die jeweiligen Vorlagen bringen unterschiedliche Vorkonfigurationen f\u00fcr den Raum mit sich. +chelp.vc3=<b>Termine f\u00fcr Meetings:</b> Definieren Sie einen oder mehrere Termine, zu denen der Raum von Teilnehmern betreten werden kann. +#</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_en.properties b/src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_en.properties new file mode 100644 index 00000000000..ac66b5cc024 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_i18n/LocalStrings_en.properties @@ -0,0 +1,54 @@ +#<OLATCE-103 OLATCE-619> +pane.tab.accessibility=Access +pane.tab.vcconfig=Configuration +condition.accessibility.title=Access +title_vc=Virtual classroom +config.header=Configuration of the virtual classroom +config.header.extended=Extended configuration +config.select.vc=System for virtual classroom +error.config.short=Configuration of "{0}" contains errors +error.config.long=The configuration of the course node "{0}" contains errors or is incomplete. + +error.update.room=Sorry! An error occured while synchronizing the virtual class room with the actual configuration. Please, inform your tutor or system administrator. + +delete.meeting.title=Delete virtual classroom +delete.meeting.text=You have changed the system for your virtual classroom. The existing classroom will thus be deleted. Do you really want to delete the classroom? + +no.meeting.learner=Currently, there are no meetings planned. +no.meeting.moderator=Currently, there are no meetings planned. Participants may enter the virtual classroom 15 minutes prior to meeting start. + +sync.meeting.title=Synchronize virtual classroom +sync.meeting.text=You have changed the configuration of the virtual classroom after it had been started. The changes will be taken into account only after having synchronized the virtual class room with the new configuration. You can synchronize the configuration at any time directly from the course run. Do you want to synchronize now? +success.update.room=The virtual classroom has been synchronized successfully. Do not forget to publish the current changes. + +vc.access.dates=Virtual classroom shall only be available at defined dates +vc.meetings.intro=Planned dates for meetings +vc.table.add=+ +vc.table.begin=Begin +vc.table.begin.empty=Missing date +vc.table.begin.error=Wrong format +vc.table.description=Description +vc.table.description.empty=Missing description +vc.table.delete=- +vc.table.duration=Duration +vc.table.duration.empty=Missing duration +vc.table.duration.error=Wrong format +vc.table.duration.example=hh\:mm +vc.table.empty=There are no meetings planned, yet. +vc.table.end=End +vc.table.intro=List of currently planned meetings: +vc.table.title=Title +vc.table.title.empty=Missing title +vc.template.choose.intro=Template for virtual classroom +vc.template.choose.label=Template +vc.template.empty=No templates available +vc.options=Access authorisation +vc.options.intro=Access authorisation +vc.options.label=Access authorisation + +chelp.ced-vc-config.title=Configure virtual classroom +chelp.hover.vc.config=Help about configuration of the virtual classroom +chelp.vc1=Create a virtual classroom for online communication (presentations, discussions, whiteboard, desktop-sharing etc.) with your course participants. Every course element is linked to exactly one virtual classroom. Choose pre-configurations and define dates for meetings. The classroom will not be automatically opened when creating the course element, but refers to its configuration and has to be opened by the moderator or participant . +chelp.vc2=<b>Template for virtual classroom:</b> The given templates result in different pre-configurations for the classroom. +chelp.vc3=<b>Dates for meetings:</b> Define a random number of dates when the classroom shall be opened for meetings and can be entered by participants. +#</OLATCE-103 OLATCE-619> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_spring/buildingblockContext.xml b/src/main/java/de/bps/course/nodes/vc/_spring/buildingblockContext.xml new file mode 100644 index 00000000000..3dc11d6eb8d --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_spring/buildingblockContext.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + + <context:property-placeholder location="classpath:serviceconfig/olat.properties, classpath:olat.local.properties" /> + + <bean id="vc" class="de.bps.course.nodes.vc.VCCourseNodeConfiguration" scope="prototype"> + <property name="enabled" value="${course.node.vc.enabled}" /> + <property name="order" value="300" /> + </bean> + +</beans> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/_spring/vcContext.xml b/src/main/java/de/bps/course/nodes/vc/_spring/vcContext.xml new file mode 100644 index 00000000000..e2e9a294ee6 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/_spring/vcContext.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + +<context:property-placeholder location="classpath:serviceconfig/olat.properties, classpath:olat.local.properties" /> +<context:annotation-config /> + +<!-- ####################################### --> +<!-- # VIRTUAL CLASSROOM SERVICE PROVIDERS # --> +<!-- ####################################### --> + + <!-- Activation of the providers --> + <bean id="vcProviderFactory" class="de.bps.course.nodes.vc.provider.VCProviderFactory" scope="singleton"> + <property name="registeredProviders"> + <list value-type="de.bps.course.nodes.vc.provider.VCProvider"> + <ref bean="adobeProvider" /> + <ref bean="wimbaProvider" /> + <ref bean="viteroProvider" /> + </list> + </property> + </bean> + + <!-- Definition of the providers --> + <bean id="wimbaProvider" class="de.bps.course.nodes.vc.provider.wimba.WimbaClassroomProvider"> + <property name="enabled" value="${vc.winba.enabled}" /> + <property name="providerId" value="wimba" /> + <property name="displayName" value="Wimba Classroom" /> + <property name="protocol" value="${vc.winba.protocol}" /> + <property name="port" value="${vc.winba.port}" /> + <property name="baseUrl" value="${vc.winba.baseurl}" /> + <property name="adminLogin" value="${vc.winba.adminlogin}" /> + <property name="adminPassword" value="${vc.winba.adminpassword}" /> + <!-- Options --> + <property name="defaultConfig"> + <ref bean="wimbaDefaultConfig" /> + </property> + </bean> + + <bean id="adobeProvider" class="de.bps.course.nodes.vc.provider.adobe.AdobeConnectProvider"> + <property name="enabled" value="${vc.adobe.enabled}" /> + <property name="providerId" value="adobe" /> + <property name="displayName" value="Adobe Connect" /> + <property name="protocol" value="${vc.adobe.protocol}" /> + <property name="port" value="${vc.adobe.port}" /> + <property name="baseUrl" value="${vc.adobe.baseurl}" /> + <property name="adminLogin" value="${vc.adobe.adminlogin}" /> + <property name="adminPassword" value="${vc.adobe.adminpassword}" /> + <property name="accountId" value="${vc.adobe.accountid}" /> + <property name="templates"><!-- optional --> + <map key-type="java.lang.String" value-type="java.lang.String"> + <entry value="Standardmeetingvorlage" key="4711" /> + </map> + </property> + <property name="showOptions" value="${vc.adobe.showoptions}" /> + <!-- type of user accounts, allowed values are: guest, user --> + <property name="userType" value="${vc.adobe.usertype}" /> + <!-- Options --> + <property name="defaultConfig"> + <ref bean="adobeDefaultConfig" /> + </property> + </bean> + +<!-- ################################## --> +<!-- # VIRTUAL CLASSROOM SERVICE JOBS # --> +<!-- ################################## --> + + <!-- Activation of the service jobs --> + <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> + <property name="triggers"> + <list> + <!-- <ref local="adobeCleanupJob" /> --> + </list> + </property> + </bean> + + <!-- Definition of the service jobs --> + <bean id="adobeCleanupJob" class="org.springframework.scheduling.quartz.CronTriggerBean"> + <property name="jobDetail"> + <bean class="org.springframework.scheduling.quartz.JobDetailBean"> + <property name="jobClass" value="de.bps.course.nodes.vc.provider.adobe.AdobeConnectCleanupJob" /> + <property name="jobDataAsMap"> + <map> + <entry key="providerId" value="adobe" /> + <entry key="daysToKeep" value="1" /> + <entry key="cleanupMeetings" value="true" /> + <entry key="cleanupModerators" value="false" /> + </map> + </property> + </bean> + </property> + <property name="cronExpression" value="0 0 2 * * ?"/><!-- 2am, daily --> + </bean> + +</beans> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/VCProvider.java b/src/main/java/de/bps/course/nodes/vc/provider/VCProvider.java new file mode 100644 index 00000000000..e540693c1ad --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/VCProvider.java @@ -0,0 +1,191 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider; + +import java.net.URL; +import java.util.Date; +import java.util.Map; + +import org.olat.core.configuration.ConfigOnOff; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.Identity; + +import de.bps.course.nodes.vc.VCConfiguration; + +/** + * + * Description:<br> + * Interface defines the API a virtual classroom provider has to provide. It is designed + * to have an own instance for every single user. + * + * <P> + * Initial Date: 09.12.2010 <br> + * @author skoeber + */ +public interface VCProvider extends ConfigOnOff { + + /** + * @return new independent instance of the provider + */ + public VCProvider newInstance(); + + /** + * @return provider id as defined in the configuration + */ + public String getProviderId(); + + /** + * @return display name of the provider + */ + public String getDisplayName(); + + /** + * @return mapping of key and displayname for available templates or empty map, but never <code>null</code> + */ + public Map<String, String> getTemplates(); + + /** + * @return <code>true</code> if virtual classroom is available at present, <code>false</code> otherwise + */ + public boolean isProviderAvailable(); + + /** + * Create a new virtual classroom to be used e.g. in a course node + * @param roomId (maybe prefixed automatically) + * @param name (optional name for meeting) + * @param description (optional description for meeting) + * @param begin (usage dependent on target platform, can be NULL) + * @param end (usage dependent on target platform, can be NULL) + * @param templateId + * @return success + */ + public boolean createClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config); + + /** + * Update an existing virtual classroom + * @param roomId of the existing classroom + * @param name the new name + * @param description the new description + * @param begin the new begin + * @param end the new end + * @param config the new configuration + * @return success + */ + public boolean updateClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config); + + /** + * Delete an existing virtual classroom + * @param roomId + * @param config + * @return success + */ + public boolean removeClassroom(String roomId, VCConfiguration config); + + /** + * Create user specific access url for the virtual classroom. Maybe the user + * must authenticated before. + * @param roomId + * @param identity + * @param config + * @return url + */ + public URL createClassroomUrl(String roomId, Identity identity, VCConfiguration config); + + /** + * Create guest access url for the virtual classroom. Dependent on the + * implementation the url can be user specific (e.g. to pre-set the username) + * @param roomId + * @param identity + * @param config + * @return url + */ + public URL createClassroomGuestUrl(String roomId, Identity identity, VCConfiguration config); + + /** + * Check whether the virtual classroom exists or not. + * @param roomId + * @param config + * @return <code>true</code> if the classroom exists, <code>false</code> otherwise + */ + public boolean existsClassroom(String roomId, VCConfiguration config); + + /** + * Login the user. Dependent on the implemenation the password can be <code>null</code>. + * If this is the case, the implementation can try to login automatically generated + * users with a default password or a password that's build up by a rule. + * @param identity + * @param password + * @return success + */ + public boolean login(Identity identity, String password); + + /** + * Create a new user. The user has moderator rights. If the user already exists, + * nothing is to do. + * @param identity + * @param roomId + * @return success + */ + public boolean createModerator(Identity identity, String roomId); + + /** + * Create a new user. The user has no specific rights. If the user already exists, + * nothing is to do. + * @param identity + * @param roomId + * @return success + */ + public boolean createUser(Identity identity, String roomId); + + /** + * Create a new guest. Dependent on the implementation the user must not be persistent. + * @param identity + * @param roomId + * @return success + */ + public boolean createGuest(Identity identity, String roomId); + + /** + * Create controller for using the virtual classroom. + * @param ureq + * @param wControl + * @param roomId + * @param name + * @param description + * @param isModerator + * @param config + * @return the controller to be embedded + */ + public Controller createDisplayController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, boolean isModerator, VCConfiguration config); + + /** + * Create controller for creation and configuration of the virtual classroom. + * @param ureq + * @param wControl + * @param roomId + * @param config + * @return the controller to be embedded + */ + public Controller createConfigController(UserRequest ureq, WindowControl wControl, String roomId, VCConfiguration config); + + /** + * Create a new default configuration. This configuration must reflect all + * necessary settings to ensure that the virtual classroom will work. + * @return new configuration + */ + public VCConfiguration createNewConfiguration(); +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/VCProviderFactory.java b/src/main/java/de/bps/course/nodes/vc/provider/VCProviderFactory.java new file mode 100644 index 00000000000..6984869bf37 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/VCProviderFactory.java @@ -0,0 +1,81 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * Description:<br> + * Factory to create an instance of a registered virtual classroom provider. Note that + * the providers are designed to be used as own instances for every single user. + * + * <P> + * Initial Date: 09.12.2010 <br> + * @author skoeber + */ +public class VCProviderFactory { + + public static String VC_PROVIDER = "vcProvider"; + + private final static Map<String, VCProvider> _registeredProviders = new HashMap<String, VCProvider>(); + + public static VCProvider createProvider(String providerId) { + return _registeredProviders.get(providerId).newInstance(); + } + + public static VCProvider createDefaultProvider() { + if(_registeredProviders == null || _registeredProviders.isEmpty()) { + return null; + } + String providerId = _registeredProviders.keySet().iterator().next(); + return createProvider(providerId); + } + + public static boolean existsProvider(String providerId) { + return _registeredProviders.containsKey(providerId); + } + + public static void registerProvider(VCProvider provider) { + _registeredProviders.put(provider.getProviderId(), provider); + } + + public static void setRegisteredProviders(List<VCProvider> providers) { + for(VCProvider provider : providers) { + registerProvider(provider); + } + } + + public static List<VCProvider> getProviders() { + List<VCProvider> providers = new ArrayList<VCProvider>(); + for(VCProvider provider:_registeredProviders.values()) { + if(provider.isEnabled()) { + providers.add(provider); + } + } + return providers; + } + + /** used by spring */ + public List<VCProvider> getRegisteredProviders() { + List<VCProvider> providers = new ArrayList<VCProvider>(); + providers.addAll(_registeredProviders.values()); + return providers; + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConfigController.java b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConfigController.java new file mode 100644 index 00000000000..258ada4fa91 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConfigController.java @@ -0,0 +1,71 @@ +// <OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.adobe; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; + +/** + * + * Description:<br> + * Config controller for Adobe Connect implementation + * + * <P> + * Initial Date: 05.01.2011 <br> + * @author skoeber + */ +public class AdobeConfigController extends BasicController { + + private VelocityContainer editVC; + private AdobeEditForm editForm; + + protected AdobeConfigController(UserRequest ureq, WindowControl wControl, String roomId, AdobeConnectProvider adobe, AdobeConnectConfiguration config) { + super(ureq, wControl); + + this.editForm = new AdobeEditForm(ureq, wControl, adobe.isShowOptions(), config); + listenTo(editForm); + + editVC = createVelocityContainer("edit"); + editVC.put("editForm", editForm.getInitialComponent()); + + putInitialPanel(editVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // nothing to do + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == editForm) { + fireEvent(ureq, event); + } + } + + @Override + protected void doDispose() { + if (editForm != null) { + removeAsListenerAndDispose(editForm); + editForm = null; + } + } + +} +// </OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectCleanupJob.java b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectCleanupJob.java new file mode 100644 index 00000000000..c399f2a5487 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectCleanupJob.java @@ -0,0 +1,171 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.adobe; + +import java.util.Date; +import java.util.List; + +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.nodes.CourseNode; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.scheduling.quartz.QuartzJobBean; + +import de.bps.course.nodes.VCCourseNode; +import de.bps.course.nodes.vc.MeetingDate; +import de.bps.course.nodes.vc.provider.VCProviderFactory; + +/** + * + * Description:<br> + * Cleanup unused Adobe Connect ressources:<br/> + * - unused meetings<br/> + * - temporary guest users + * + * <P> + * Initial Date: 04.01.2011 <br> + * @author skoeber + */ +public class AdobeConnectCleanupJob extends QuartzJobBean { + + private OLog logger = Tracing.createLoggerFor(AdobeConnectCleanupJob.class); + + private String providerId; + private boolean cleanupMeetings, cleanupModerators; + private int daysToKeep; + + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + AdobeConnectProvider adobe = null; + + boolean success = VCProviderFactory.existsProvider(providerId); + if(!success) { + throw new JobExecutionException("Invalid configuration: defined a virtual classroom cleanup job for non existing provider \"" + providerId + "\""); + } + + try { + adobe = (AdobeConnectProvider) VCProviderFactory.createProvider(providerId); + } catch(ClassCastException e) { + throw new JobExecutionException("Invalid configuration: defined a virtual classroom cleanup job and provider implementation doesn't fit"); + } + + success = adobe.isProviderAvailable(); + if(!success) { + logger.error("Tried to cleanup Adobe Connect meetings but it's actually not available"); + return; + } + + /* + * the concrete jobs + */ + // cleanup unused meetings + if(cleanupMeetings) { + logger.info("Start cleaning unused Adobe Connect meetings"); + cleanupMeetings(adobe, daysToKeep); + } + // cleanup unused moderator guest accounts + if(cleanupModerators) { + logger.info("Start cleaning unused Adobe Connect moderator guest accounts"); + cleanupModerators(adobe); + } + } + + /** + * @param adobe + */ + protected void cleanupMeetings(AdobeConnectProvider adobe, int daysToKeep) { + boolean success = false; + + Date lowerLimit = new Date((new Date()).getTime() - (daysToKeep * 24*60*60*1000)); + + // search all virtual classrooms with the used prefix + List<String> roomIds = adobe.findClassrooms(AdobeConnectProvider.PREFIX); + for(String roomId : roomIds) { + // format is olat-courseId-nodeId, e.g. olat-82823405537032-82823405537043 + // load course and node + String courseId = roomId.split("-")[1]; + String nodeId = roomId.split("-")[2]; + ICourse course = CourseFactory.loadCourse(Long.parseLong(courseId)); + CourseNode vcNode = course.getRunStructure().getNode(nodeId); + if(!(vcNode instanceof VCCourseNode)) { + logger.warn("Tried to cleanup Adobe Connect meeting for a non Adobe Connect course node: " + roomId); + continue; + } + AdobeConnectConfiguration config = (AdobeConnectConfiguration) vcNode.getModuleConfiguration().get(VCCourseNode.CONF_VC_CONFIGURATION); + if(config == null) { + // invalid configuration, do nothing and continue + continue; + } + + boolean keep = false; + for(MeetingDate date : config.getMeetingDates()) { + if(keep) continue; + Date end = date.getEnd(); + keep = lowerLimit.before(end); + } + + // no planned date in the future, we can delete + // build the correct roomId + String toDelete = courseId + "-" + nodeId; + if(!keep) success = adobe.removeClassroom(toDelete, config); + + if(!success) { + logger.warn("Error when cleaning up Adobe Connect meeting \"" + roomId + "\""); + continue; + } + } + } + + protected void cleanupModerators(AdobeConnectProvider adobe) { +// boolean success = false; +// TODO implement + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public int getDaysToKeep() { + return daysToKeep; + } + + public void setDaysToKeep(int daysToKeep) { + this.daysToKeep = daysToKeep; + } + + public void setCleanupMeetings(boolean cleanupMeetings) { + this.cleanupMeetings = cleanupMeetings; + } + + public boolean isCleanupMeetings() { + return cleanupMeetings; + } + + public void setCleanupModerators(boolean cleanupModerators) { + this.cleanupModerators = cleanupModerators; + } + + public boolean isCleanupModerators() { + return cleanupModerators; + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectConfiguration.java b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectConfiguration.java new file mode 100644 index 00000000000..ecccfed3f48 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectConfiguration.java @@ -0,0 +1,57 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.adobe; + +import java.io.Serializable; + +import de.bps.course.nodes.vc.DefaultVCConfiguration; + +/** + * + * Description:<br> + * Configuration object for Adobe Connect + * + * <P> + * Initial Date: 20.12.2010 <br> + * @author skoeber + */ +public class AdobeConnectConfiguration extends DefaultVCConfiguration implements Serializable { + + private boolean guestAccessAllowed; + private boolean guestStartMeetingAllowed; + + public boolean isGuestAccessAllowed() { + return guestAccessAllowed; + } + public void setGuestAccessAllowed(boolean guestAccessAllowed) { + this.guestAccessAllowed = guestAccessAllowed; + } + public boolean isGuestStartMeetingAllowed() { + return guestStartMeetingAllowed; + } + public void setGuestStartMeetingAllowed(boolean guestStartMeetingAllowed) { + this.guestStartMeetingAllowed = guestStartMeetingAllowed; + } + + @Override + public boolean isConfigValid() { + boolean valid = true; + if(isUseMeetingDates()) { + valid = getMeetingDates() != null && !getMeetingDates().isEmpty(); + } + return valid; + } + +} +//</OLATCE-103> diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectProvider.java b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectProvider.java new file mode 100644 index 00000000000..89b6ba1c8e5 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeConnectProvider.java @@ -0,0 +1,773 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.adobe; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.UriBuilder; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.lang.NotImplementedException; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.helpers.Settings; +import org.olat.core.id.Identity; +import org.olat.core.id.UserConstants; +import org.olat.core.logging.AssertException; +import org.olat.core.logging.LogDelegator; +import org.olat.core.util.Encoder; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import de.bps.course.nodes.vc.VCConfiguration; +import de.bps.course.nodes.vc.provider.VCProvider; + +/** + * + * Description:<br> + * Virtual classroom provider for Adobe Connect. + * + * <P> + * Initial Date: 09.12.2010 <br> + * @author skoeber + */ +public class AdobeConnectProvider extends LogDelegator implements VCProvider { + + private static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; + private static final String COOKIE = "BREEZESESSION="; + protected static final String PREFIX = "olat-"; + private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm"; + + protected static String DEFAULT_TEMPLATE = "default"; + + // configuration + private static AdobeConnectConfiguration defaultConfig; + private boolean enabled; + private String providerId; + private String displayName; + private String protocol; + private int port; + private String baseUrl; + private String adminLogin; + private String adminPassword; + private String accountId; + private Map<String, String> templates; + private boolean guestAccessAllowedDefault; + private boolean guestStartMeetingAllowedDefault; + private boolean useMeetingDatesDefault; + private boolean showOptions; + private String userType; + + // runtime data + private String cookie; + + /** + * Constructor for internal use to create new instance + * @param providerId + * @param protocol + * @param port + * @param baseUrl + * @param adminLogin + * @param adminPassword + */ + private AdobeConnectProvider(String providerId, String displayName, String protocol, int port, String baseUrl, String adminLogin, String adminPassword, + String accountId, Map<String, String> templates, boolean guestAccessAllowedDefault, boolean guestStartMeetingAllowedDefault, + boolean showOptions, String userType) { + setProviderId(providerId); + setDisplayName(displayName); + setProtocol(protocol); + setPort(port); + setBaseUrl(baseUrl); + setAdminLogin(adminLogin); + setAdminPassword(adminPassword); + setAccountId(accountId); + setTemplates(templates); + setGuestAccessAllowedDefault(guestAccessAllowedDefault); + setGuestStartMeetingAllowedDefault(guestStartMeetingAllowedDefault); + setShowOptions(showOptions); + setUserType(userType); + } + + /** + * Public constructor, mostly used by spring<br/> + * <b>Important</b> when using: set configuration manually! + */ + public AdobeConnectProvider() { + // + } + + @Override + public boolean createClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config) { + if(existsClassroom(roomId, config)) return true; + + if(!loginAdmin()) throw new AssertException("Cannot login to Adobe Connect. Please check module configuration and Adobe Connect connectivity."); + + // begin and end can be NULL, see interface description + if(begin == null) begin = new Date(); + if(end == null) end = new Date(begin.getTime() + 365*24*60*60*1000); // preset one year + + // formatter for begin and end + SimpleDateFormat sd = new SimpleDateFormat(DATE_FORMAT); + + // find my-meetings + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "sco-shortcuts"); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return false; + + String folderScoId = null; + // use my-meetings folder + String result = evaluate(responseDoc, "//sco[@type=\"my-meetings\"]"); + if(result != null && !result.isEmpty()) { + folderScoId = evaluate(responseDoc, "//sco[@type=\"my-meetings\"]/attribute::sco-id"); + } + // my-meetings folder not found, fallback to meetings + if(folderScoId == null) { + result = evaluate(responseDoc, "//sco[@type=\"meetings\"]"); + if(result != null && !result.isEmpty()) { + folderScoId = evaluate(responseDoc, "//sco[@type=\"meetings\"]/attribute::sco-id"); + } + } + // meetings folder not found, error case + if(folderScoId == null) return false; + // folder found where to insert the new meeting + // create new meeting + parameters = new HashMap<String, String>(); + parameters.put("action", "sco-update"); + parameters.put("type", "meeting"); + parameters.put("name", PREFIX + roomId); + parameters.put("folder-id", folderScoId); + parameters.put("date-begin", sd.format(begin)); + parameters.put("date-end", sd.format(end)); + parameters.put("url-path", PREFIX + roomId); + String templateId = ((AdobeConnectConfiguration)config).getTemplateKey(); + if(templateId != null && !templateId.equals(DEFAULT_TEMPLATE)) + parameters.put("source-sco-id", templateId); + responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return false; + + // adjust permissions + String meetingScoId = evaluate(responseDoc, "//sco/attribute::sco-id"); + parameters.clear(); + parameters.put("action", "permissions-update"); + parameters.put("acl-id", meetingScoId); + parameters.put("principal-id", "public-access"); + if(((AdobeConnectConfiguration)config).isGuestAccessAllowed()) + parameters.put("permission-id", "view-hidden"); + else + parameters.put("permission-id", "remove"); + responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return false; + + logout(); + return true; + } + + @Override + public boolean updateClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config) { + if(!existsClassroom(roomId, config)) return false; + if(!loginAdmin()) throw new AssertException("Cannot login to Adobe Connect. Please check module configuration and that Adobe Connect is available."); + + String scoId = getScoIdFor(roomId); + if(scoId == null) return false; + + // formatter for begin and end + SimpleDateFormat sd = new SimpleDateFormat(DATE_FORMAT); + + Map<String, String> parameters = new HashMap<String, String>(); + // update meeting configuration + parameters.put("action", "sco-update"); + parameters.put("sco-id", scoId); + if(begin != null) + parameters.put("date-begin", sd.format(begin)); + if(end != null) + parameters.put("date-end", sd.format(end)); + String templateId = ((AdobeConnectConfiguration)config).getTemplateKey(); + if(templateId != null && !templateId.equals(DEFAULT_TEMPLATE)) + parameters.put("source-sco-id", templateId); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return false; + + // adjust permissions + parameters.clear(); + parameters.put("action", "permissions-update"); + parameters.put("acl-id", scoId); + parameters.put("principal-id", "public-access"); + if(((AdobeConnectConfiguration)config).isGuestAccessAllowed()) + parameters.put("permission-id", "view-hidden"); + else + parameters.put("permission-id", "remove"); + responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return false; + + logout(); + return true; + } + + @Override + public boolean removeClassroom(String roomId, VCConfiguration config) { + if(!existsClassroom(roomId, config)) return true; + if(!loginAdmin()) throw new AssertException("Cannot login to Adobe Connect. Please check module configuration and that Adobe Connect is available."); + + String scoId = getScoIdFor(roomId); + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "sco-delete"); + parameters.put("sco-id", scoId); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return false; + + logout(); + return true; + } + + private String getScoIdFor(String roomId) { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "sco-search-by-field"); + parameters.put("query", PREFIX + roomId); + parameters.put("filter-type", "meeting"); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return null; + + Object result = evaluate(responseDoc, "//sco/url-path[text()='/" + PREFIX + roomId + "/']", XPathConstants.NODESET); + if(result == null) return null; + NodeList nodes = (NodeList) result; + if(nodes.getLength() == 1) { + String scoId = evaluate(responseDoc, "//sco[1]/attribute::sco-id"); + return scoId; + } + else if(nodes.getLength() > 1) + throw new AssertException("More than one Adobe Connect room found for one course node!"); + else + return null; + } + + @Override + public URL createClassroomUrl(String roomId, Identity identity, VCConfiguration config) { + URL url = null; + URI uri = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port) + .path(PREFIX + roomId).queryParam("session", cookie).build(); + try { + url = uri.toURL(); + } catch (MalformedURLException e) { + logWarn("Cannot create access URL to Adobe Connect meeting for id \"" + PREFIX + roomId + "\" and user \"" + identity.getName() + "\"", e); + } + return url; + } + + @Override + public URL createClassroomGuestUrl(String roomId, Identity identity, VCConfiguration config) { + URL url = null; + URI uri = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port) + .path(PREFIX + roomId).queryParam("guestName", identity.getName()).build(); + try { + url = uri.toURL(); + } catch (MalformedURLException e) { + logWarn("Cannot create access URL to Adobe Connect meeting for id \"" + PREFIX + roomId + "\" and user \"" + identity.getName() + "\"", e); + } + return url; + } + + @Override + public boolean existsClassroom(String roomId, VCConfiguration config) { + if(!loginAdmin()) return false; + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "sco-search-by-field"); + parameters.put("query", PREFIX + roomId); + parameters.put("filter-type", "meeting"); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) return false; + + Object result = evaluate(responseDoc, "//sco/url-path[text()='/" + PREFIX + roomId + "/']", XPathConstants.NODESET); + logout(); + if(result == null) return false; + NodeList nodes = (NodeList) result; + if(nodes.getLength() == 1) + return true; + else if(nodes.getLength() > 1) + throw new AssertException("More than one Adobe Connect room found for one course node!"); + else + return false; + } + + protected List<String> findClassrooms(String name) { + List<String> results = new ArrayList<String>(); + if(!loginAdmin()) return results; + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "sco-search-by-field"); + parameters.put("field", "name"); + parameters.put("query", name); + parameters.put("filter-type", "meeting"); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + if(!evaluateOk(responseDoc)) { + logError("Invalid response when searching for classrooms with the name \"" + name + "\"", null); + return results; + } + + Object result = evaluate(responseDoc, "descendant-or-self::sco-search-by-field-info/child::sco/child::name", XPathConstants.NODESET); + logout(); + if(result == null) if(isLogDebugEnabled()) logDebug("Search for Adobe Connect classrooms with name \"" + name + "\" with no results"); + NodeList nodes = (NodeList) result; + for(int i=0; i<nodes.getLength(); i++) { + Node node = nodes.item(i); + String roomId = node.getFirstChild().getNodeValue(); + results.add(roomId); + } + + return results; + } + + @Override + public boolean createModerator(Identity identity, String roomId) { + if(!loginAdmin()) return false; + Map<String, String> parameters = new HashMap<String, String>(); + // create user + parameters.put("action", "principal-update"); + parameters.put("first-name", identity.getUser().getProperty(UserConstants.FIRSTNAME, null)); + parameters.put("last-name", identity.getUser().getProperty(UserConstants.LASTNAME, null)); + parameters.put("login", PREFIX + identity.getName()); + parameters.put("password", Encoder.encrypt(identity.getName() + "@" + Settings.getApplicationName())); + parameters.put("type", userType); + parameters.put("has-children", "false"); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + + if(!evaluateOk(responseDoc)) { + boolean exists = false; + String error = evaluate(responseDoc, "/results/status[1]/attribute::code"); + if(error.equals("invalid")) { + error = evaluate(responseDoc, "/results[1]/status[1]/invalid/attribute::subcode"); + exists = error.equals("duplicate"); + } + if(!exists) return false; + } + + // search the user + String principalId = getPrincipalIdFor(identity); + if(principalId == null) return false; // error case + + // create permissions for the meeting + String scoId = getScoIdFor(roomId); + if(scoId == null) return false; + parameters.clear(); + parameters.put("action", "permissions-update"); + parameters.put("acl-id", scoId); + parameters.put("principal-id", principalId); + parameters.put("permission-id", "host"); + String response = sendRequest(parameters); + responseDoc = getResponseDocument(response); + logout(); + + return evaluateOk(responseDoc); + } + + @Override + public boolean createUser(Identity identity, String roomId) { + throw new NotImplementedException("method createUser not yet implemented"); + } + + @Override + public boolean createGuest(Identity identity, String roomId) { + throw new NotImplementedException("method createGuest not yet implemented"); + } + + @Override + public String getProviderId() { + return providerId; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public VCProvider newInstance() { + AdobeConnectProvider newInstance = new AdobeConnectProvider(providerId, displayName, protocol, port, baseUrl, adminLogin, adminPassword, accountId, templates, + guestAccessAllowedDefault, guestStartMeetingAllowedDefault, showOptions, userType); + return newInstance; + } + + @Override + public Controller createDisplayController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, boolean isModerator, VCConfiguration config) { + AdobeDisplayController displayCtr = new AdobeDisplayController(ureq, wControl, roomId, name, description, isModerator, (AdobeConnectConfiguration) config, this); + return displayCtr; + } + + @Override + public Controller createConfigController(UserRequest ureq, WindowControl wControl, String roomId, VCConfiguration config) { + AdobeConfigController configCtr = new AdobeConfigController(ureq, wControl, roomId, this, (AdobeConnectConfiguration) config); + return configCtr; + } + + @Override + public boolean login(Identity identity, String password) { + if(cookie == null) createCookie(); + Map<String,String> parameters = new HashMap<String, String>(); + parameters.put("action", "login"); + if(accountId != null) parameters.put("account-id", accountId); + parameters.put("login", PREFIX + identity.getName()); + parameters.put("password", Encoder.encrypt(identity.getName() + "@" + Settings.getApplicationName())); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + + return evaluateOk(responseDoc); + } + + private boolean loginAdmin() { + if(cookie == null) createCookie(); + Map<String,String> parameters = new HashMap<String, String>(); + parameters.put("action", "login"); + if(accountId != null) parameters.put("account-id", accountId); + parameters.put("login", adminLogin); + parameters.put("password", adminPassword); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + + return evaluateOk(responseDoc); + } + + private boolean logout() { + if(cookie == null) return true; + Map<String,String> parameters = new HashMap<String, String>(); + parameters.put("action", "logout"); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + cookie = null; + + return evaluateOk(responseDoc); + } + + private boolean createCookie() { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "common-info"); + String response = sendRequest(parameters); + + Document responseDoc = getResponseDocument(response); + boolean success = evaluateOk(responseDoc); + if (success) { + // get cookie + String result = evaluate(responseDoc, "/results/common[1]/cookie[1]/text()"); + cookie = result; + } + return success; + } + + private String getPrincipalIdFor(Identity identity) { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "principal-list"); + parameters.put("filter-type", userType); + parameters.put("filter-type", "user"); + parameters.put("filter-login", PREFIX + identity.getName()); + String response = sendRequest(parameters); + + Document responseDoc = getResponseDocument(response); + boolean success = evaluateOk(responseDoc); + if(!success) return null; + // get principalId + NodeList nodes = (NodeList) evaluate(responseDoc, "//principal", XPathConstants.NODESET); + if(nodes == null) return null; // error case + if(nodes.getLength() == 1) { + String principalId = evaluate(responseDoc, "//principal[1]/attribute::principal-id"); + return principalId; + } else if(nodes.getLength() > 1) { + throw new AssertException("Multiple Adobe Connect users with the same login name found: " + identity.getName()); + } else return null; + } + + private Document getResponseDocument(String response) { + DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); + domFactory.setNamespaceAware(true); // never forget this! + DocumentBuilder builder; + Document doc = null; + try { + builder = domFactory.newDocumentBuilder(); + InputSource is = new InputSource(new StringReader(response)); + doc = builder.parse(is); + } catch (ParserConfigurationException e) { + if(isLogDebugEnabled()) logDebug("Error while creating DOM parser."); + } catch (SAXException e) { + if(isLogDebugEnabled()) logDebug("Error while parsing result XML document."); + } catch (IOException e) { + if(isLogDebugEnabled()) logDebug("Error while reading response."); + } + return doc; + } + + private boolean evaluateOk(Document responseDoc) { + String result = evaluate(responseDoc, "/results/status[1]/attribute::code"); + if(result == null || result.isEmpty()) + return false; + return result.equals("ok"); + } + + private String evaluate(Document responseDoc, String expression) { + Object result = evaluate(responseDoc, expression, XPathConstants.STRING); + if(result == null || !(result instanceof String)) + return new String(); + return (String)result; + } + + private Object evaluate(Document responseDoc, String expression, QName type) { + if(responseDoc == null) return null; + XPathFactory factory = XPathFactory.newInstance(); + XPath xpath = factory.newXPath(); + XPathExpression expr; + Object result; + try { + expr = xpath.compile(expression); + result = expr.evaluate(responseDoc, type); + } catch (XPathExpressionException e) { + result = null; + } + + return result; + } + + private String sendRequest(Map<String, String> parameters) { + URL url = createRequestUrl(parameters); + URLConnection urlConn; + + try { + urlConn = url.openConnection(); + // setup url connection + urlConn.setDoOutput(true); + urlConn.setUseCaches(false); + // add content type + urlConn.setRequestProperty("Content-Type", CONTENT_TYPE); + // add cookie information + if(cookie != null) urlConn.setRequestProperty("Cookie", COOKIE + cookie); + + // send request + urlConn.connect(); + + // read response + BufferedReader input = new BufferedReader(new InputStreamReader(urlConn.getInputStream())); + StringBuilder response= new StringBuilder(); + String line; + while( (line = input.readLine()) != null ) response.append(line); + input.close(); + + if(isLogDebugEnabled()) { + logDebug("Requested URL: " + url); + logDebug("Response: " + response); + } + + return response.toString(); + } catch (IOException e) { + logError("Sending request to Adobe Connect failed. Request: " + url.toString(), e); + return ""; + } + } + + private URL createRequestUrl(Map<String, String> parameters) { + UriBuilder ubu = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port).path("api").path("xml"); + + for(String key : parameters.keySet()) { + ubu.queryParam(key, parameters.get(key)); + } + + URL url = null; + try { + url = ubu.build().toURL(); + } catch (Exception e) { + logWarn("Error while creating URL for Adobe Connect request.", e); + // try to build the URL in a naiv way below + } + if(url == null) { + // error case, try the naiv way + try { + StringBuilder sb = new StringBuilder(protocol + "://" + baseUrl + ":" + port + "/api/xml"); + if(!parameters.isEmpty()) sb.append("?"); + for(String key : parameters.keySet()) { + sb.append(key + "=" + parameters.get(key) + "&"); + } + sb.replace(sb.length(), sb.length(), ""); + url = new URL(sb.toString()); + } catch (MalformedURLException e) { + logError("Error while creating URL for Adobe Connect request. Please check the configuration!", e); + } + } + + return url; + } + + @Override + public boolean isProviderAvailable() { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("action", "common-info"); + Document responseDoc = getResponseDocument(sendRequest(parameters)); + + return evaluateOk(responseDoc); + } + + @Override + public VCConfiguration createNewConfiguration() { + AdobeConnectConfiguration config = new AdobeConnectConfiguration(); + config.setProviderId(providerId); + config.setGuestAccessAllowed(defaultConfig.isGuestAccessAllowed()); + config.setGuestStartMeetingAllowed(defaultConfig.isGuestStartMeetingAllowed()); + config.setUseMeetingDates(defaultConfig.isUseMeetingDates()); + return config; + } + + @Override + public Map<String, String> getTemplates() { + if(templates == null) templates = Collections.emptyMap(); + return templates; + } + + //////////////////////////// + // setters used by spring // + //////////////////////////// + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public void setPort(int port) { + this.port = port; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public void setAdminLogin(String adminLogin) { + this.adminLogin = adminLogin; + } + + public void setAdminPassword(String adminPassword) { + this.adminPassword = adminPassword; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public void setTemplates(Map<String,String> templates) { + this.templates = templates; + } + + public void setGuestAccessAllowedDefault(boolean guestAccessAllowedDefault) { + this.guestAccessAllowedDefault = guestAccessAllowedDefault; + } + + public void setGuestStartMeetingAllowedDefault(boolean guestStartMeetingAllowedDefault) { + this.guestStartMeetingAllowedDefault = guestStartMeetingAllowedDefault; + } + + public void setUseMeetingDatesDefault(boolean useMeetingDatesDefault) { + this.useMeetingDatesDefault = useMeetingDatesDefault; + } + + public void setShowOptions(boolean showOptions) { + this.showOptions = showOptions; + } + + public void setUserType(String userType) { + this.userType = userType; + } + + public void setDefaultConfig(AdobeConnectConfiguration config) { + defaultConfig = config; + defaultConfig.setProviderId(providerId); + } + + ///////////////////////////// + // getters used internally // + ///////////////////////////// + @Override + public boolean isEnabled() { + return enabled; + } + + protected String getProtocol() { + return protocol; + } + + protected int getPort() { + return port; + } + + protected String getBaseUrl() { + return baseUrl; + } + + protected String getAdminLogin() { + return adminLogin; + } + + protected String getAdminPassword() { + return adminPassword; + } + + protected String getAccountId() { + return accountId; + } + + protected boolean isGuestAccessAllowedDefault() { + return guestAccessAllowedDefault; + } + + protected boolean isGuestStartMeetingAllowedDefault() { + return guestStartMeetingAllowedDefault; + } + + protected boolean isUseMeetingDatesDefault() { + return useMeetingDatesDefault; + } + + protected boolean isShowOptions() { + return showOptions; + } + + protected String getUserType() { + return userType; + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeDisplayController.java b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeDisplayController.java new file mode 100644 index 00000000000..95d76fd43b4 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeDisplayController.java @@ -0,0 +1,218 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.adobe; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.media.RedirectMediaResource; + +import de.bps.course.nodes.vc.MeetingDate; +import de.bps.course.nodes.vc.provider.VCProvider; + +/** + * + * Description:<br> + * Display controller for Adobe Connect implementation, mostly used as + * run controller in the course node. + * + * <P> + * Initial Date: 16.12.2010 <br> + * @author skoeber + */ +public class AdobeDisplayController extends BasicController { + + private static String COMMAND_START_MEETING = "cmd.start.meeting"; + private static String COMMAND_START_JOIN_MEETING = "cmd.start.join.meeting"; + private static String COMMAND_JOIN_MODERATOR = "cmd.join.moderator"; + private static String COMMAND_JOIN_LEARNER = "cmd.join.learner"; + private static String COMMAND_REMOVE_MEETING = "cmd.remove.meeting"; + private static String COMMAND_SYNC_MEETING = "cmd.sync.meeting"; + + // objects for run view + private VelocityContainer runVC; + private String roomId; + private Link startModerator, startJoinLearner, joinModerator, joinLearner; + private Link removeMeeting, updateMeeting; + + // data + private List<MeetingDate> dateList = new ArrayList<MeetingDate>(); + private AdobeConnectConfiguration config; + private MeetingDate meeting; + private Date allBegin, allEnd; + + private VCProvider adobe; + + public AdobeDisplayController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, boolean isModerator, AdobeConnectConfiguration config, VCProvider provider) { + super(ureq, wControl); + this.roomId = roomId; + this.adobe = provider; + this.config = config; + + // The dates Table to the Course odes + if(config.getMeetingDates() != null) dateList.addAll(config.getMeetingDates()); + + // select actual meeting + if(config.isUseMeetingDates()) { + Date now = new Date((new Date()).getTime() + 15*60*1000); // allow to start meetings about 15 minutes before begin + for(MeetingDate date : dateList) { + Date begin = date.getBegin(); + Date end = date.getEnd(); + if(now.after(begin) & now.before(end)) { + meeting = date; + } + allBegin = allBegin == null ? begin : begin.before(allBegin) ? begin : allBegin; + allEnd = allEnd == null ? end : end.after(allEnd) ? end : allEnd; + } + } else { + allBegin = new Date(); + allEnd = new Date(allBegin.getTime() + 365*24*60*60*1000); // preset one year + meeting = new MeetingDate(); + meeting.setBegin(allBegin); + meeting.setEnd(allEnd); + meeting.setTitle(name); + meeting.setDescription(description); + } + + runVC = createVelocityContainer("run"); + + startModerator = LinkFactory.createButton(COMMAND_START_MEETING, runVC, this); + startJoinLearner = LinkFactory.createButton(COMMAND_START_JOIN_MEETING, runVC, this); + joinLearner = LinkFactory.createButton(COMMAND_JOIN_LEARNER, runVC, this); + joinModerator = LinkFactory.createButton(COMMAND_JOIN_MODERATOR, runVC, this); + removeMeeting = LinkFactory.createButton(COMMAND_REMOVE_MEETING, runVC, this); + updateMeeting = LinkFactory.createButton(COMMAND_SYNC_MEETING, runVC, this); + // set target to be able to open new browser window on event + startJoinLearner.setTarget("_blank"); + joinLearner.setTarget("_blank"); + joinModerator.setTarget("_blank"); + // render the right button + boolean isUseDates = config.isUseMeetingDates(); + boolean isMeeting = !isUseDates | meeting != null; + boolean exists = adobe.existsClassroom(roomId, config); + boolean guestCanStart = config.isGuestStartMeetingAllowed(); + runVC.contextPut("isModerator", isModerator); + runVC.contextPut("exists", exists); + runVC.contextPut("guestCanStart", guestCanStart); + runVC.contextPut("useDates", isUseDates); + joinLearner.setEnabled(isMeeting & exists); + + putInitialPanel(runVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == startModerator) { + /* + * create new meeting room and prepare to join it + */ + boolean success = adobe.createClassroom(roomId, null, null, allBegin, allEnd, config); + if(success) { + runVC.contextPut("exists", true); + runVC.setDirty(true); + } else { + getWindowControl().setError(translate("error.create.room")); + } + } else if(source == startJoinLearner) { + /* + * create new meeting room and join immediately as guest + */ + boolean success = adobe.createClassroom(roomId, null, null, allBegin, allEnd, config); + if(success) { + runVC.contextPut("exists", true); + runVC.setDirty(true); + // join meeting as guest + URL url = adobe.createClassroomGuestUrl(roomId, ureq.getIdentity(), config); + RedirectMediaResource rmr = new RedirectMediaResource(url.toString()); + ureq.getDispatchResult().setResultingMediaResource(rmr); + return; + } else { + getWindowControl().setError(translate("error.create.room")); + } + } else if(source == joinLearner) { + /* + * easiest case: simply generate link to join meeting as guest + */ + URL url = adobe.createClassroomGuestUrl(roomId, ureq.getIdentity(), config); + RedirectMediaResource rmr = new RedirectMediaResource(url.toString()); + ureq.getDispatchResult().setResultingMediaResource(rmr); + return; + } else if(source == joinModerator) { + /* + * join meeting as moderator, first prepare user to have appropriate rights + */ + boolean success = adobe.existsClassroom(roomId, config); + // update rights for user to moderate meeting + if(success) { + success = adobe.createModerator(ureq.getIdentity(), roomId); + } else { + // room not found, should not appear + getWindowControl().setError(translate("error.no.room")); + return; + } + // login the user as moderator + if(success) { + success = adobe.login(ureq.getIdentity(), null); + } else { + // could not create moderator or update the rights + getWindowControl().setError(translate("error.update.rights")); + return; + } + // redirect to the meeting + if(success) { + URL url = adobe.createClassroomUrl(roomId, ureq.getIdentity(), config); + RedirectMediaResource rmr = new RedirectMediaResource(url.toString()); + ureq.getDispatchResult().setResultingMediaResource(rmr); + } else { + // login failed + getWindowControl().setError(translate("error.no.login")); + return; + } + return; + } else if(source == removeMeeting) { + boolean success = adobe.removeClassroom(roomId, config); + if(success) { + runVC.contextPut("exists", false); + runVC.setDirty(true); + } else { + // removing failed + getWindowControl().setError(translate("error.remove.room")); + } + } else if(source == updateMeeting) { + boolean success = adobe.updateClassroom(roomId, null, null, allBegin, allEnd, config); + if(success) { + getWindowControl().setInfo(translate("success.update.room")); + } else { + // update failed + getWindowControl().setError(translate("error.update.room")); + } + } + } + + @Override + protected void doDispose() { + // nothing to dispose + } +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditForm.java b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditForm.java new file mode 100644 index 00000000000..bb382203f07 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditForm.java @@ -0,0 +1,92 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.adobe; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.editor.NodeEditController; + +/** + * Description:<br> + * Edit form for Adobe Connect specific options. + * + * <P> + * Initial Date: 30.08.2010 <br> + * + * @author jens Lindner (jlindne4@hs-mittweida.de) + * @author skoeber + */ +public class AdobeEditForm extends FormBasicController { + + // GUI + private FormSubmit submit; + private MultipleSelectionElement multiSelectOptions; + private static String OPTION_START_MEETING = "vc.access.start"; + private static String OPTION_OPEN_MEETING = "vc.access.open"; + private boolean showOptions; + + // data + private AdobeConnectConfiguration config; + + public AdobeEditForm(UserRequest ureq, WindowControl wControl, boolean showOptions, AdobeConnectConfiguration config) { + super(ureq, wControl, FormBasicController.LAYOUT_VERTICAL); + this.config = config; + this.showOptions = showOptions; + + initForm(this.flc, this, ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + + // meeting options + String[] accessKeys = new String[] {OPTION_OPEN_MEETING, OPTION_START_MEETING}; + String[] accessVals = new String[] {translate(OPTION_OPEN_MEETING), translate(OPTION_START_MEETING)}; + multiSelectOptions = uifactory.addCheckboxesVertical("vc.access", "vc.access.label", formLayout, accessKeys, accessVals, null, 1); + multiSelectOptions.select(OPTION_START_MEETING, !config.isGuestStartMeetingAllowed()); + multiSelectOptions.select(OPTION_OPEN_MEETING, !config.isGuestAccessAllowed()); + multiSelectOptions.setVisible(showOptions); + multiSelectOptions.addActionListener(this, FormEvent.ONCHANGE); + multiSelectOptions.showLabel(false); + + submit = new FormSubmit("subm", "submit"); + formLayout.add(submit); + } + + @Override + protected void doDispose() { + // nothing to dispose + } + + @Override + protected void formOK(UserRequest ureq) { + // read data from form elements + if(showOptions) { + config.setGuestAccessAllowed(!multiSelectOptions.getSelectedKeys().contains(OPTION_OPEN_MEETING)); + config.setGuestStartMeetingAllowed(!multiSelectOptions.getSelectedKeys().contains(OPTION_START_MEETING)); + } + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + + protected AdobeConnectConfiguration getConfig() { + return config; + } +} +//</OLATCE-103> diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditTableDataModel.java b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditTableDataModel.java new file mode 100644 index 00000000000..613003998a7 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/AdobeEditTableDataModel.java @@ -0,0 +1,71 @@ +// <OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2010 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.adobe; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import org.olat.core.gui.components.table.DefaultTableDataModel; + +import de.bps.course.nodes.vc.MeetingDate; + +/** + * Description:<br> + * Data model for editing dates lists - Virtual Classroom dates. + * + * <P> + * Initial Date: 14.07.2010 <br> + * + * @author Jens Lindner (jlindne4@hs-mittweida.de) + * @author skoeber + */ +public class AdobeEditTableDataModel extends DefaultTableDataModel { + + private static int COLUMN_COUNT = 4; + + public AdobeEditTableDataModel(final List<MeetingDate> objects) { + super(objects); + } + + /** + * {@inheritDoc} + */ + @Override + public int getColumnCount() { + return COLUMN_COUNT; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getValueAt(int row, int col) { + final MeetingDate model = ((MeetingDate) objects.get(row)); + switch (col) { + case 0: + return model.getTitle(); + case 1: + return model.getDescription(); + case 2: + return model.getBegin(); + case 3: + SimpleDateFormat sd = new SimpleDateFormat("dd.MM.yyyy HH:mm"); + return sd.format(model.getEnd()); + default: + return "error"; + } + } +} +// </OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/_chelp/ced-vc-config.html b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_chelp/ced-vc-config.html new file mode 100644 index 00000000000..0abcd75c4c1 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_chelp/ced-vc-config.html @@ -0,0 +1,4 @@ +<!-- <OLATCE-103> --> +<p>$r.translate("chelp.vc1")</p> +<p>$r.translate("chelp.vc2")</p> +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/edit.html b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/edit.html new file mode 100644 index 00000000000..ebfa3e68eb8 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/edit.html @@ -0,0 +1,5 @@ +<!-- <OLATCE-103> --> +$r.contextHelpWithWrapper("de.bps.course.nodes.vc.provider.adobe","ced-vc-config.html","chelp.hover.vc.config") + +$r.render("editForm") +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/run.html b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/run.html new file mode 100644 index 00000000000..60f4d3d1f54 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_content/run.html @@ -0,0 +1,28 @@ +<!-- <OLATCE-103> --> +#if($isModerator) + #if($exists) + <p>$r.translate("cmd.join.moderator.intro")</p> + $r.render("cmd.join.moderator") + <div class="b_info"> + <p><strong>$r.translate("title.options")</strong></p> + $r.render("cmd.remove.meeting") + $r.render("cmd.sync.meeting") + </div> + #else + <p>$r.translate("cmd.start.meeting.intro")</p> + $r.render("cmd.start.meeting") + #end +#else + #if($exists) + <p>$r.translate("cmd.join.learner.intro")</p> + $r.render("cmd.join.learner") + #else + #if($guestCanStart) + <p>$r.translate("cmd.join.learner.intro")</p> + $r.render("cmd.start.join.meeting") + #else + <p>$r.translate("cmd.join.learner.wait")</p> + #end + #end +#end +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_de.properties b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_de.properties new file mode 100644 index 00000000000..a9618db8c87 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_de.properties @@ -0,0 +1,34 @@ +#<OLATCE-103> +vc.access.label=Zutrittsberechtigung +vc.access.start=Nur Moderatoren d\u00fcrfen diesen Raum er\u00f6ffnen +vc.access.open=Moderator muss im Raum online sein, um Zutritt f\u00fcr Teilnehmer zu best\u00e4tigen +vc.access.dates=Virtuelles Klassenzimmer soll nur zu bestimmten Terminen betreten werden k\u00f6nnen + +cmd.start.meeting=Virtuelles Klassenzimmer er\u00f6ffnen +cmd.start.meeting.intro=Das virtuelle Klassenzimmer wurde noch nicht er\u00f6ffnet. Teilnehmer k\u00f6nnen den Raum f\u00fcr ein geplantes Meeting ggf. nicht betreten. +cmd.start.join.meeting=Virtuelles Klassenzimmer betreten +cmd.join.moderator=Virtuelles Klassenzimmer moderieren +cmd.join.moderator.intro=Das virtuelle Klassenzimmer wurde er\u00f6ffnet. Sie k\u00f6nnen den Raum nun betreten und moderieren. Teilnehmer k\u00f6nnen das virtuelle Klassenzimmer ohne Zustimmung eines Moderators ggf. nicht betreten. +cmd.join.learner=Virtuelles Klassenzimmer betreten +cmd.join.learner.intro=Das virtuelle Klassenzimmer kann betreten werden. +cmd.join.learner.wait=Das virtuelle Klassenzimmer wurde noch nicht er\u00f6ffnet. +cmd.remove.meeting=Meeting schlie\u00dfen +cmd.sync.meeting=Meeting synchronisieren +title.options=Weiterf¸hrende Informationen und Optionen f¸r Autoren + +error.no.room=Das virtuelle Klassenzimmer konnte nicht geladen werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.no.login=Die Anmeldung war nicht erfolgreich. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.create.room=Das virtuelle Klassenzimmer konnte nicht erzeugt werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.remove.room=Das virtuelle Klassenzimmer konnte nicht entfernt werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.update.room=Das virtuelle Klassenzimmer konnte nicht mit den aktuellen Einstellungen synchronisiert werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.update.rights=Ihnen konnten nicht die notwendigen Rechte f\u00fcr diesen Raum zugewiesen werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. + +sync.meeting.title=Virtuelles Klassenzimmer synchronisieren +sync.meeting.text=Sie haben die Einstellungen f\u00fcr diese virtuelle Klassenzimmer ge\u00e4ndert, nachdem dieses er\u00f6ffnet wurde. Die \u00c4nderungen werden erst wirksam, wenn Sie das virtuelle Klassenzimmer mit den neuen Einstellungen synchronisieren. Die Einstellungen k\u00f6nnen auch jederzeit direkt im Kurs synchronisiert werden. M\u00f6chten Sie die Einstellungen jetzt synchronisieren? +success.update.room=Das virtuelle Klassenzimmer wurde erfolgreich mit den aktuellen Einstellungen synchronisiert. + +chelp.ced-vc-config.title=Virtuelles Klassenzimmer konfigurieren (Adobe Connect) +chelp.hover.vc.config=Hilfe zur Konfiguration des Virtuellen Klassenzimmers +chelp.vc1=Legen Sie spezielle Optionen f¸r Ihr virtuelles Klassenzimmer in Adobe Connect fest. +chelp.vc2=<b>Zutrittsberechtigung:</b> Legen Sie fest, ob der Zugang zu einem Raum f\u00fcr Teilnehmer erst durch einen Moderator best\u00e4tigt werden muss bzw. ob Teilnehmer selbst\u00e4ndig den Raum er\u00f6ffnen und betreten d\u00fcrfen. +#</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_en.properties b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_en.properties new file mode 100644 index 00000000000..85916426086 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_i18n/LocalStrings_en.properties @@ -0,0 +1,34 @@ +#<OLATCE-103 OLATCE-619> +vc.access.label=Access authorisation +vc.access.start=Only moderators are allowed to open this virtual classroom +vc.access.open=Moderator must be in classroom to grant access to users +vc.access.dates=Virtual classroom shall only be available at defined dates + +cmd.start.meeting=Open virtual classroom +cmd.start.meeting.intro=The virtual classroom has not been opened, yet. Participants are not able to enter the classroom for a meeting. +cmd.start.join.meeting=Enter virtual classroom +cmd.join.moderator=Moderate virtual classroom +cmd.join.moderator.intro=The virtual classroom has been opened. You may now enter the classroom and moderate the meeting. Participants are not able to join the meeting until the moderator grant them access. +cmd.join.learner=Enter virtual classroom +cmd.join.learner.intro=You can join the virtual classroom. +cmd.join.learner.wait=The virtual classroom has not been opened, yet. +cmd.remove.meeting=Close meeting +cmd.sync.meeting=Syncronize meeting +title.options=Additional information and options for authors + +error.no.room=Sorry! An error occured while loading the virtual classroom. Please inform your tutor or system administrator. +error.no.login=Login was not successfull. Please, contact your tutor or system administrator for any further information. +error.create.room=Sorry! An error occured while creating the virtual classroom. Please inform your tutor or system administrator. +error.remove.room=Sorry! An error occured while removing the virtual class room. Please inform your tutor or system administrator. +error.update.room=Sorry! An error occured while synchronizing the virtual class room with the actual configuration. Please inform your tutor or system administrator. +error.update.rights=Sorry! An error occured while granting access to the virtual classroom. Please inform your tutor or system administrator. + +sync.meeting.title=Synchronize virtual classroom +sync.meeting.text=You have changed the configuration of the virtual classroom after it had been started. The changes will be taken into account only after having synchronized the virtual class room with the new configuration. You can synchronize the configuration at any time directly from the course run. Do you want to synchronize now? +success.update.room=Successfully synchronized the virtual classroom with the current configuration. + +chelp.ced-vc-config.title=Configure virtual classroom (Adobe Connect) +chelp.hover.vc.config=Help about configuration of the virtual classroom +chelp.vc1=Choose special options for your virtual classroom in Adobe Connect. +chelp.vc2=<b>Access authorisation:</b> Choose whether a has to be in the classroom to grant access to other users or if users may open and enter a classroom on their own. +#</OLATCE-103 OLATCE-619> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/adobe/_spring/adobeContext.xml b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_spring/adobeContext.xml new file mode 100644 index 00000000000..214277857e6 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/adobe/_spring/adobeContext.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + + <bean id="adobeDefaultConfig" class="de.bps.course.nodes.vc.provider.adobe.AdobeConnectConfiguration"> + <!-- general --> + <property name="createMeetingImmediately" value="false" /> + <!-- Adobe Connect specific --> + <property name="guestAccessAllowed" value="true" /> + <property name="guestStartMeetingAllowed" value="true" /> + <property name="useMeetingDates" value="false" /> + </bean> + +</beans> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/StatusCode.java b/src/main/java/de/bps/course/nodes/vc/provider/wimba/StatusCode.java new file mode 100644 index 00000000000..5993c40124b --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/StatusCode.java @@ -0,0 +1,67 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.wimba; + +/** + * + * Description:<br> + * API return codes, see Wimba Classroom 6.0 API Guide, page 5 + * + * <P> + * Initial Date: 07.01.2011 <br> + * @author skoeber + */ +public enum StatusCode { + + /** 100 OK */ OK(100), + /** 200 Database access error (database unavailable) */ DB_ACCESS_ERROR(200), + /** 201 Permission denied */ DENIED(201), + /** 204 Not authenticated, invalid authentication */ NO_AUTH(204), + /** 300 Database constraint error */ DB_CONSTRAINT_ERROR(300), + /** 301 Target already exists */ ALREADY_EXISTS(301), + /** 302 Target not found */ NOT_FOUND(302), + /** 400 Miscellaneous */ MISC(400), + /** 401 Not implemented */ NOT_IMPLEMENTED(401), + /** 402 Malformed query */ MALFORMED_QUERY(402), + /** 404 API HTTP server error */ SERVER_ERROR(404), + /** Undefined */ UNDEFINED(666); + + private int code; + + private StatusCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static StatusCode getStatus(int code) { + switch (code) { + case 100: return OK; + case 200: return DB_ACCESS_ERROR; + case 201: return DENIED; + case 204: return NO_AUTH; + case 300: return DB_CONSTRAINT_ERROR; + case 301: return ALREADY_EXISTS; + case 302: return NOT_FOUND; + case 400: return MISC; + case 401: return NOT_IMPLEMENTED; + case 402: return MALFORMED_QUERY; + case 404: return SERVER_ERROR; + default: return UNDEFINED; + } + } +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomConfiguration.java b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomConfiguration.java new file mode 100644 index 00000000000..6f8f1076d37 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomConfiguration.java @@ -0,0 +1,183 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.wimba; + +import java.io.Serializable; + +import de.bps.course.nodes.vc.DefaultVCConfiguration; + +/** + * + * Description:<br> + * Configuration object for Wimba Classroom + * + * <P> + * Initial Date: 06.01.2011 <br> + * @author skoeber + */ +public class WimbaClassroomConfiguration extends DefaultVCConfiguration implements Serializable { + + /** presentation tools are available only to instructors or to both students and instructors */ + private boolean toolsToStudentsEnabled; + /** allow students to speak by default */ + private boolean studentsSpeakAllowed; + /** allow students to show their video by default */ + private boolean studentsVideoAllowed; + /** enable students to use text chat */ + private boolean studentsChatAllowed; + /** enable private text chat among student */ + private boolean studentsPrivateChatAllowed; + /** enable user status indicators */ + private boolean userStatusIndicatorsEnabled; + /** enable user status updates appear in chat */ + private boolean userStatusUpdateInChatEnabled; + /** enable students to use the eBoard by default */ + private boolean studentEBoardEnabled; + /** enable breakout rooms */ + private boolean breakoutRoomsEnabled; + /** enable archiving */ + private boolean archivingEnabled; + /** automatically open new archives */ + private boolean autoOpenNewArchives; + /** enable appshare */ + private boolean appshareEnabled; + /** enable on-the-fly PowerPoint import */ + private boolean powerPointImportEnabled; + /** enable guest access */ + private boolean guestAccessAllowed; + + /* be compatible with old configuration versions */ + @SuppressWarnings("unused") private transient boolean chatEnabled; + @SuppressWarnings("unused") private transient boolean privateChatEnabled; + + public boolean isToolsToStudentsEnabled() { + return toolsToStudentsEnabled; + } + + public boolean isStudentsSpeakAllowed() { + return studentsSpeakAllowed; + } + + public boolean isStudentsVideoAllowed() { + return studentsVideoAllowed; + } + + public boolean isUserStatusIndicatorsEnabled() { + return userStatusIndicatorsEnabled; + } + + public boolean isUserStatusUpdateInChatEnabled() { + return userStatusUpdateInChatEnabled; + } + + public boolean isStudentEBoardEnabled() { + return studentEBoardEnabled; + } + + public boolean isBreakoutRoomsEnabled() { + return breakoutRoomsEnabled; + } + + public boolean isArchivingEnabled() { + return archivingEnabled; + } + + public boolean isAutoOpenNewArchives() { + return autoOpenNewArchives; + } + + public boolean isAppshareEnabled() { + return appshareEnabled; + } + + public boolean isPowerPointImportEnabled() { + return powerPointImportEnabled; + } + + public boolean isGuestAccessAllowed() { + return guestAccessAllowed; + } + + public void setToolsToStudentsEnabled(boolean toolsToStudentsEnabled) { + this.toolsToStudentsEnabled = toolsToStudentsEnabled; + } + + public void setStudentsSpeakAllowed(boolean studentsSpeakAllowed) { + this.studentsSpeakAllowed = studentsSpeakAllowed; + } + + public void setStudentsVideoAllowed(boolean studentsVideoAllowed) { + this.studentsVideoAllowed = studentsVideoAllowed; + } + + public void setUserStatusIndicatorsEnabled(boolean userStatusIndicatorsEnabled) { + this.userStatusIndicatorsEnabled = userStatusIndicatorsEnabled; + } + + public void setUserStatusUpdateInChatEnabled(boolean userStatusUpdateInChatEnabled) { + this.userStatusUpdateInChatEnabled = userStatusUpdateInChatEnabled; + } + + public void setStudentEBoardEnabled(boolean studentEBoardEnabled) { + this.studentEBoardEnabled = studentEBoardEnabled; + } + + public void setBreakoutRoomsEnabled(boolean breakoutRoomsEnabled) { + this.breakoutRoomsEnabled = breakoutRoomsEnabled; + } + + public void setArchivingEnabled(boolean archivingEnabled) { + this.archivingEnabled = archivingEnabled; + } + + public void setAutoOpenNewArchives(boolean autoOpenNewArchives) { + this.autoOpenNewArchives = autoOpenNewArchives; + } + + public void setAppshareEnabled(boolean appshareEnabled) { + this.appshareEnabled = appshareEnabled; + } + + public void setPowerPointImportEnabled(boolean powerPointImportEnabled) { + this.powerPointImportEnabled = powerPointImportEnabled; + } + + public void setGuestAccessAllowed(boolean guestAccessAllowed) { + this.guestAccessAllowed = guestAccessAllowed; + } + + public void setStudentsChatAllowed(boolean studentsChatAllowed) { + this.studentsChatAllowed = studentsChatAllowed; + } + + public boolean isStudentsChatAllowed() { + return studentsChatAllowed; + } + + public void setStudentsPrivateChatAllowed(boolean studentsPrivateChatAllowed) { + this.studentsPrivateChatAllowed = studentsPrivateChatAllowed; + } + + public boolean isStudentsPrivateChatAllowed() { + return studentsPrivateChatAllowed; + } + + @Override + public boolean isConfigValid() { + // TODO implement logic + return true; + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomProvider.java b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomProvider.java new file mode 100644 index 00000000000..6654131dcb5 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaClassroomProvider.java @@ -0,0 +1,812 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.wimba; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.UriBuilder; + +import org.apache.commons.lang.NotImplementedException; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.helpers.Settings; +import org.olat.core.id.Identity; +import org.olat.core.id.UserConstants; +import org.olat.core.logging.AssertException; +import org.olat.core.logging.LogDelegator; +import org.olat.core.util.Encoder; + +import de.bps.course.nodes.vc.VCConfiguration; +import de.bps.course.nodes.vc.provider.VCProvider; + +/** + * + * Description:<br> + * Virtual classroom provider implementation for Wimba Classroom + * + * <P> + * Initial Date: 06.01.2011 <br> + * @author skoeber + */ +public class WimbaClassroomProvider extends LogDelegator implements VCProvider { + + /** Return type of responses, see Wimba Classroom 6.0 API Guide, page 5 */ + private static final String CONTENT_TYPE = "text/html"; + /** Session cookie, see Wimba Classroom 6.0 API Guide, page 6 */ + private static final String COOKIE = "AuthCookieHandler_Horizon="; + /** Session token for remote login, see Wimba Classroom 6.0 API Guide, page 56 */ + private static final String TOKEN = "authToken"; + /** Delimiter for data in record format, see Wimba Classroom 6.0 API Guide, page 5 */ + private static final String DELIM = "=END RECORD"; + protected static final String PREFIX = "olat_"; + + // targets for service URLs + protected static String TARGET_OPEN_MANAGEROOM = "cmd.open.manageroom"; + protected static String TARGET_OPEN_POLLRESULTS = "cmd.open.pollresults"; + protected static String TARGET_OPEN_TRACKING = "cmd.open.tracking"; + protected static String TARGET_OPEN_ROOMSETTINGS = "cmd.open.roomsettings"; + protected static String TARGET_OPEN_MEDIASETTINGS = "cmd.open.mediasettings"; + protected static String TARGET_OPEN_WIZARD = "cmd.open.wizard"; + + private static String ENDPOINT_WIZARD = "/wizard/wizard.html.pl?wizardconf=wizard.conf"; + + // configuration + private static WimbaClassroomConfiguration defaultConfig; + private boolean enabled; + private String providerId; + private String displayName; + private String protocol; + private int port; + private String baseUrl; + private String adminLogin; + private String adminPassword; + + // runtime data + private String cookie; + private String token; + + private WimbaClassroomProvider(String providerId, String displayName, String protocol, int port, String baseUrl, String adminLogin, String adminPassword) { + setProviderId(providerId); + setDisplayName(displayName); + setProtocol(protocol); + setPort(port); + setBaseUrl(baseUrl); + setAdminLogin(adminLogin); + setAdminPassword(adminPassword); + } + + /** + * Public constructor, mostly used by spring<br/> + * <b>Important</b> when using: set configuration manually! + */ + public WimbaClassroomProvider() { + // + } + + @Override + public VCProvider newInstance() { + return new WimbaClassroomProvider(providerId, displayName, protocol, port, baseUrl, adminLogin, adminPassword); + } + + @Override + public String getProviderId() { + return providerId; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public boolean isProviderAvailable() { + if(!loginAdmin()) return false; + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "statusServer"); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + + return success; + } + + @Override + public boolean createClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config) { + if(existsClassroom(roomId, config)) return true; + boolean success = handleClassroomRequest("createClass", roomId, name, description, begin, end, config); + // set preview mode because this setting has no effect on creation + if(success) { + success = setPreviewMode(roomId, false, false); + updateRights("Guest", roomId, "Student", false); + } + return success; + } + + @Override + public boolean updateClassroom(String roomId, String name, String description, Date begin, Date end, VCConfiguration config) { + if(!existsClassroom(roomId, config)) { + logWarn("Tried to update Wimba Classroom meeting, that not exists!", null); + return false; + } + return handleClassroomRequest("modifyClass", roomId, name, description, begin, end, config); + } + + private boolean handleClassroomRequest(String function, String roomId, String name, String description, Date begin, Date end, VCConfiguration config) { + WimbaClassroomConfiguration wc = (WimbaClassroomConfiguration) config; + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", function); + parameters.put("target", PREFIX + roomId); + parameters.put("userlimit", "-1"); + /** name is limited to maximum of 50 characters, see Wimba Classroom 6.0 API Guide, page 15 */ + if(name != null && name.length() > 50) name = name.substring(0, 49); + parameters.put("longname", name); + /** name is limited to maximum of 50 characters, see Wimba Classroom 6.0 API Guide, page 15 */ + if(description != null && description.length() > 50) description = description.substring(0, 49); + parameters.put("can_liveshare", param(wc.isAppshareEnabled())); + parameters.put("can_archive", param(wc.isArchivingEnabled())); + parameters.put("auto_open_new__archives", param(wc.isAutoOpenNewArchives())); + parameters.put("bor_enabled", param(wc.isBreakoutRoomsEnabled())); + parameters.put("can_ppt_import", param(wc.isPowerPointImportEnabled())); + parameters.put("student_wb_enabled", param(wc.isStudentEBoardEnabled())); + parameters.put("hms_two_way_enabled", param(wc.isStudentsSpeakAllowed())); + parameters.put("media_format", "hms"); + parameters.put("media_type", wc.isStudentsVideoAllowed() ? "two-way-video" : "simulcast-only"); + parameters.put("hms_simulcast_restricted", param(!wc.isToolsToStudentsEnabled())); + parameters.put("userstatus_enabled", param(wc.isUserStatusIndicatorsEnabled())); + parameters.put("send_userstatus_updates", param(wc.isUserStatusUpdateInChatEnabled())); + parameters.put("chatenable", param(wc.isStudentsChatAllowed())); + parameters.put("privatechatenable", param(wc.isStudentsPrivateChatAllowed())); + parameters.put("archive", param(false)); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + + boolean success = evaluateOk(response); + if(!success) handleError(response.getStatus(), null); + + if(wc.isGuestAccessAllowed()) { + updateRights("Guest", roomId, "Student", false); + } else if(function.equals("modifyClass")) { + // only delete guest access if this is an already existing meeting + updateRights("Guest", roomId, "Student", true); + } + + return success; + } + + @Override + public boolean removeClassroom(String roomId, VCConfiguration config) { + if(!existsClassroom(roomId, config)) return true; + + if(!loginAdmin()) throw new AssertException("Cannot login to Wimba Classroom. Please check module configuration and Wimba Classroom connectivity"); + + // first delete recordings + Map<String, String> mapRecordings = listRecordings(roomId); + for(String key : mapRecordings.keySet()) { + removeClassroomRecording(key, config); + } + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "deleteClass"); + parameters.put("target", PREFIX + roomId); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + + boolean success = evaluateOk(response); + if(!success) handleError(response.getStatus(), null); + + return success; + } + + @Override + public URL createClassroomUrl(String roomId, Identity identity, VCConfiguration config) { + URL url = null; + URI uri = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port) + .path("check_wizard.pl").queryParam("channel", PREFIX + roomId).queryParam("hzA", token).build(); + try { + url = uri.toURL(); + } catch (MalformedURLException e) { + logWarn("Cannot create access URL to Wimba Classroom meeting for id \"" + PREFIX + roomId + "\" and user \"" + identity.getName() + "\"", e); + } + + return url; + } + + @Override + public URL createClassroomGuestUrl(String roomId, Identity identity, VCConfiguration config) { + URL url = null; + URI uri = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port) + .path("launcher.cgi").queryParam("room",PREFIX + roomId).build(); + try { + url = uri.toURL(); + } catch (MalformedURLException e) { + logWarn("Cannot create guest access URL to Wimba Classroom meeting for id \"" + PREFIX + roomId, e); + } + + return url; + } + + @Override + public boolean existsClassroom(String roomId, VCConfiguration config) { + if(!loginAdmin()) return false; + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "listClass"); + parameters.put("filter00", "class_id"); + parameters.put("filter00value", PREFIX + roomId); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + + boolean success = evaluateOk(response); + if(success) success = response.hasRecords() && response.numRecords() == 1; + + return success; + } + + @Override + public boolean login(Identity identity, String password) { + if(!loginAdmin()) return false; + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "getAuthToken"); + parameters.put("target", PREFIX + identity.getName()); + parameters.put("nickname", identity.getName()); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + + if(success & response.hasRecords()) { + String responseToken = response.findRecord(TOKEN); + if(responseToken != null) { + token = responseToken; + } else { + success = false; + } + } + + return success; + } + + private boolean loginAdmin() { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "NoOp"); + parameters.put("AuthType", "AuthCookieHandler"); + parameters.put("AuthName", "Horizon"); + parameters.put("credential_0", adminLogin); + parameters.put("credential_1", adminPassword); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + + if(!success) { + logError("Cannot login to Wimba Classroom. Please check module configuration and Wimba Classroom connectivity", null); + } + + return success; + } + + @Override + public boolean createModerator(Identity identity, String roomId) { + if(!loginAdmin()) throw new AssertException("Cannot login to Wimba Classroom. Please check module configuration and Wimba Classroom connectivity"); + + boolean success = false; + boolean exists = existsLogin(identity); + + // create login if necessary + if(!exists) success = createLogin(identity); + // update access rights + if(exists | success) success = updateRights(identity.getName(), roomId, "Instructor", false); + + return success; + } + + @Override + public boolean createUser(Identity identity, String roomId) { + if(!loginAdmin()) throw new AssertException("Cannot login to Wimba Classroom. Please check module configuration and Wimba Classroom connectivity"); + + boolean success = false; + boolean exists = existsLogin(identity); + + // create login if necessary + if(!exists) success = createLogin(identity); + // update access rights + if(exists | success) success = updateRights(identity.getName(), roomId, "Student", false); + + return success; + } + + private boolean updateRights(String username, String roomId, String role, boolean delete) { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", delete ? "deleteRole" : "createRole"); + parameters.put("target", PREFIX + roomId); + parameters.put("user_id", username.equals("Guest") ? "Guest" : PREFIX + username); + parameters.put("role_id", role); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + + return success; + } + + private boolean createLogin(Identity identity) { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "createUser"); + parameters.put("target", PREFIX + identity.getName()); + parameters.put("password_type", "P");// specified password, see Wimba Classroom 6.0 API Guide, page 8 + parameters.put("password", Encoder.encrypt(identity.getName() + "@" + Settings.getApplicationName())); + parameters.put("first_name", identity.getUser().getProperty(UserConstants.FIRSTNAME, null)); + parameters.put("last_name", identity.getUser().getProperty(UserConstants.LASTNAME, null)); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + + return success; + } + + private boolean existsLogin(Identity identity) { + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "listUser"); + parameters.put("attribute", "user_id"); + parameters.put("filter01", "user_id"); + parameters.put("filter01value", PREFIX + identity.getName()); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + boolean exists = false; + if(success) exists = response.numRecords() == 1; + + return exists; + } + + @Override + public boolean createGuest(Identity identity, String roomId) { + throw new NotImplementedException("method createGuest not yet implemented"); + } + + @Override + public Controller createDisplayController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, boolean isModerator, + VCConfiguration config) { + WimbaDisplayController displayCtr = new WimbaDisplayController(ureq, wControl, roomId, name, description, isModerator, (WimbaClassroomConfiguration) config, this); + return displayCtr; + } + + @Override + public Controller createConfigController(UserRequest ureq, WindowControl wControl, String roomId, VCConfiguration config) { + WimbaConfigController configCtr = new WimbaConfigController(ureq, wControl, roomId, this, (WimbaClassroomConfiguration) config); + return configCtr; + } + + public boolean isPreviewMode(String roomId, boolean isRecording) { + if(!loginAdmin()) throw new AssertException("Cannot login to Wimba Classroom. Please check module configuration and Wimba Classroom connectivity"); + + boolean mode = false; + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "listClass"); + parameters.put("attribute", "preview"); + parameters.put("filter00", "class_id"); + parameters.put("filter00value", isRecording ? roomId : PREFIX + roomId); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + + if(success & response.hasRecords()) { + String modeStr = response.getRecords().get(0).get("preview"); + if(modeStr == null || modeStr.equals("0")) mode = false; + else if(modeStr.equals("1")) mode = true; + } + + return mode; + } + + public boolean setPreviewMode(String roomId, boolean enabled, boolean isRecording) { + if(!loginAdmin()) throw new AssertException("Cannot login to Wimba Classroom. Please check module configuration and Wimba Classroom connectivity"); + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "modifyClass"); + parameters.put("target", isRecording ? roomId : PREFIX + roomId); + parameters.put("preview", param(enabled)); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + + boolean success = evaluateOk(response); + if(!success) handleError(response.getStatus(), null); + + return success; + } + + /** + * + * @param roomId + * @return map with recordingId and name + */ + public Map<String, String> listRecordings(String roomId) { + Map<String, String> mapKeyName = new HashMap<String, String>(); + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "listClass"); + parameters.put("attribute", "longname"); + parameters.put("filter00", "archive_of"); + parameters.put("filter00value", PREFIX + roomId); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + boolean success = evaluateOk(response); + + if(success & response.hasRecords()) { + List<Map<String, String>> records = response.getRecords(); + for(Map<String, String> record : records) { + mapKeyName.put(record.get("class_id"), record.get("longname")); + } + } + + return mapKeyName; + } + + public URL createClassroomRecordingUrl(String recordingId, Identity identity, VCConfiguration config) { + URL url = null; + /* Notice: This is a very special and wimba specific case, the recordingId must not prefixed! */ + URI uri = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port) + .path("check_wizard.pl").queryParam("channel", recordingId).queryParam("hzA", token).build(); + try { + url = uri.toURL(); + } catch (MalformedURLException e) { + logWarn("Cannot create access URL to Wimba Classroom meeting for id \"" + recordingId + "\" and user \"" + identity.getName() + "\"", e); + } + + return url; + } + + public boolean removeClassroomRecording(String recordingId, VCConfiguration config) { + if(!loginAdmin()) throw new AssertException("Cannot login to Wimba Classroom. Please check module configuration and Wimba Classroom connectivity"); + + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("function", "deleteClass"); + parameters.put("target", recordingId); + String raw = sendRequest(parameters); + WimbaResponse response = getResponseDocument(raw); + + boolean success = evaluateOk(response); + if(!success) handleError(response.getStatus(), null); + + return success; + } + + @Override + public VCConfiguration createNewConfiguration() { + // do a deep copy + Object deepCopy = null; + try { + ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); + ObjectOutputStream objectOutput; + objectOutput = new ObjectOutputStream(byteOutput); + objectOutput.writeObject(defaultConfig); + ByteArrayInputStream byteInput = new ByteArrayInputStream(byteOutput.toByteArray()); + ObjectInputStream objectInput = new ObjectInputStream(byteInput); + deepCopy = objectInput.readObject(); + } catch (Exception e) { + logError("Creation of default Wimba Classroom configuration failed.", e); + } + WimbaClassroomConfiguration newConfig = (WimbaClassroomConfiguration) deepCopy; + + return newConfig; + } + + /** + * Creates Wimba Classroom specific direct access URLs for several services + * @param target + * @param roomId + * @return + */ + protected String createServiceUrl(String target, String roomId) { + StringBuilder sb = new StringBuilder(); + sb.append(getProtocol()).append("://").append(getBaseUrl()).append(":").append(getPort()); + String classId = PREFIX + roomId; + String hzA = "&hzA=" + token; + if(target.equals(TARGET_OPEN_WIZARD)) { + sb.append(ENDPOINT_WIZARD); + } else if(target.equals(TARGET_OPEN_MANAGEROOM)) { + sb.append("/admin/api/class/carousels?class_id="); + sb.append(classId); + sb.append(hzA); + } else if(target.equals(TARGET_OPEN_POLLRESULTS)) { + sb.append("/admin/api/class/results?class_id="); + sb.append(classId); + sb.append(hzA); + } else if(target.equals(TARGET_OPEN_TRACKING)) { + sb.append("/admin/api/server/tracking?mode=detailed&popup=1&channel="); + sb.append(classId); + sb.append(hzA); + } else if(target.equals(TARGET_OPEN_ROOMSETTINGS)) { + sb.append("/admin/api/class/properties.pl?class_id="); + sb.append(classId); + sb.append(hzA); + } else if(target.equals(TARGET_OPEN_MEDIASETTINGS)) { + sb.append("/admin/api/class/media.pl?class_id="); + sb.append(classId); + sb.append(hzA); + } + sb.append("&no_sidebar=1"); + + return sb.toString(); + } + + ///////////////////////////// + // internal helper methods // + ///////////////////////////// + + private boolean evaluateOk(WimbaResponse response) { + return response.getStatus().equals(StatusCode.OK); + } + + private WimbaResponse getResponseDocument(String rawResponse) { + StringReader input = new StringReader(rawResponse); + LineNumberReader reader = new LineNumberReader(input); + + WimbaResponse response = new WimbaResponse(); + String line; + Map<String, String> record = new HashMap<String, String>(); + try { + // start with status code in first line + line = reader.readLine(); + response.setStatus(getStatusCode(line)); + // read the records following + while( (line=reader.readLine()) != null ) { + if(line.equals(DELIM)) { + // end of record + response.addRecord(record); + record = new HashMap<String, String>(); + } else { + // regular part of a record + String[] elem = line.split("=", 2); + record.put(elem[0], elem[1]); + } + } + } catch (IOException e) { + logError("The Wimba Classroom response could not parsed. Raw response: " + rawResponse, e); + } + + return response; + } + + /** + * @param line + */ + private int getStatusCode(String line) { + int code = StatusCode.UNDEFINED.getCode(); + String extracted = line.split(" ", 2)[0]; + if(extracted != null && !extracted.isEmpty()) { + try { + code = Integer.parseInt(extracted); + } catch(NumberFormatException e) { + // nothing to do since code is pre-set + } + } + return code; + } + + private String sendRequest(Map<String, String> parameters) { + URL url = createRequestUrl(parameters); + HttpURLConnection urlConn; + + try { + urlConn = (HttpURLConnection) url.openConnection(); + // setup url connection + urlConn.setDoOutput(true); + urlConn.setUseCaches(false); + urlConn.setInstanceFollowRedirects(false); + // add content type + urlConn.setRequestProperty("Content-Type", CONTENT_TYPE); + // add cookie information + if(cookie != null) urlConn.setRequestProperty("Cookie", cookie); + + // send request + urlConn.connect(); + + // detect redirect + int code = urlConn.getResponseCode(); + boolean moved = code == HttpURLConnection.HTTP_MOVED_PERM | code == HttpURLConnection.HTTP_MOVED_TEMP; + if(moved) { + String location = urlConn.getHeaderField("Location"); + List<String> headerVals = urlConn.getHeaderFields().get("Set-Cookie"); + for(String val : headerVals) { + if(val.startsWith(COOKIE)) cookie = val; + } + url = createRedirectUrl(location); + urlConn = (HttpURLConnection) url.openConnection(); + urlConn.setRequestProperty("Cookie", cookie); + } + + // read response + BufferedReader input = new BufferedReader(new InputStreamReader(urlConn.getInputStream())); + StringBuilder response= new StringBuilder(); + String line; + while( (line = input.readLine()) != null ) response.append(line).append("\n"); + input.close(); + + if(isLogDebugEnabled()) logDebug("Response: " + response); + + return response.toString(); + } catch (IOException e) { + logError("Sending request to Wimba Classroom failed. Request: " + url.toString(), e); + return ""; + } + } + + private URL createRedirectUrl(String location) { + UriBuilder ubu = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port).path(location); + + URL url = null; + try { + url = ubu.build().toURL(); + } catch (Exception e) { + logWarn("Error while creating redirect URL for Wimba Classroom request.", e); + // try to build the URL in a naiv way below + } + if(url == null) { + // error case, try the naiv way + try { + String urlString = new String(protocol + "://" + baseUrl + ":" + port + location); + url = new URL(urlString); + } catch (MalformedURLException e) { + logError("Error while creating URL for Wimba Classroom request. Please check the configuration!", e); + } + } + + if(isLogDebugEnabled()) logDebug("Redirect: " + url); + + return url; + } + + private URL createRequestUrl(Map<String, String> parameters) { + UriBuilder ubu = UriBuilder.fromUri(protocol + "://" + baseUrl).port(port).path("admin").path("api").path("api.pl"); + + for(String key : parameters.keySet()) { + ubu.queryParam(key, parameters.get(key)); + } + + URL url = null; + try { + url = ubu.build().toURL(); + } catch (Exception e) { + logWarn("Error while creating URL for Wimba Classroom request.", e); + // try to build the URL in a naiv way below + } + if(url == null) { + // error case, try the naiv way + try { + StringBuilder sb = new StringBuilder(protocol + "://" + baseUrl + ":" + port + "/admin/api/api.pl"); + if(!parameters.isEmpty()) sb.append("?"); + for(String key : parameters.keySet()) { + sb.append(key + "=" + parameters.get(key) + "&"); + } + sb.replace(sb.length(), sb.length(), ""); + url = new URL(sb.toString()); + } catch (MalformedURLException e) { + logError("Error while creating URL for Wimba Classroom request. Please check the configuration!", e); + } + } + + if(isLogDebugEnabled()) logDebug("Request: " + url); + + return url; + } + + private void handleError(StatusCode status, Throwable cause) { + logError("Request to Wimba Classroom returned error: " + status.getCode() + " (" + status.name() + ")", cause); + } + + private String param(boolean bool) { + return bool ? "1" : "0"; + } + + @Override + public Map<String, String> getTemplates() { + // no support for templating meetings + return Collections.emptyMap(); + } + + //////////////////////////// + // setters used by spring // + //////////////////////////// + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public void setPort(int port) { + this.port = port; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public void setAdminLogin(String adminLogin) { + this.adminLogin = adminLogin; + } + + public void setAdminPassword(String adminPassword) { + this.adminPassword = adminPassword; + } + + public void setDefaultConfig(WimbaClassroomConfiguration config) { + defaultConfig = config; + defaultConfig.setProviderId(providerId); + } + + ///////////////////////////// + // getters used internally // + ///////////////////////////// + + protected String getProtocol() { + return protocol; + } + + protected int getPort() { + return port; + } + + protected String getBaseUrl() { + return baseUrl; + } + + protected String getAdminLogin() { + return adminLogin; + } + + protected String getAdminPassword() { + return adminPassword; + } + + public WimbaClassroomConfiguration getDefaultConfig() { + return defaultConfig; + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaConfigController.java b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaConfigController.java new file mode 100644 index 00000000000..b34cd836b84 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaConfigController.java @@ -0,0 +1,113 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.wimba; + +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.media.RedirectMediaResource; + +/** + * + * Description:<br> + * Config controller to adjust settings of Wimba Classroom, e.g. for usage in course editor + * + * <P> + * Initial Date: 06.01.2011 <br> + * @author skoeber + */ +public class WimbaConfigController extends BasicController { + + private static String COMMAND_OPEN_ROOMSETTINGS = WimbaClassroomProvider.TARGET_OPEN_ROOMSETTINGS; + private static String COMMAND_OPEN_MEDIASETTINGS = WimbaClassroomProvider.TARGET_OPEN_MEDIASETTINGS; + + // GUI + private VelocityContainer editVC; + private WimbaEditForm editForm; + private Link openRoomSettings, openMediaSettings; + + private WimbaClassroomProvider wimba; + private WimbaClassroomConfiguration config; + private String roomId; + + protected WimbaConfigController(UserRequest ureq, WindowControl wControl, String roomId, WimbaClassroomProvider wimba, WimbaClassroomConfiguration config) { + super(ureq, wControl); + this.wimba = wimba; + this.config = config; + this.roomId = roomId; + + editVC = createVelocityContainer("edit"); + editForm = new WimbaEditForm(ureq, wControl, config); + listenTo(editForm); + editVC.put("editForm", editForm.getInitialComponent()); + + openRoomSettings = LinkFactory.createButton(COMMAND_OPEN_ROOMSETTINGS, editVC, this); + openMediaSettings = LinkFactory.createButton(COMMAND_OPEN_MEDIASETTINGS, editVC, this); + openRoomSettings.setTarget("_blank"); + openMediaSettings.setTarget("_blank"); + + putInitialPanel(editVC); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == editForm) { + fireEvent(ureq, event); + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == openRoomSettings | source == openMediaSettings) { + boolean exists = wimba.existsClassroom(roomId, config); + if(exists) { + openWimbaUrl(ureq, ((Link)source).getCommand()); + } else { + // normally this case should not occure, but be failsafe and show msg + getWindowControl().setError("error.no.room"); + } + return; + } + } + + private void openWimbaUrl(UserRequest ureq, String target) { + boolean success = wimba.createModerator(ureq.getIdentity(), roomId); + if(success) { + wimba.login(ureq.getIdentity(), null); + String url = wimba.createServiceUrl(target, roomId); + RedirectMediaResource rmr = new RedirectMediaResource(url); + ureq.getDispatchResult().setResultingMediaResource(rmr); + } else { + // could not create moderator or update the rights + getWindowControl().setError(translate("error.update.rights")); + return; + } + } + + @Override + protected void doDispose() { + if(editForm != null) { + removeAsListenerAndDispose(editForm); + editForm = null; + } + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaDisplayController.java b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaDisplayController.java new file mode 100644 index 00000000000..c4c74e55c8d --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaDisplayController.java @@ -0,0 +1,388 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.wimba; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +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.table.DefaultColumnDescriptor; +import org.olat.core.gui.components.table.StaticColumnDescriptor; +import org.olat.core.gui.components.table.TableController; +import org.olat.core.gui.components.table.TableDataModel; +import org.olat.core.gui.components.table.TableEvent; +import org.olat.core.gui.components.table.TableGuiConfiguration; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.media.RedirectMediaResource; +import org.olat.core.gui.translator.Translator; + +/** + * + * Description:<br> + * Display controller for Wimba Classroom usage at run-time + * + * <P> + * Initial Date: 06.01.2011 <br> + * @author skoeber + */ +public class WimbaDisplayController extends BasicController { + + private static String COMMAND_JOIN_MODERATOR = "cmd.join.moderator"; + private static String COMMAND_JOIN_LEARNER = "cmd.join.learner"; + private static String COMMAND_JOIN_GUEST = "cmd.join.guest"; + private static String COMMAND_START_MEETING = "cmd.start.meeting"; + private static String COMMAND_CLOSE_MEETING = "cmd.close.meeting"; + private static String COMMAND_UNCLOSE_MEETING = "cmd.unclose.meeting"; + private static String COMMAND_UPDATE_MEETING = "cmd.update.meeting"; + private static String COMMAND_OPEN_MANAGEROOM = WimbaClassroomProvider.TARGET_OPEN_MANAGEROOM; + private static String COMMAND_OPEN_POLLRESULTS = WimbaClassroomProvider.TARGET_OPEN_POLLRESULTS; + private static String COMMAND_OPEN_TRACKING = WimbaClassroomProvider.TARGET_OPEN_TRACKING; + private static String COMMAND_OPEN_WIZARD = WimbaClassroomProvider.TARGET_OPEN_WIZARD; + private static String COMMAND_OPEN_RECORDING = "cmd.open.recording"; + private static String COMMAND_TOGGLESTATUS_RECORDING = "cmd.togglestatus.recording"; + private static String COMMAND_DELETE_RECORDING = "cmd.delete.recording"; + + // GUI + private VelocityContainer runVC; + private Link joinModerator, joinLearner, joinGuest; + private Link startMeeting, closeMeeting, uncloseMeeting, updateMeeting; + private Link openWizard, openManageRoom, openPollResults, openTracking; + private TableController recTable; + + //data + private WimbaClassroomConfiguration config; + private String name, description; + private String roomId; + private RecordingsTableModel recTableModel; + + private WimbaClassroomProvider wimba; + + protected WimbaDisplayController(UserRequest ureq, WindowControl wControl, String roomId, String name, String description, boolean isModerator, + WimbaClassroomConfiguration config, WimbaClassroomProvider provider) { + super(ureq, wControl); + this.wimba = provider; + this.config = config; + this.roomId = roomId; + this.name = name; + this.description = description; + + runVC = createVelocityContainer("run"); + + boolean exists = wimba.existsClassroom(roomId, config); + boolean closed = exists ? wimba.isPreviewMode(roomId, false) : false; + boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); + boolean showGuestLink = config.isGuestAccessAllowed(); + + if(isModerator) { + wimba.createModerator(ureq.getIdentity(), roomId); + } else if(!isGuest) { + wimba.createUser(ureq.getIdentity(), roomId); + } + + String guestLink = wimba.createClassroomGuestUrl(roomId, ureq.getIdentity(), config).toString(); + + joinModerator = LinkFactory.createButton(COMMAND_JOIN_MODERATOR, runVC, this); + joinLearner = LinkFactory.createButton(COMMAND_JOIN_LEARNER, runVC, this); + joinGuest = LinkFactory.createButton(COMMAND_JOIN_GUEST, runVC, this); + startMeeting = LinkFactory.createButton(COMMAND_START_MEETING, runVC, this); + closeMeeting = LinkFactory.createButton(COMMAND_CLOSE_MEETING, runVC, this); + uncloseMeeting = LinkFactory.createButton(COMMAND_UNCLOSE_MEETING, runVC, this); + updateMeeting = LinkFactory.createButton(COMMAND_UPDATE_MEETING, runVC, this); + // set target to be able to open new browser window on event + joinGuest.setTarget("_blank"); + joinLearner.setTarget("_blank"); + joinModerator.setTarget("_blank"); + + joinLearner.setEnabled(!closed); + joinGuest.setEnabled(!closed); + + runVC.contextPut("exists", exists); + runVC.contextPut("closed", closed); + runVC.contextPut("isGuest", isGuest); + runVC.contextPut("isModerator", isModerator); + runVC.contextPut("showGuestLink", showGuestLink); + runVC.contextPut("guestLink", guestLink); + + // convenience links + openWizard = LinkFactory.createButton(COMMAND_OPEN_WIZARD, runVC, this); + openWizard.setTarget("_blank"); + + // moderator links + if(isModerator) { + openManageRoom = LinkFactory.createButton(COMMAND_OPEN_MANAGEROOM, runVC, this); + openPollResults = LinkFactory.createButton(COMMAND_OPEN_POLLRESULTS, runVC, this); + openTracking = LinkFactory.createButton(COMMAND_OPEN_TRACKING, runVC, this); + openManageRoom.setTarget("_blank"); + openPollResults.setTarget("_blank"); + openTracking.setTarget("_blank"); + } + + // show recordings + TableGuiConfiguration tableConfig = new TableGuiConfiguration(); + tableConfig.setColumnMovingOffered(false); + tableConfig.setDisplayRowCount(true); + tableConfig.setPageingEnabled(true); + tableConfig.setTableEmptyMessage(translate("table.recordings.empty")); + recTable = new TableController(tableConfig, ureq, wControl, getTranslator()); + DefaultColumnDescriptor recCol = new DefaultColumnDescriptor("table.recordings.name", 1, COMMAND_OPEN_RECORDING, getLocale()); + recCol.setIsPopUpWindowAction(true, null); + recTable.addColumnDescriptor(recCol); + if(isModerator) { + recTable.addColumnDescriptor(new DefaultColumnDescriptor("table.recordings.status", 2, COMMAND_TOGGLESTATUS_RECORDING, getLocale())); + recTable.addColumnDescriptor(new StaticColumnDescriptor(COMMAND_DELETE_RECORDING, "table.recordings.action", translate("table.recordings.delete"))); + } + Map<String, String> recordings = wimba.listRecordings(roomId); + List<String> keys = new ArrayList<String>(recordings.keySet()); + Collections.sort(keys); + List<Object[]> recordingData = new ArrayList<Object[]>(); + for(String key : keys) { + String title = recordings.get(key); + boolean preview = wimba.isPreviewMode(key, true); + /* + * for moderators: add all recordings and show actions + * for users: add only recordings that are not in preview mode + */ + if(!preview | isModerator) recordingData.add(new Object[] {key, title, preview}); + } + recTableModel = new RecordingsTableModel(recordingData, getTranslator()); + recTable.setTableDataModel(recTableModel); + listenTo(recTable); + runVC.put("recordingsTable", recTable.getInitialComponent()); + + putInitialPanel(runVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == startMeeting) { + boolean success = wimba.createClassroom(roomId, name, description, null, null, config); + if(success) { + runVC.contextPut("exists", true); + runVC.setDirty(true); + } else { + getWindowControl().setError(translate("error.create.room")); + } + } else if(source == joinModerator) { + boolean success = wimba.existsClassroom(roomId, config); + // update rights for user to moderate meeting + if(success) { + success = wimba.createModerator(ureq.getIdentity(), roomId); + } else { + // room not found, should not appear + getWindowControl().setError(translate("error.no.room")); + return; + } + // login the user as moderator + if(success) { + success = wimba.login(ureq.getIdentity(), null); + } else { + // could not create moderator or update the rights + getWindowControl().setError(translate("error.update.rights")); + return; + } + // redirect to the meeting + if(success) { + joinMeeting(ureq, false); + } else { + // login failed + getWindowControl().setError(translate("error.no.login")); + return; + } + return; + } else if(source == joinLearner) { + joinMeeting(ureq, false); + return; + } else if(source == joinGuest) { + joinMeeting(ureq, true); + return; + } else if(source == closeMeeting) { + boolean success = wimba.setPreviewMode(roomId, true, false); + if(success) { + runVC.contextPut("closed", true); + runVC.setDirty(true); + } else { + // closing failed + getWindowControl().setError(translate("error.remove.room")); + } + } else if(source == uncloseMeeting) { + boolean success = wimba.setPreviewMode(roomId, false, false); + if(success) { + runVC.contextPut("closed", false); + runVC.setDirty(true); + } else { + // reopen failed + getWindowControl().setError(translate("error.update.room")); + } + } else if(source == updateMeeting) { + boolean success = wimba.updateClassroom(roomId, name, description, null, null, config); + if(success) { + getWindowControl().setInfo(translate("success.update.room")); + } else { + // update failed + getWindowControl().setError(translate("error.update.room")); + } + } else if(source == openWizard | source == openManageRoom | source == openPollResults | source == openTracking) { + openWimbaUrl(ureq, ((Link)source).getCommand()); + return; + } + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == recTable) { + if(event instanceof TableEvent) { + TableEvent tEvent = (TableEvent)event; + String action = tEvent.getActionId(); + int row = tEvent.getRowId(); + String key = (String) recTable.getTableDataModel().getValueAt(row, 0); + if(action.equals(COMMAND_OPEN_RECORDING)) { + wimba.login(ureq.getIdentity(), null); + URL url = wimba.createClassroomRecordingUrl(key, ureq.getIdentity(), config); + RedirectMediaResource rmr = new RedirectMediaResource(url.toString()); + ureq.getDispatchResult().setResultingMediaResource(rmr); + return; + } else if(action.equals(COMMAND_TOGGLESTATUS_RECORDING)) { + Object[] entry = (Object[]) recTable.getTableDataModel().getObject(row); + Boolean preview = (Boolean) entry[2]; + if(wimba.setPreviewMode(key, !preview, true)) { + String text = preview ? "table.recordings.unclose.success" : "table.recordings.close.success"; + getWindowControl().setInfo(translate(text)); + recTableModel.toggleStatus(row); + recTable.modelChanged(); + runVC.setDirty(true); + } else { + String text = preview ? "table.recordings.unclose.error" : "table.recordings.close.error"; + getWindowControl().setError(translate(text)); + } + } else if(action.equals(COMMAND_DELETE_RECORDING)) { + if(wimba.removeClassroomRecording(key, config)) { + getWindowControl().setInfo(translate("table.recordings.delete.success")); + recTableModel.removeRecording(row); + recTable.modelChanged(); + runVC.setDirty(true); + } else { + getWindowControl().setError(translate("table.recordings.delete.error")); + } + } + } + } + } + + private void openWimbaUrl(UserRequest ureq, String target) { + boolean success = wimba.createModerator(ureq.getIdentity(), roomId); + if(success) { + wimba.login(ureq.getIdentity(), null); + String url = wimba.createServiceUrl(target, roomId); + RedirectMediaResource rmr = new RedirectMediaResource(url); + ureq.getDispatchResult().setResultingMediaResource(rmr); + } else { + // could not create moderator or update the rights + getWindowControl().setError(translate("error.update.rights")); + return; + } + } + + private void joinMeeting(UserRequest ureq, boolean guest) { + URL url; + if(guest) { + url = wimba.createClassroomGuestUrl(roomId, ureq.getIdentity(), config); + } else { + boolean success = wimba.login(ureq.getIdentity(), null); + // no success, maybe the user account doesn't exist, create it and try the login again + if(!success) wimba.createUser(ureq.getIdentity(), roomId); + wimba.login(ureq.getIdentity(), null); + url = wimba.createClassroomUrl(roomId, ureq.getIdentity(), config); + } + RedirectMediaResource rmr = new RedirectMediaResource(url.toString()); + ureq.getDispatchResult().setResultingMediaResource(rmr); + } + + @Override + protected void doDispose() { + // nothing to dispose + } + +} + +class RecordingsTableModel implements TableDataModel { + + private List<Object[]> recordings = new ArrayList<Object[]>(); + private Translator translator; + + public RecordingsTableModel(List<Object[]> recordings, Translator translator) { + this.recordings.addAll(recordings); + this.translator = translator; + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public int getRowCount() { + return recordings.size(); + } + + @Override + public Object getValueAt(int row, int col) { + Object[] recording = this.recordings.get(row); + switch(col) { + case 0: return recording[col];//key + case 1: return recording[col];//title + case 2: //action string depending on status + Boolean preview = (Boolean) recording[col]; + return preview + ? translator.translate("table.recordings.unclose") + : translator.translate("table.recordings.close"); + default: return recording[col]; + } + } + + @Override + public Object getObject(int row) { + return recordings.get(row); + } + + @Override + public void setObjects(List objects) { + this.recordings = objects; + } + + @Override + public Object createCopyWithEmptyList() { + // not used + return ""; + } + + public void removeRecording(int row) { + this.recordings.remove(row); + } + + public void toggleStatus(int row) { + Boolean status = (Boolean) this.recordings.get(row)[2]; + this.recordings.get(row)[2] = !status; + } +} +//</OLATCE-103> diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaEditForm.java b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaEditForm.java new file mode 100644 index 00000000000..7c19cc7a6c5 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaEditForm.java @@ -0,0 +1,78 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.wimba; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.editor.NodeEditController; + +/** + * + * Description:<br> + * Edit form for date list and Wimba Classroom specific configuration + * + * <P> + * Initial Date: 12.01.2011 <br> + * @author skoeber + */ +public class WimbaEditForm extends FormBasicController { + + // GUI + private FormSubmit submit; + private MultipleSelectionElement multiSelectOptions; + private static String OPTION_GUEST_ACCESS = "vc.guest.access"; + + // data + private WimbaClassroomConfiguration config; + + public WimbaEditForm(UserRequest ureq, WindowControl wControl, WimbaClassroomConfiguration config) { + super(ureq, wControl, FormBasicController.LAYOUT_VERTICAL); + this.config = config; + + initForm(this.flc, this, ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + + // options + String[] optionKeys = new String[] {OPTION_GUEST_ACCESS}; + String[] optionVals = new String[] {translate(OPTION_GUEST_ACCESS)}; + multiSelectOptions = uifactory.addCheckboxesVertical("vc.options", "vc.options.label", formLayout, optionKeys, optionVals, null, 1); + multiSelectOptions.select(OPTION_GUEST_ACCESS, config.isGuestAccessAllowed()); + multiSelectOptions.showLabel(false); + + submit = new FormSubmit("subm", "submit"); + + formLayout.add(submit); + } + + @Override + protected void formOK(UserRequest ureq) { + config.setGuestAccessAllowed(multiSelectOptions.getSelectedKeys().contains(OPTION_GUEST_ACCESS)); + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + + @Override + protected void doDispose() { + // nothing to dispose + } + +} +//<OLATCE-103> diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaResponse.java b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaResponse.java new file mode 100644 index 00000000000..ac7ddfddd15 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/WimbaResponse.java @@ -0,0 +1,92 @@ +//<OLATCE-103> +/** + * + * BPS Bildungsportal Sachsen GmbH<br> + * Bahnhofstrasse 6<br> + * 09111 Chemnitz<br> + * Germany<br> + * + * Copyright (c) 2005-2011 by BPS Bildungsportal Sachsen GmbH<br> + * http://www.bps-system.de<br> + * + * All rights reserved. + */ +package de.bps.course.nodes.vc.provider.wimba; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * + * Description:<br> + * Response object for API calls to Wimba Classroom + * + * <P> + * Initial Date: 07.01.2011 <br> + * @author skoeber + */ +public class WimbaResponse { + + private StatusCode status = StatusCode.UNDEFINED; + private List<Map<String, String>> records = new ArrayList<Map<String,String>>(); + + /* + * several constructors for efficiency + */ + + public WimbaResponse() { + // + } + + public WimbaResponse(StatusCode status) { + setStatus(status); + } + + public WimbaResponse(int code) { + setStatus(code); + } + + protected StatusCode getStatus() { + return status; + } + + protected void setStatus(StatusCode status) { + this.status = status; + } + + protected void setStatus(int code) { + this.status = StatusCode.getStatus(code); + } + + protected void addRecord(Map<String, String> record) { + records.add(record); + } + + protected List<Map<String, String>> getRecords() { + return records; + } + + /** + * Will return the first value found, but there may be more than this one + * @param key + * @return value found or <code>null</code> + */ + protected String findRecord(String key) { + String value = null; + for(Map<String, String> record : records) { + value = record.get(key); + } + return value; + } + + protected boolean hasRecords() { + return !records.isEmpty(); + } + + protected int numRecords() { + return records.size(); + } + +} +//</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/_chelp/ced-vc-config.html b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_chelp/ced-vc-config.html new file mode 100644 index 00000000000..adc858afa76 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_chelp/ced-vc-config.html @@ -0,0 +1,5 @@ +<!-- <OLATCE-103> --> +<p>$r.translate("chelp.vc1")</p> +<p>$r.translate("chelp.vc2")</p> +<p>$r.translate("chelp.vc3")</p> +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/edit.html b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/edit.html new file mode 100644 index 00000000000..3950a4b95d3 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/edit.html @@ -0,0 +1,7 @@ +<!-- <OLATCE-103> --> +$r.contextHelpWithWrapper("de.bps.course.nodes.vc.provider.wimba","ced-vc-config.html","chelp.hover.vc.config") +$r.render("cmd.open.roomsettings") +<!-- $r.render("cmd.open.mediasettings") --> + +$r.render("editForm") +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/run.html b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/run.html new file mode 100644 index 00000000000..9458271c7a5 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_content/run.html @@ -0,0 +1,58 @@ +<!-- <OLATCE-103> --> +#if($exists) + #if($closed) + <p class="b_warning">$r.translate("meeting.status")</p> + #end + + #if($isModerator) + #if(!$closed) + <p>$r.translate("cmd.join.moderator.intro")</p> + #end + <p> + $r.render("cmd.join.moderator") + </p> + $r.render("recordingsTable") + <div class="b_info"> + <p><strong>$r.translate("title.options")</strong></p> + #if($showGuestLink) + <p>$r.translate("description.guestlink") + <pre>$guestLink</pre></p> + <br/> + #end + #if($closed) + ##<p class="b_warning">$r.translate("meeting.status")</p> + $r.render("cmd.unclose.meeting") + #else + $r.render("cmd.close.meeting") + #end + ##$r.render("cmd.update.meeting") + $r.render("cmd.open.manageroom") + $r.render("cmd.open.pollresults") + ##$r.render("cmd.open.tracking") + </div> + #else + #if($isGuest && $showGuestLink) + $r.render("cmd.join.guest") + #else + #if(!$closed) + <p>$r.translate("cmd.join.learner.intro")</p> + #end + $r.render("cmd.join.learner") + <p> + $r.render("recordingsTable") + </p> + #end + #end +#else + #if($isModerator) + <p>$r.translate("cmd.start.meeting.intro")</p> + $r.render("cmd.start.meeting") + #else + <p>$r.translate("cmd.join.learner.wait")</p> + #end +#end +<div class="b_info"> +<p>$r.translate("cmd.open.wizard.intro")</p> +$r.render("cmd.open.wizard") +</div> +<!-- </OLATCE-103> --> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_de.properties b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_de.properties new file mode 100644 index 00000000000..c61a7a56f83 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_de.properties @@ -0,0 +1,61 @@ +#<OLATCE-103> +vc.options.label=Optionen +vc.guest.access=Gastzugang erlauben + +cmd.close.meeting=Virtuelles Klassenzimmer schlie\u00dfen +cmd.unclose.meeting=Virtuelles Klassenzimmer \u00f6ffnen +cmd.delete.recording=Aufzeichnung l\u00f6schen +cmd.join.guest=Meeting als Gast betreten +cmd.join.moderator=Meeting moderieren +cmd.join.moderator.intro=Das virtuelle Klassenzimmer ist ge\u00f6ffnet. Sie k\u00f6nnen das Meeting moderieren. Teilnehmer k\u00f6nnen dem Meeting ohne Zustimmung eines Moderators ggf. nicht beitreten. +cmd.join.learner=Klassenzimmer betreten +cmd.join.learner.intro=Das virtuelle Klassenzimmer kann betreten werden. +cmd.join.learner.wait=Das virtuelle Klassenzimmer wurde noch nicht er\u00f6ffnet. +cmd.open.manageroom=Rauminhalte verwalten +cmd.open.pollresults=Umfrageergebnisse einsehen +cmd.open.recording=Aufzeichnung starten +cmd.open.tracking=Tracking +cmd.open.roomsettings=Raumkonfiguration +cmd.open.mediasettings=Medienkonfiguration +cmd.open.wizard=Setup-Wizard starten +cmd.open.wizard.intro=Benutzen Sie den Setup Wizard um sicherzustellen, dass ihr Computer f\u00fcr die Benutzung von Wimba Classroom vorbereitet ist. +cmd.start.meeting=Meeting starten +cmd.start.meeting.intro=Das virtuelle Klassenzimmer wurde noch nicht ge\u00f6ffnet. Teilnehmer k\u00f6nnen einem geplanten Meeting je nach Konfiguration ggf. noch nicht beitreten. +cmd.update.meeting=Konfiguration synchronisieren +description.guestlink=Der Gastzugang f\u00fcr dieses Meeting ist erlaubt. Folgender Link erm\u00f6glicht den direkten Zugang zum virtuellen Klassenzimmer als Gast: +title.options=Erweiterte Optionen f\u00fcr Kursautoren + +table.recordings.empty=Es sind keine Aufzeichnungen f\u00fcr dieses virtuelle Klassenzimmer verf\u00fcgbar. +table.recordings.name=Aufzeichnungen +table.recordings.action=Aktion +table.recordings.close=Aufzeichnung schlie\u00dfen +table.recordings.close.error=Die Aufzeichnung konnte nicht geschlossen werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +table.recordings.close.success=Aufzeichnung erfolgreich geschlossen +table.recordings.delete=Aufzeichnung entfernen +table.recordings.delete.error=Die Aufzeichnung konnte nicht entfernt werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +table.recordings.delete.success=Aufzeichnung erfolgreich entfernt +table.recordings.unclose=Aufzeichnung \u00f6ffnen +table.recordings.unclose.error=Die Aufzeichnung konnte nicht ge\u00f6ffnet werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +table.recordings.unclose.success=Aufzeichnung erfolgreich ge\u00f6ffnet +table.recordings.status=Status \u00e4ndern + +error.no.room=Das virtuelle Klassenzimmer konnte nicht geladen werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.no.login=Die Anmeldung war nicht erfolgreich. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.create.room=Das virtuelle Klassenzimmer konnte nicht erzeugt werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.close.room=Das virtuelle Klassenzimmer konnte nicht geschlossen werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.remove.room=Das virtuelle Klassenzimmer konnte nicht entfernt werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.update.room=Das virtuelle Klassenzimmer konnte nicht mit den aktuellen Einstellungen synchronisiert werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. +error.update.rights=Ihnen konnten nicht die notwendigen Rechte f\u00fcr dieses Meeting zugewiesen werden. Bitte wenden Sie sich an Ihren Betreuer oder Ihre Systemadministratoren. + +meeting.status=Das Meeting ist geschlossen und kann momentan nicht von Studenten betreten werden. + +sync.meeting.title=Virtuelles Klassenzimmer synchronisieren +sync.meeting.text=Sie haben die Einstellungen f\u00fcr diese virtuelle Klassenzimmer ge\u00e4ndert, nachdem dieses er\u00f6ffnet wurde. Die \u00c4nderungen werden erst wirksam, wenn Sie das virtuelle Klassenzimmer mit den neuen Einstellungen synchronisieren. Die Einstellungen k\u00f6nnen auch jederzeit direkt im Kurs synchronisiert werden. M\u00f6chten Sie die Einstellungen jetzt synchronisieren? +success.update.room=Das virtuelle Klassenzimmer wurde erfolgreich mit den aktuellen Einstellungen synchronisiert. + +chelp.ced-vc-config.title=Erweiterte Konfiguration (Wimba Classroom) +chelp.hover.vc.config=Hilfe zur erweiterten Konfiguration des Virtuellen Klassenzimmers +chelp.vc1=Mit Klick auf "Raumkonfiguration" \u00f6ffnet sich ein neues Fenster, in welchem Sie Ihr Wimba-Meeting vorkonfigurieren k\u00f6nnen. Aktivieren oder deaktivieren Sie z.B. den Chat, das eBoard oder die Archivierung des Meetings. +chelp.vc2=Mit Klick auf "Medienkonfiguration" werden Ihnen technische Daten zu Ihrem Wimba-Meeting angezeigt. +chelp.vc3=Sollen Nutzer auch anonym an Meetings teilnehmen d\u00fcrfen, aktivieren Sie bitte die Option "Gastzugang erlauben". +#</OLATCE-103> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_en.properties b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_en.properties new file mode 100644 index 00000000000..7f0571cccae --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_i18n/LocalStrings_en.properties @@ -0,0 +1,60 @@ +#<OLATCE-103 OLATCE-619> +vc.options.label=Options +vc.guest.access=Allow access as guest (anonymously) + +cmd.close.meeting=Close virtual classroom +cmd.unclose.meeting=Reopen virtual classroom +cmd.delete.recording=Delete recording +cmd.join.guest=Enter virtual classroom as guest +cmd.join.moderator=Moderate virtual classroom +cmd.join.moderator.intro=The virtual classroom has been opened. You may now enter the classroom and moderate the meeting. Participants might not be able to join the meeting until they are granted access by a moderator. +cmd.join.learner=Enter virtual classroom +cmd.join.learner.intro=You can join the virtual classroom. +cmd.join.learner.wait=The virtual classroom has not been opened, yet. Please contact your tutor for any further information. +cmd.open.manageroom=Manage room content +cmd.open.pollresults=View survey results +cmd.open.recording=Start recording +cmd.open.tracking=Tracking +cmd.open.roomsettings=Room settings +cmd.open.mediasettings=Media settings +cmd.open.wizard=Start setup wizard +cmd.open.wizard.intro=Run the setup wizard to make sure that your computer is able to use the Wimba Classroom. +cmd.start.meeting=Open virtual classroom +cmd.start.meeting.intro=The virtual classroom has not been opened, yet. Participants might not be able to enter the classroom for a meeting. +cmd.update.meeting=Syncronize configuration +description.guestlink=Guest access for this meeting is allowed. The following link gives a direct access to the virtual classroom as a guest: +title.options=Additional information and options for authors + +table.recordings.empty=There are no recordings available for this virtual classroom. +table.recordings.name=Recordings +table.recordings.action=Action +table.recordings.close=Close recording +table.recordings.close.error=Sorry! An error occured while removing the recording. Please inform your support or system administrator. +table.recordings.close.success=Closed recording successfully +table.recordings.delete=Remove recording +table.recordings.delete.error=Sorry! An error occured while removing the recording. Please inform your support or system administrator. +table.recordings.delete.success=Recording removed successfully +table.recordings.unclose=Open recording +table.recordings.unclose.error=Sorry! An error occured while opening the recording. Please inform your support or system administrator. +table.recordings.unclose.success=Recording opened successfully +table.recordings.status=Change status + +error.no.room=Sorry! An error occured while loading the virtual classroom. Please inform your tutor or system administrator. +error.no.login=Login was not successfull. Please, contact your tutor or system administrator for any further questions. +error.create.room=Sorry! An error occured while creating the virtual classroom. Please inform your tutor or system administrator. +error.remove.room=Sorry! An error occured while removing the virtual class room. Please inform your tutor or system administrator. +error.update.room=Sorry! An error occured while synchronizing the virtual class room with the actual configuration. Please inform your tutor or system administrator. +error.update.rights=Sorry! An error occured while granting access to the virtual classroom. Please inform your tutor or system administrator. + +meeting.status=This virtual classroom ist currently closed. Students cannot access this room. + +sync.meeting.title=Synchronize virtual classroom +sync.meeting.text=You have changed the configuration of the virtual classroom after it had been started. The changes will be taken into account only after having synchronized the virtual class room with the new configuration. You can synchronize the configuration at any time directly from the course run. Do you want to synchronize now? +success.update.room=The virtual classroom has been synchronized successfully with its new configuration. + +chelp.ced-vc-config.title=Extended configuration (Wimba Classroom) +chelp.hover.vc.config=Help about configuration of the virtual classroom +chelp.vc1=Choose "Room settings" to pre-configure your wimba meeting room. Activate or deactivate chat, eboard, archiving etc. +chelp.vc2=Choose "Media settings" to get technical information about your wimba meeting. +chelp.vc3=If users may participate anonymously, please activate "Allow access as guest". +#</OLATCE-103 OLATCE-619> \ No newline at end of file diff --git a/src/main/java/de/bps/course/nodes/vc/provider/wimba/_spring/wimbaContext.xml b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_spring/wimbaContext.xml new file mode 100644 index 00000000000..38168a031f2 --- /dev/null +++ b/src/main/java/de/bps/course/nodes/vc/provider/wimba/_spring/wimbaContext.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + + <bean id="wimbaDefaultConfig" class="de.bps.course.nodes.vc.provider.wimba.WimbaClassroomConfiguration"> + <!-- general --> + <property name="createMeetingImmediately" value="true" /> + <!-- Wimba specific --> + <property name="toolsToStudentsEnabled" value="true" /> + <property name="studentsSpeakAllowed" value="true" /> + <property name="studentsVideoAllowed" value="true" /> + <property name="userStatusIndicatorsEnabled" value="true" /> + <property name="userStatusUpdateInChatEnabled" value="true" /> + <property name="studentEBoardEnabled" value="true" /> + <property name="breakoutRoomsEnabled" value="true" /> + <property name="archivingEnabled" value="true" /> + <property name="autoOpenNewArchives" value="false" /> + <property name="appshareEnabled" value="true" /> + <property name="powerPointImportEnabled" value="true" /> + <property name="guestAccessAllowed" value="false" /> + <property name="studentsChatAllowed" value="true" /> + <property name="studentsPrivateChatAllowed" value="true" /> + </bean> + +</beans> \ No newline at end of file diff --git a/src/main/webapp/static/themes/default/images/olat/vc.png b/src/main/webapp/static/themes/default/images/olat/vc.png new file mode 100755 index 0000000000000000000000000000000000000000..031996014fd3d08214da326888523fe9743454bd GIT binary patch literal 412 zcmV;N0b~A&P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzK1oDDRCwB~lfO>GFc8KsjvZP+tRNWK zSV45`##pI4Vqrx>eI8z+sycMz&%gul3TzbvOofWrPQrK21yvcML}2Nsb1uH`&X-G6 z6b1d;ilV5_wC>wnoSZ%H;2W%Q;1iZvi_fzzNWeDOLC`KN)5HEipA3i3*Q3!`8$(-3 zLJ2THYfVb2kR{79^3IX9mQvubznjlzT!lU!3?6PLlX0Hst*^?$Ps`<F5P-7slBMm6 z(F=|X0?u0Hk==RUTEL(Jjtc^~SL`5=b`U^=xF2yTkfxym+d&}a^N6biLIt)%4|FL| zR`9i28UAFwe!s&FG;tisko;+onnw8yt{1jVqpa!<;T+t+^}$x2sp-d4Qr14W01I#k zUd1lN&n55iho}<%_C;P^f_rc(ylw2+e+7<smMVcC0R{jj{IpxEkdI9O0000<MNUMn GLSTX)9JG}H literal 0 HcmV?d00001 diff --git a/src/main/webapp/static/themes/default/images/olat/vc_over.png b/src/main/webapp/static/themes/default/images/olat/vc_over.png new file mode 100755 index 0000000000000000000000000000000000000000..6f0b2a7d003f9795554cdb7218d25d3f5da82b56 GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucLCF%=h?3y^w370~qEv=}#LT=BJwMkFg)(D3 zQ$4c_&1<88rrLYDIEGmGZ=Gc8eK<j+wOfAnjGV52Jl+cycl&K??rwc_!*Pv*So^M$ zm-{)k9d%NW;9O>^dFkj$AaG@GZ_-mzn)vAJoaxsc9dC#5*z7rZ*3#eTPRJ{J{mUl( z?Z0>rSFn74z}Bi=z^L>fn88M%L2if3JE=R3`zEDtx+#2n+tQ0UU5_muRnD8k);Oni zq08NMErBX;{oHHri(fB&y5j1j!&R|HJLfECX^qPDKDOgVa8qwqX?4%O4a-?xPd&xQ z`kpgko~>$x_Vn_|I$ws*Z!`~Pt=#gx?cn7E1EVvWm$RHc`sg1w8-Jk<m=|VOFOd9U z4#TsX!A<u1m;6_Et@U<3ch-kL_W6585#0+6&wp*Ho2p>Kn07!Wf-!dkL*Cbu>F1sw z)S2-|=}3atew`QV^&YC!)_$5-znN3);FskOm`_VzkG`Ux{Qwx=44$rjF6*2UngCg{ B!dL(R literal 0 HcmV?d00001 -- GitLab