From f042993b8b91a348430ed4e6eab3b36f792effa7 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Tue, 9 Jun 2020 16:05:49 +0200
Subject: [PATCH] OO-4734: webcam only layout option for BigBlueButton meeting

---
 .../bigbluebutton/BigBlueButtonMeeting.java   |  4 ++
 .../BigBlueButtonMeetingLayoutEnum.java       | 46 +++++++++++++++
 .../manager/BigBlueButtonManagerImpl.java     | 14 ++++-
 .../manager/BigBlueButtonMeetingDAO.java      |  2 +
 .../model/BigBlueButtonMeetingImpl.java       | 28 +++++++++
 .../EditBigBlueButtonMeetingController.java   | 58 ++++++++++++++++++-
 .../ui/_i18n/LocalStrings_de.properties       |  3 +
 .../ui/_i18n/LocalStrings_en.properties       |  3 +
 .../database/mysql/alter_15_0_x_to_15_1_0.sql |  4 ++
 .../database/mysql/setupDatabase.sql          |  1 +
 .../oracle/alter_15_0_x_to_15_1_0.sql         |  4 ++
 .../database/oracle/setupDatabase.sql         |  1 +
 .../postgresql/alter_15_0_x_to_15_1_0.sql     |  4 ++
 .../database/postgresql/setupDatabase.sql     |  1 +
 14 files changed, 170 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingLayoutEnum.java

diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java
index 0534275e6e6..48a8f07bfa7 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java
@@ -54,6 +54,10 @@ public interface BigBlueButtonMeeting extends ModifiedInfo, CreateInfo {
 	
 	public void setWelcome(String welcome);
 	
+	public BigBlueButtonMeetingLayoutEnum getMeetingLayout();
+	
+	public void setMeetingLayout(BigBlueButtonMeetingLayoutEnum layout);
+	
 	public boolean isPermanent();
 	
 	public void setPermanent(boolean permanent);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingLayoutEnum.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingLayoutEnum.java
new file mode 100644
index 00000000000..6c7da91b254
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingLayoutEnum.java
@@ -0,0 +1,46 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.bigbluebutton;
+
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 9 juin 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum BigBlueButtonMeetingLayoutEnum {
+	
+	standard,
+	webcam;
+	
+	public static final BigBlueButtonMeetingLayoutEnum secureValueOf(String val) {
+		BigBlueButtonMeetingLayoutEnum layout = BigBlueButtonMeetingLayoutEnum.standard;
+		if(StringHelper.containsNonWhitespace(val)) {
+			for(BigBlueButtonMeetingLayoutEnum l:values()) {
+				if(l.name().equals(val)) {
+					layout = l;
+				}
+			}
+		}
+		return layout;
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
index 25d827b755d..3cca3082d0b 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
@@ -60,6 +60,7 @@ import org.olat.group.BusinessGroupService;
 import org.olat.group.DeletableGroupData;
 import org.olat.modules.bigbluebutton.BigBlueButtonManager;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingLayoutEnum;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
 import org.olat.modules.bigbluebutton.BigBlueButtonModule;
 import org.olat.modules.bigbluebutton.BigBlueButtonRecording;
@@ -735,12 +736,21 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
 		}
 
 		BigBlueButtonUriBuilder uriBuilder = getUriBuilder(server);
-		return uriBuilder
+		uriBuilder
 			.operation("join")
 			.parameter("meetingID", meeting.getMeetingId())
 			.parameter("fullName", getFullName(identity))
 			.parameter("password", password)
-			.optionalParameter("userID", userId)
+			.optionalParameter("userID", userId);
+		
+		if(BigBlueButtonMeetingLayoutEnum.webcam.equals(meeting.getMeetingLayout())) {
+			uriBuilder
+				.optionalParameter("userdata-bbb_auto_swap_layout", "true")
+				.optionalParameter("userdata-bbb_auto_share_webcam", "true")
+				.optionalParameter("userdata-bbb_show_participants_on_login", "false");
+		}
+		
+		return uriBuilder
 			.build()
 			.toString();
 	}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
index abe4aa8ac96..0e6a8ad7811 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
@@ -33,6 +33,7 @@ import org.olat.core.commons.persistence.QueryBuilder;
 import org.olat.core.util.StringHelper;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingLayoutEnum;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
 import org.olat.modules.bigbluebutton.BigBlueButtonServer;
 import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl;
@@ -62,6 +63,7 @@ public class BigBlueButtonMeetingDAO {
 		meeting.setMeetingId(UUID.randomUUID().toString());
 		meeting.setAttendeePassword(UUID.randomUUID().toString());
 		meeting.setModeratorPassword(UUID.randomUUID().toString());
+		meeting.setMeetingLayout(BigBlueButtonMeetingLayoutEnum.standard);
 		
 		meeting.setEntry(entry);
 		meeting.setSubIdent(subIdent);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java
index 146482c3d05..538468e1cfd 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java
@@ -37,6 +37,7 @@ import org.olat.core.id.Persistable;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupImpl;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingLayoutEnum;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
 import org.olat.modules.bigbluebutton.BigBlueButtonServer;
 import org.olat.repository.RepositoryEntry;
@@ -90,6 +91,9 @@ public class BigBlueButtonMeetingImpl implements Persistable, BigBlueButtonMeeti
 	
 	@Column(name="b_permanent", nullable=false, insertable=true, updatable=true)
 	private boolean permanent;
+	
+	@Column(name="b_layout", nullable=false, insertable=true, updatable=true)
+	private String layout;
 
 	@Column(name="b_meeting_id", nullable=false, insertable=true, updatable=false)
 	private String meetingId;
@@ -200,6 +204,30 @@ public class BigBlueButtonMeetingImpl implements Persistable, BigBlueButtonMeeti
 	public void setWelcome(String welcome) {
 		this.welcome = welcome;
 	}
+	
+	
+
+	public String getLayout() {
+		return layout;
+	}
+
+	public void setLayout(String layout) {
+		this.layout = layout;
+	}
+
+	@Override
+	public BigBlueButtonMeetingLayoutEnum getMeetingLayout() {
+		return BigBlueButtonMeetingLayoutEnum.secureValueOf(layout);
+	}
+
+	@Override
+	public void setMeetingLayout(BigBlueButtonMeetingLayoutEnum meetingLayout) {
+		if(meetingLayout == null) {
+			layout = BigBlueButtonMeetingLayoutEnum.standard.name();
+		} else {
+			layout = meetingLayout.name();
+		}
+	}
 
 	@Override
 	public boolean isPermanent() {
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
index baa43adcda0..e0a24061f8c 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
@@ -42,6 +42,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.bigbluebutton.BigBlueButtonManager;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeetingLayoutEnum;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate;
 import org.olat.modules.bigbluebutton.BigBlueButtonModule;
 import org.olat.modules.bigbluebutton.BigBlueButtonTemplatePermissions;
@@ -65,6 +66,7 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 	private DateChooser startDateEl;
 	private DateChooser endDateEl;
 	private SingleSelection templateEl;
+	private SingleSelection layoutEl;
 
 	private final Mode mode;
 	private final String subIdent;
@@ -171,7 +173,27 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 		if(!templateSelected && templatesKeys.length > 0) {
 			templateEl.select(templatesKeys[0], true);
 		}
-		
+
+		KeyValues layoutKeyValues = new KeyValues();
+		layoutKeyValues.add(KeyValues.entry(BigBlueButtonMeetingLayoutEnum.standard.name(), translate("layout.standard")));
+		if(isWebcamLayoutAvailable()) {
+			layoutKeyValues.add(KeyValues.entry(BigBlueButtonMeetingLayoutEnum.webcam.name(), translate("layout.webcam")));
+		}
+		layoutEl = uifactory.addDropdownSingleselect("meeting.layout", "meeting.layout", formLayout,
+				layoutKeyValues.keys(), layoutKeyValues.values());
+		boolean layoutSelected = false;
+		String selectedLayout = meeting == null ? BigBlueButtonMeetingLayoutEnum.standard.name() : meeting.getMeetingLayout().name();
+		for(String layoutKey:layoutKeyValues.keys()) {
+			if(layoutKey.equals(selectedLayout)) {
+				layoutEl.select(layoutKey, true);
+				layoutSelected = true;
+			}
+		}
+		if(!layoutSelected) {
+			layoutEl.select(BigBlueButtonMeetingLayoutEnum.standard.name(), true);
+		}
+		layoutEl.setVisible(layoutEl.getKeys().length > 1);
+	
 		openCalLink = uifactory.addFormLink("calendar.open", formLayout);
 		openCalLink.setIconLeftCSS("o_icon o_icon-fw o_icon_calendar");
 		updateTemplateInformations();
@@ -217,6 +239,14 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 		}
 	}
 	
+	private boolean isWebcamLayoutAvailable() {
+		if(!templateEl.isOneSelected()) {
+			return true;
+		}
+		BigBlueButtonMeetingTemplate template = getSelectedTemplate();
+		return template != null && (template.getWebcamsOnlyForModerator() == null || !template.getWebcamsOnlyForModerator().booleanValue());
+	}
+	
 	private void updateTemplateInformations() {
 		templateEl.setExampleKey(null, null);
 		if(templateEl.isOneSelected()) {
@@ -234,6 +264,24 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 		}
 	}
 	
+	private void updateLayoutSelection() {
+		boolean webcamAvailable = isWebcamLayoutAvailable();
+		if(webcamAvailable && layoutEl.getKeys().length == 1) {
+			KeyValues layoutKeyValues = new KeyValues();
+			layoutKeyValues.add(KeyValues.entry(BigBlueButtonMeetingLayoutEnum.standard.name(), translate("layout.standard")));
+			layoutKeyValues.add(KeyValues.entry(BigBlueButtonMeetingLayoutEnum.webcam.name(), translate("layout.webcam")));
+			layoutEl.setKeysAndValues(layoutKeyValues.keys(), layoutKeyValues.values(), null);
+		} else if(!webcamAvailable && layoutEl.getKeys().length > 1) {
+			layoutEl.select(BigBlueButtonMeetingLayoutEnum.standard.name(), true);
+			
+			KeyValues layoutKeyValues = new KeyValues();
+			layoutKeyValues.add(KeyValues.entry(BigBlueButtonMeetingLayoutEnum.standard.name(), translate("layout.standard")));
+			layoutEl.setKeysAndValues(layoutKeyValues.keys(), layoutKeyValues.values(), null);
+		}
+		
+		layoutEl.setVisible(layoutEl.getKeys().length > 1);
+	}
+	
 	private void doOpenCalendar(UserRequest ureq) {
 		removeAsListenerAndDispose(calCtr);
 		removeAsListenerAndDispose(cmc);
@@ -428,6 +476,7 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
 		if(templateEl == source) {
 			updateTemplateInformations();
+			updateLayoutSelection();
 		} else if (openCalLink == source) {
 			doOpenCalendar(ureq);
 		}
@@ -465,6 +514,13 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 			meeting.setFollowupTime(followupTime);
 		}
 		
+		if(layoutEl.isVisible() && layoutEl.isOneSelected()) {
+			BigBlueButtonMeetingLayoutEnum layout = BigBlueButtonMeetingLayoutEnum.secureValueOf(layoutEl.getSelectedKey());
+			meeting.setMeetingLayout(layout);
+		} else {
+			meeting.setMeetingLayout(BigBlueButtonMeetingLayoutEnum.standard);
+		}
+		
 		bigBlueButtonManager.updateMeeting(meeting);
 
 		fireEvent(ureq, Event.DONE_EVENT);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
index c7c2064e0a3..c7e1d7a82a7 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
@@ -56,6 +56,8 @@ error.too.long.time=Zeit ist zu lang. Es sind maximal {0} Minuten erlaubt.
 error.url.invalid=Ung\u00FCltige Serveradresse
 filter.all.instances=Alle OpenOlats
 filter.this.instance=Dieses OpenOlat
+layout.standard=Standard
+layout.webcam=Webcam Termin
 meeting.create.intro=Der Online-Termin wurde vom Betreuer noch nicht er\u00F6ffnet. Teilnehmer k\u00F6nnen den Raum noch nicht betreten.
 meeting.day=Datum des Meetings
 meeting.deleted=Das Meeting wurde erfolgreich gel\u00F6scht.
@@ -65,6 +67,7 @@ meeting.ended=Der Online-Termin wurde bereits beendet.
 meeting.followupTime=Nachlaufzeit (Min.)
 meeting.go.button=Zum Online-Termin Raum
 meeting.join.button=Meeting beitreten
+meeting.layout=Darstellung
 meeting.leadTime=Vorlaufzeit (Min.)
 meeting.leadTime.explain=Die Vorlaufzeit ist nur f\u00FCr Moderatoren relevant.
 meeting.name=Name
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
index 048640dc3bf..dbb087ec630 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
@@ -56,6 +56,8 @@ error.too.long.time=Time is too long. It is limited to {0} minutes.
 error.url.invalid=Invalid server URL
 filter.all.instances=All OpenOlats
 filter.this.instance=This OpenOlat
+layout.standard=Standard
+layout.webcam=Webcam meeting
 meeting.create.intro=The meeting has not yet been started by the coach. Participants are not able to enter the classroom.
 meeting.day=Date of the meeting
 meeting.deleted=The meeting was successfully deleted.
@@ -65,6 +67,7 @@ meeting.ended=The online-meeting has already ended.
 meeting.followupTime=Follow-up (min.)
 meeting.go.button=Go to the onlin-meeting room
 meeting.join.button=Join the meeting
+meeting.layout=Layout
 meeting.leadTime=Prep time (min.)
 meeting.leadTime.explain=Prep time is only relevant for moderators.
 meeting.name=Name
diff --git a/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql b/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql
index 6a7674dc2cc..6398b3f4d96 100644
--- a/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql
+++ b/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql
@@ -4,6 +4,10 @@ alter table o_bs_identity add column inactivationemaildate datetime;
 alter table o_bs_identity add column deletionemaildate datetime;
 
 
+-- BigBlueButton
+alter table o_bbb_meeting add column b_layout varchar(16) default 'standard';
+
+
 -- Appointments
 create table o_ap_topic (
    id bigint not null auto_increment,
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index b982291c057..fdf1d8bcba3 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1204,6 +1204,7 @@ create table o_bbb_meeting (
    b_name varchar(128) not null,
    b_description varchar(2000) default null,
    b_welcome mediumtext,
+   b_layout varchar(16) default 'standard',
    b_permanent bool default false not null,
    b_start_date datetime default null,
    b_leadtime bigint default 0 not null,
diff --git a/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql b/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql
index 262344bd5be..d4f33987e8c 100644
--- a/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql
+++ b/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql
@@ -4,6 +4,10 @@ alter table o_bs_identity add inactivationemaildate date;
 alter table o_bs_identity add deletionemaildate date;
 
 
+-- BigBlueButton
+alter table o_bbb_meeting add b_layout varchar2(16) default 'standard';
+
+
 -- Appointments
 create table o_ap_topic (
    id number(20) generated always as identity,
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 71a69295b52..4af0597b96e 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1267,6 +1267,7 @@ create table o_bbb_meeting (
    b_name varchar(128) not null,
    b_description varchar(2000) default null,
    b_welcome CLOB,
+   b_layout varchar2(16) default 'standard',
    b_permanent number default 0 not null,
    b_start_date timestamp default null,
    b_leadtime number(20) default 0 not null,
diff --git a/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql b/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql
index fbcdb8fd1b6..57add3c0a5f 100644
--- a/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql
+++ b/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql
@@ -4,6 +4,10 @@ alter table o_bs_identity add column inactivationemaildate timestamp;
 alter table o_bs_identity add column deletionemaildate timestamp;
 
 
+-- BigBlueButton
+alter table o_bbb_meeting add column b_layout varchar(16) default 'standard';
+
+
 -- Appointments
 create table o_ap_topic (
    id bigserial,
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index fddc7eff2aa..a4ea3b26c7d 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1226,6 +1226,7 @@ create table o_bbb_meeting (
    b_name varchar(128) not null,
    b_description varchar(2000) default null,
    b_welcome text,
+   b_layout varchar(16) default 'standard',
    b_permanent bool default false not null,
    b_start_date timestamp default null,
    b_leadtime bigint default 0 not null,
-- 
GitLab