diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/GuestsApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/GuestsApp.scala index 823e999df6479933b7012feb2995815847eb55c1..9c97de077ff75ffaa9099f6932114bf8c52e8a53 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/GuestsApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/GuestsApp.scala @@ -7,6 +7,7 @@ trait GuestsApp extends GetGuestsWaitingApprovalReqMsgHdlr with GuestsWaitingApprovedMsgHdlr with GuestWaitingLeftMsgHdlr with SetGuestPolicyMsgHdlr + with SetGuestLobbyMessageMsgHdlr with GetGuestPolicyReqMsgHdlr { this: MeetingActor => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala index 88339acf05d697f4a8df22fbb3944a0909e0a430..67eedfcb48e3ad4f13143ac85d63ef833273b037 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/GuestsWaiting.scala @@ -24,6 +24,9 @@ object GuestsWaiting { guests.setGuestPolicy(policy) } + def setGuestLobbyMessage(guests: GuestsWaiting, message: String): Unit = { + guests.setGuestLobbyMessage(message) + } } class GuestsWaiting { @@ -31,6 +34,8 @@ class GuestsWaiting { private var guestPolicy = GuestPolicy(GuestPolicyType.ALWAYS_ACCEPT, SystemUser.ID) + private var guestLobbyMessage = "" + private def toVector: Vector[GuestWaiting] = guests.values.toVector private def save(user: GuestWaiting): GuestWaiting = { @@ -49,6 +54,8 @@ class GuestsWaiting { def getGuestPolicy(): GuestPolicy = guestPolicy def setGuestPolicy(policy: GuestPolicy) = guestPolicy = policy + + def setGuestLobbyMessage(message: String) = guestLobbyMessage = message } case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, avatar: String, authenticated: Boolean, registeredOn: Long) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala index 497e168bb55f0b5361503b0ca6ca23b1b02dae3f..fcb3dcf9251aa78d8738bec997a0ce7eb68d6504 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala @@ -95,6 +95,8 @@ class ReceivedJsonMsgHandlerActor( routeGenericMsg[SetGuestPolicyCmdMsg](envelope, jsonNode) case GetGuestPolicyReqMsg.NAME => routeGenericMsg[GetGuestPolicyReqMsg](envelope, jsonNode) + case SetGuestLobbyMessageCmdMsg.NAME => + routeGenericMsg[SetGuestLobbyMessageCmdMsg](envelope, jsonNode) // Users case GetUsersMeetingReqMsg.NAME => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index 98172c3dae184bd36ba881a241eecbf410ba1998..066935415b2bd03af600cd1137f9b8c09743181d 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -443,6 +443,7 @@ class MeetingActor( // Guests case m: GetGuestsWaitingApprovalReqMsg => handleGetGuestsWaitingApprovalReqMsg(m) case m: SetGuestPolicyCmdMsg => handleSetGuestPolicyMsg(m) + case m: SetGuestLobbyMessageCmdMsg => handleSetGuestLobbyMessageMsg(m) case m: GuestsWaitingApprovedMsg => handleGuestsWaitingApprovedMsg(m) case m: GuestWaitingLeftMsg => handleGuestWaitingLeftMsg(m) case m: GetGuestPolicyReqMsg => handleGetGuestPolicyReqMsg(m) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala index 7c08cdfdfd49facb7e99a966b5a8934f213451b0..608f9c2d8436ae3ba2dda5b9bece331c8dc5a9d5 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala @@ -139,6 +139,8 @@ class AnalyticsActor extends Actor with ActorLogging { case m: GuestsWaitingForApprovalEvtMsg => logMessage(msg) case m: SetGuestPolicyCmdMsg => logMessage(msg) case m: GuestPolicyChangedEvtMsg => logMessage(msg) + case m: SetGuestLobbyMessageCmdMsg => logMessage(msg) + case m: GuestLobbyMessageChangedEvtMsg => logMessage(msg) // System case m: ClientToServerLatencyTracerMsg => traceMessage(msg) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/guests/SetGuestLobbyMessageMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/guests/SetGuestLobbyMessageMsgHdlr.scala new file mode 100755 index 0000000000000000000000000000000000000000..dc5ad18a1dd089e89a9556287d16af9716379d5a --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/guests/SetGuestLobbyMessageMsgHdlr.scala @@ -0,0 +1,31 @@ +package org.bigbluebutton.core2.message.handlers.guests + +import org.bigbluebutton.common2.msgs.SetGuestLobbyMessageCmdMsg +import org.bigbluebutton.core.models.{ GuestsWaiting } +import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } +import org.bigbluebutton.core2.message.senders.MsgBuilder +import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } +import org.bigbluebutton.core.running.MeetingActor + +trait SetGuestLobbyMessageMsgHdlr extends RightsManagementTrait { + this: MeetingActor => + + val liveMeeting: LiveMeeting + val outGW: OutMsgRouter + + def handleSetGuestLobbyMessageMsg(msg: SetGuestLobbyMessageCmdMsg): Unit = { + if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) { + val meetingId = liveMeeting.props.meetingProp.intId + val reason = "No permission to set guest lobby message in meeting." + PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting) + } else { + GuestsWaiting.setGuestLobbyMessage(liveMeeting.guestsWaiting, msg.body.message) + val event = MsgBuilder.buildGuestLobbyMessageChangedEvtMsg( + liveMeeting.props.meetingProp.intId, + msg.header.userId, + msg.body.message + ) + outGW.send(event) + } + } +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala index 82f228180cd73256510f607f4604c81ea963f437..cebf928937da4b7fa477419c4ed6c1d2c729232c 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala @@ -16,6 +16,17 @@ object MsgBuilder { BbbCommonEnvCoreMsg(envelope, event) } + def buildGuestLobbyMessageChangedEvtMsg(meetingId: String, userId: String, message: String): BbbCommonEnvCoreMsg = { + val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId) + val envelope = BbbCoreEnvelope(GuestLobbyMessageChangedEvtMsg.NAME, routing) + val header = BbbClientMsgHeader(GuestLobbyMessageChangedEvtMsg.NAME, meetingId, userId) + + val body = GuestLobbyMessageChangedEvtMsgBody(message) + val event = GuestLobbyMessageChangedEvtMsg(header, body) + + BbbCommonEnvCoreMsg(envelope, event) + } + def buildGuestApprovedEvtMsg(meetingId: String, userId: String, status: String, approvedBy: String): BbbCommonEnvCoreMsg = { val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId) val envelope = BbbCoreEnvelope(GuestApprovedEvtMsg.NAME, routing) diff --git a/akka-bbb-fsesl/build.sbt b/akka-bbb-fsesl/build.sbt index 99fca10117ecae327ad80b65566028539b327c80..6fbc2f7941670b9f8e614140513ecd62ae5e1eee 100755 --- a/akka-bbb-fsesl/build.sbt +++ b/akka-bbb-fsesl/build.sbt @@ -79,4 +79,4 @@ daemonGroup in Linux := group javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=/etc/bigbluebutton/bbb-fsesl-akka.conf", "-Dlogback.configurationFile=conf/logback.xml") -debianPackageDependencies in Debian ++= Seq("java8-runtime-headless", "bash") +debianPackageDependencies in Debian ++= Seq("java8-runtime-headless", "bash", "bbb-freeswitch-core") diff --git a/akka-bbb-fsesl/src/debian/DEBIAN/postinst b/akka-bbb-fsesl/src/debian/DEBIAN/postinst new file mode 100644 index 0000000000000000000000000000000000000000..83db223d8e84a7b619733d17d167bbd2ca5e5b60 --- /dev/null +++ b/akka-bbb-fsesl/src/debian/DEBIAN/postinst @@ -0,0 +1,10 @@ +# +# Update ESL password to match the password from bbb-freeswitch-core, which is +# listed as a package dependency to ensure that it configures first. +# + +ESL_PASSWORD=$(xmlstarlet sel -t -m 'configuration/settings/param[@name="password"]' -v @value /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml) + +if [ -n "$ESL_PASSWORD" ]; then + sed -i "s/ClueCon/$ESL_PASSWORD/g" /etc/bigbluebutton/bbb-fsesl-akka.conf +fi diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala index fd438b24b8d605bab9689b6eb26621c97b9cf703..308c481f56e0e76cea6c09dfc21f317d58138ae5 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/GuestsMsgs.scala @@ -103,6 +103,26 @@ case class GuestPolicyChangedEvtMsg( ) extends BbbCoreMsg case class GuestPolicyChangedEvtMsgBody(policy: String, setBy: String) +/** + * Message from user to set the guest lobby message. + */ +object SetGuestLobbyMessageCmdMsg { val NAME = "SetGuestLobbyMessageCmdMsg" } +case class SetGuestLobbyMessageCmdMsg( + header: BbbClientMsgHeader, + body: SetGuestLobbyMessageCmdMsgBody +) extends StandardMsg +case class SetGuestLobbyMessageCmdMsgBody(message: String) + +/** + * Message sent to all clients that guest lobby message has been changed. + */ +object GuestLobbyMessageChangedEvtMsg { val NAME = "GuestLobbyMessageChangedEvtMsg" } +case class GuestLobbyMessageChangedEvtMsg( + header: BbbClientMsgHeader, + body: GuestLobbyMessageChangedEvtMsgBody +) extends BbbCoreMsg +case class GuestLobbyMessageChangedEvtMsgBody(message: String) + /** * Message from user to get the guest policy. */ diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java index 814736ff602308a1976dc87a2259286ac5b230d6..fad6f60344ca23bfedaf9f6495dab960ff98e8f5 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java @@ -61,6 +61,7 @@ import org.bigbluebutton.api.messaging.messages.CreateBreakoutRoom; import org.bigbluebutton.api.messaging.messages.CreateMeeting; import org.bigbluebutton.api.messaging.messages.EndMeeting; import org.bigbluebutton.api.messaging.messages.GuestPolicyChanged; +import org.bigbluebutton.api.messaging.messages.GuestLobbyMessageChanged; import org.bigbluebutton.api.messaging.messages.GuestStatusChangedEventMsg; import org.bigbluebutton.api.messaging.messages.GuestsStatus; import org.bigbluebutton.api.messaging.messages.IMessage; @@ -1101,6 +1102,8 @@ public class MeetingService implements MessageListener { processGuestStatusChangedEventMsg((GuestStatusChangedEventMsg) message); } else if (message instanceof GuestPolicyChanged) { processGuestPolicyChanged((GuestPolicyChanged) message); + } else if (message instanceof GuestLobbyMessageChanged) { + processGuestLobbyMessageChanged((GuestLobbyMessageChanged) message); } else if (message instanceof RecordChapterBreak) { processRecordingChapterBreak((RecordChapterBreak) message); } else if (message instanceof AddPad) { @@ -1125,6 +1128,13 @@ public class MeetingService implements MessageListener { } } + public void processGuestLobbyMessageChanged(GuestLobbyMessageChanged msg) { + Meeting m = getMeeting(msg.meetingId); + if (m != null) { + m.setGuestLobbyMessage(msg.message); + } + } + public void processAddPad(AddPad msg) { Meeting m = getMeeting(msg.meetingId); if (m != null) { diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java index 1393228d56674b7fe5ba1290d00b110eb6aa67fa..e2884db3445619a0dab5912b404e20d21cec0200 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java @@ -68,6 +68,7 @@ public class Meeting { private String defaultAvatarURL; private String defaultConfigToken; private String guestPolicy = GuestPolicy.ASK_MODERATOR; + private String guestLobbyMessage = ""; private Boolean authenticatedGuest = false; private boolean userHasJoined = false; private Map<String, String> pads; @@ -376,6 +377,14 @@ public class Meeting { return guestPolicy; } + public void setGuestLobbyMessage(String message) { + guestLobbyMessage = message; + } + + public String getGuestLobbyMessage() { + return guestLobbyMessage; + } + public void setAuthenticatedGuest(Boolean authGuest) { authenticatedGuest = authGuest; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/messages/GuestLobbyMessageChanged.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/messages/GuestLobbyMessageChanged.java new file mode 100755 index 0000000000000000000000000000000000000000..ca76c835e877b1a894edfedd61877e5b570609bb --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/messaging/messages/GuestLobbyMessageChanged.java @@ -0,0 +1,11 @@ +package org.bigbluebutton.api.messaging.messages; + +public class GuestLobbyMessageChanged implements IMessage { + public final String meetingId; + public final String message; + + public GuestLobbyMessageChanged(String meetingId, String message) { + this.meetingId = meetingId; + this.message = message; + } +} diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala index 7eb216f80f497677bae82e1fa9494eb9a1f4fa27..2676e1bdad6cdf96b529b68ae2e3812209bdf2d7 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala @@ -94,6 +94,8 @@ class ReceivedJsonMsgHdlrActor(val msgFromAkkaAppsEventBus: MsgFromAkkaAppsEvent route[GuestsWaitingApprovedEvtMsg](envelope, jsonNode) case GuestPolicyChangedEvtMsg.NAME => route[GuestPolicyChangedEvtMsg](envelope, jsonNode) + case GuestLobbyMessageChangedEvtMsg.NAME => + route[GuestLobbyMessageChangedEvtMsg](envelope, jsonNode) case AddPadEvtMsg.NAME => route[AddPadEvtMsg](envelope, jsonNode) case AddCaptionsPadsEvtMsg.NAME => diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala index 09fd1744a7250a3d2f49803f4b68b732b7eedaac..ac25f7610c30f77572dcbb1baec069bd9c3fe104 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/meeting/OldMeetingMsgHdlrActor.scala @@ -39,6 +39,7 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW) case m: PresentationUploadTokenSysPubMsg => handlePresentationUploadTokenSysPubMsg(m) case m: GuestsWaitingApprovedEvtMsg => handleGuestsWaitingApprovedEvtMsg(m) case m: GuestPolicyChangedEvtMsg => handleGuestPolicyChangedEvtMsg(m) + case m: GuestLobbyMessageChangedEvtMsg => handleGuestLobbyMessageChangedEvtMsg(m) case m: AddCaptionsPadsEvtMsg => handleAddCaptionsPadsEvtMsg(m) case m: AddPadEvtMsg => handleAddPadEvtMsg(m) case m: RecordingChapterBreakSysMsg => handleRecordingChapterBreakSysMsg(m) @@ -52,6 +53,10 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW) olgMsgGW.handle(new GuestPolicyChanged(msg.header.meetingId, msg.body.policy)) } + def handleGuestLobbyMessageChangedEvtMsg(msg: GuestLobbyMessageChangedEvtMsg): Unit = { + olgMsgGW.handle(new GuestLobbyMessageChanged(msg.header.meetingId, msg.body.message)) + } + def handleAddPadEvtMsg(msg: AddPadEvtMsg): Unit = { olgMsgGW.handle(new AddPad(msg.header.meetingId, msg.body.padId, msg.body.readOnlyId)) } diff --git a/bigbluebutton-html5/.gitignore b/bigbluebutton-html5/.gitignore index 3a833eaf4f800bfece09c1cd77ed8cd112714fc2..b8bd4a6520ec7750e5c0d810ffc70d79e9e3e98a 100755 --- a/bigbluebutton-html5/.gitignore +++ b/bigbluebutton-html5/.gitignore @@ -3,5 +3,7 @@ npm-debug.log node_modules/ .meteor/dev_bundle tests/webdriverio/.testing-env +public/locales/de_DE.json +public/locales/ja_JP.json diff --git a/bigbluebutton-html5/imports/api/guest-users/server/methods.js b/bigbluebutton-html5/imports/api/guest-users/server/methods.js index dc7ccd93f0dea3c9a099a50a4b653a9c4b1734d2..f1dbcab3f7ea75c7ce149c8f1004a2f6ca31cf2e 100644 --- a/bigbluebutton-html5/imports/api/guest-users/server/methods.js +++ b/bigbluebutton-html5/imports/api/guest-users/server/methods.js @@ -1,8 +1,10 @@ import { Meteor } from 'meteor/meteor'; import allowPendingUsers from '/imports/api/guest-users/server/methods/allowPendingUsers'; import changeGuestPolicy from '/imports/api/guest-users/server/methods/changeGuestPolicy'; +import setGuestLobbyMessage from '/imports/api/guest-users/server/methods/setGuestLobbyMessage'; Meteor.methods({ allowPendingUsers, changeGuestPolicy, + setGuestLobbyMessage, }); diff --git a/bigbluebutton-html5/imports/api/guest-users/server/methods/setGuestLobbyMessage.js b/bigbluebutton-html5/imports/api/guest-users/server/methods/setGuestLobbyMessage.js new file mode 100644 index 0000000000000000000000000000000000000000..770ea415235e95c8c89260400628e1235e63f839 --- /dev/null +++ b/bigbluebutton-html5/imports/api/guest-users/server/methods/setGuestLobbyMessage.js @@ -0,0 +1,24 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; +import Logger from '/imports/startup/server/logger'; +import { extractCredentials } from '/imports/api/common/server/helpers'; + +const REDIS_CONFIG = Meteor.settings.private.redis; +const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; +const EVENT_NAME = 'SetGuestLobbyMessageCmdMsg'; + +export default function setGuestLobbyMessage(message) { + check(message, String); + + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(meetingId, String); + check(requesterUserId, String); + + const payload = { message }; + + Logger.info(`User=${requesterUserId} set guest lobby message to ${message}`); + + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); +} diff --git a/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js b/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js index 81f0ef206a42523e373fd5b1f58f316a19b870b6..e13e7c5f2da353bd9835b1067186ad5c617d7c25 100644 --- a/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js @@ -4,6 +4,7 @@ import handleGetAllMeetings from './handlers/getAllMeetings'; import handleMeetingEnd from './handlers/meetingEnd'; import handleMeetingDestruction from './handlers/meetingDestruction'; import handleMeetingLocksChange from './handlers/meetingLockChange'; +import handleGuestLobbyMessageChanged from './handlers/guestLobbyMessageChanged'; import handleUserLockChange from './handlers/userLockChange'; import handleRecordingStatusChange from './handlers/recordingStatusChange'; import handleRecordingTimerChange from './handlers/recordingTimerChange'; @@ -21,5 +22,6 @@ RedisPubSub.on('RecordingStatusChangedEvtMsg', handleRecordingStatusChange); RedisPubSub.on('UpdateRecordingTimerEvtMsg', handleRecordingTimerChange); RedisPubSub.on('WebcamsOnlyForModeratorChangedEvtMsg', handleChangeWebcamOnlyModerator); RedisPubSub.on('GetLockSettingsRespMsg', handleMeetingLocksChange); +RedisPubSub.on('GuestLobbyMessageChangedEvtMsg', handleGuestLobbyMessageChanged); RedisPubSub.on('MeetingTimeRemainingUpdateEvtMsg', handleTimeRemainingUpdate); RedisPubSub.on('SelectRandomViewerRespMsg', handleSelectRandomViewer); diff --git a/bigbluebutton-html5/imports/api/meetings/server/handlers/guestLobbyMessageChanged.js b/bigbluebutton-html5/imports/api/meetings/server/handlers/guestLobbyMessageChanged.js new file mode 100644 index 0000000000000000000000000000000000000000..e6bca30ecfedd3f20b4286fa0b6e79086a198f72 --- /dev/null +++ b/bigbluebutton-html5/imports/api/meetings/server/handlers/guestLobbyMessageChanged.js @@ -0,0 +1,11 @@ +import setGuestLobbyMessage from '../modifiers/setGuestLobbyMessage'; +import { check } from 'meteor/check'; + +export default function handleGuestLobbyMessageChanged({ body }, meetingId) { + const { message } = body; + + check(meetingId, String); + check(message, String); + + return setGuestLobbyMessage(meetingId, message); +} diff --git a/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingEnd.js b/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingEnd.js index d1e7feed8d546b4232ade9bd321621ccb6bd5fa6..48e29bef3607186454f0afcc22cc73c7f927af49 100644 --- a/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingEnd.js +++ b/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingEnd.js @@ -4,11 +4,15 @@ import Meetings from '/imports/api/meetings'; import Breakouts from '/imports/api/breakouts'; import Logger from '/imports/startup/server/logger'; -export default function handleMeetingEnd({ body }) { +export default function handleMeetingEnd({ header, body }) { check(body, Object); const { meetingId } = body; check(meetingId, String); + check(header, Object); + const { userId } = header; + check(userId, String); + const cb = (err, num, meetingType) => { if (err) { Logger.error(`${meetingType} ending error: ${err}`); @@ -20,7 +24,7 @@ export default function handleMeetingEnd({ body }) { }; Meetings.update({ meetingId }, - { $set: { meetingEnded: true } }, + { $set: { meetingEnded: true, meetingEndedBy: userId } }, (err, num) => { cb(err, num, 'Meeting'); }); Breakouts.update({ parentMeetingId: meetingId }, diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js index a17bec63f2b851c3e0b2c3305371cda8fff4ce4d..a0dea94a0a5b9fd6913cfe5d6865837ca1a7ec33 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js @@ -148,6 +148,7 @@ export default function addMeeting(meeting) { meetingId, meetingEnded, publishedPoll: false, + guestLobbyMessage: '', randomlySelectedUser: '', }, flat(newMeeting, { safe: true, diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/setGuestLobbyMessage.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/setGuestLobbyMessage.js new file mode 100644 index 0000000000000000000000000000000000000000..08650ed27d4475259c813eb62b01e25eb28e9037 --- /dev/null +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/setGuestLobbyMessage.js @@ -0,0 +1,28 @@ +import Meetings from '/imports/api/meetings'; +import Logger from '/imports/startup/server/logger'; +import { check } from 'meteor/check'; + +export default function setGuestLobbyMessage(meetingId, guestLobbyMessage) { + check(meetingId, String); + check(guestLobbyMessage, String); + + const selector = { + meetingId, + }; + + const modifier = { + $set: { + guestLobbyMessage, + }, + }; + + try { + const { numberAffected } = Meetings.upsert(selector, modifier); + + if (numberAffected) { + Logger.verbose(`Set guest lobby message meetingId=${meetingId} guestLobbyMessage=${guestLobbyMessage}`); + } + } catch (err) { + Logger.error(`Setting guest lobby message: ${err}`); + } +} diff --git a/bigbluebutton-html5/imports/startup/client/intl.jsx b/bigbluebutton-html5/imports/startup/client/intl.jsx index 0d44ca2d67522c69ce2c6bcefdf016f449523f8f..c56fac96baa696728478bc9e458028b0b98e5016 100644 --- a/bigbluebutton-html5/imports/startup/client/intl.jsx +++ b/bigbluebutton-html5/imports/startup/client/intl.jsx @@ -63,6 +63,7 @@ class IntlStartup extends Component { fetchLocalizedMessages(locale, init = false) { const url = `./locale?locale=${locale}&init=${init}`; + const localesPath = 'locales'; this.setState({ fetching: true }, () => { fetch(url) @@ -73,11 +74,65 @@ class IntlStartup extends Component { return response.json(); }) - .then(({ messages, normalizedLocale }) => { - const dasherizedLocale = normalizedLocale.replace('_', '-'); - this.setState({ messages, fetching: false, normalizedLocale: dasherizedLocale }, () => { - IntlStartup.saveLocale(dasherizedLocale); - }); + .then(({ normalizedLocale, regionDefaultLocale }) => { + fetch(`${localesPath}/${DEFAULT_LANGUAGE}.json`) + .then((response) => { + if (!response.ok) { + return Promise.reject(); + } + return response.json(); + }) + .then((messages) => { + if (regionDefaultLocale !== '') { + fetch(`${localesPath}/${regionDefaultLocale}.json`) + .then((response) => { + if (!response.ok) { + return Promise.resolve(); + } + return response.json(); + }) + .then((regionDefaultMessages) => { + messages = Object.assign(messages, regionDefaultMessages); + return messages; + }); + } + + if (normalizedLocale !== DEFAULT_LANGUAGE && normalizedLocale !== regionDefaultLocale) { + fetch(`${localesPath}/${normalizedLocale}.json`) + .then((response) => { + if (!response.ok) { + return Promise.reject(); + } + return response.json(); + }) + .then((localeMessages) => { + messages = Object.assign(messages, localeMessages); + return messages; + }) + .catch(() => { + normalizedLocale = (regionDefaultLocale) || DEFAULT_LANGUAGE; + const dasherizedLocale = normalizedLocale.replace('_', '-'); + this.setState({ messages, fetching: false, normalizedLocale: dasherizedLocale }, () => { + IntlStartup.saveLocale(normalizedLocale); + }); + }); + } + + return messages; + }) + .then((messages) => { + const dasherizedLocale = normalizedLocale.replace('_', '-'); + this.setState({ messages, fetching: false, normalizedLocale: dasherizedLocale }, () => { + IntlStartup.saveLocale(dasherizedLocale); + }); + }) + .catch(() => { + normalizedLocale = DEFAULT_LANGUAGE; + const dasherizedLocale = normalizedLocale.replace('_', '-'); + this.setState({ fetching: false, normalizedLocale: dasherizedLocale }, () => { + IntlStartup.saveLocale(normalizedLocale); + }); + }); }) .catch(() => { this.setState({ fetching: false, normalizedLocale: null }, () => { diff --git a/bigbluebutton-html5/imports/startup/server/index.js b/bigbluebutton-html5/imports/startup/server/index.js index 7ad0f829dd88987b6f9723b24333698fd15aefb7..5f6508e6a32a1c6ac6d7cf89defff9dae9f2a45d 100755 --- a/bigbluebutton-html5/imports/startup/server/index.js +++ b/bigbluebutton-html5/imports/startup/server/index.js @@ -12,7 +12,16 @@ import Redis from './redis'; import setMinBrowserVersions from './minBrowserVersion'; let guestWaitHtml = ''; -const AVAILABLE_LOCALES = fs.readdirSync('assets/app/locales'); + +const env = Meteor.isDevelopment ? 'development' : 'production'; + +const meteorRoot = fs.realpathSync(`${process.cwd()}/../`); + +const applicationRoot = (env === 'development') + ? fs.realpathSync(`${meteorRoot}'/../../../../public/locales/`) + : fs.realpathSync(`${meteorRoot}/../programs/web.browser/app/locales/`); + +const AVAILABLE_LOCALES = fs.readdirSync(`${applicationRoot}`); const FALLBACK_LOCALES = JSON.parse(Assets.getText('config/fallbackLocales.json')); process.on('uncaughtException', (err) => { @@ -27,7 +36,6 @@ process.on('uncaughtException', (err) => { Meteor.startup(() => { const APP_CONFIG = Meteor.settings.public.app; - const env = Meteor.isDevelopment ? 'development' : 'production'; const CDN_URL = APP_CONFIG.cdn; const instanceId = parseInt(process.env.INSTANCE_ID, 10) || 1; @@ -106,40 +114,39 @@ Meteor.startup(() => { session.bbbFixApplied = true; } }, 5000); + } + if (CDN_URL.trim()) { + // Add CDN + BrowserPolicy.content.disallowEval(); + BrowserPolicy.content.allowInlineScripts(); + BrowserPolicy.content.allowInlineStyles(); + BrowserPolicy.content.allowImageDataUrl(CDN_URL); + BrowserPolicy.content.allowFontDataUrl(CDN_URL); + BrowserPolicy.content.allowOriginForAll(CDN_URL); + WebAppInternals.setBundledJsCssPrefix(CDN_URL + APP_CONFIG.basename + Meteor.settings.public.app.instanceId); + + const fontRegExp = /\.(eot|ttf|otf|woff|woff2)$/; + + WebApp.rawConnectHandlers.use('/', (req, res, next) => { + if (fontRegExp.test(req._parsedUrl.pathname)) { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Vary', 'Origin'); + res.setHeader('Pragma', 'public'); + res.setHeader('Cache-Control', '"public"'); + } + return next(); + }); + } - if (CDN_URL.trim()) { - // Add CDN - BrowserPolicy.content.disallowEval(); - BrowserPolicy.content.allowInlineScripts(); - BrowserPolicy.content.allowInlineStyles(); - BrowserPolicy.content.allowImageDataUrl(CDN_URL); - BrowserPolicy.content.allowFontDataUrl(CDN_URL); - BrowserPolicy.content.allowOriginForAll(CDN_URL); - WebAppInternals.setBundledJsCssPrefix(CDN_URL + APP_CONFIG.basename + Meteor.settings.public.app.instanceId); - - const fontRegExp = /\.(eot|ttf|otf|woff|woff2)$/; - - WebApp.rawConnectHandlers.use('/', (req, res, next) => { - if (fontRegExp.test(req._parsedUrl.pathname)) { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Vary', 'Origin'); - res.setHeader('Pragma', 'public'); - res.setHeader('Cache-Control', '"public"'); - } - return next(); - }); - } - - setMinBrowserVersions(); + setMinBrowserVersions(); - Logger.warn(`SERVER STARTED. - ENV=${env} - nodejs version=${process.version} - BBB_HTML5_ROLE=${process.env.BBB_HTML5_ROLE} - INSTANCE_ID=${instanceId} - PORT=${process.env.PORT} - CDN=${CDN_URL}\n`, APP_CONFIG); - } + Logger.warn(`SERVER STARTED. + ENV=${env} + nodejs version=${process.version} + BBB_HTML5_ROLE=${process.env.BBB_HTML5_ROLE} + INSTANCE_ID=${instanceId} + PORT=${process.env.PORT} + CDN=${CDN_URL}\n`, APP_CONFIG); }); @@ -192,7 +199,7 @@ WebApp.connectHandlers.use('/locale', (req, res) => { const browserLocale = override && req.query.init === 'true' ? override.split(/[-_]/g) : req.query.locale.split(/[-_]/g); - const localeList = [fallback]; + let localeFile = fallback; const usableLocales = AVAILABLE_LOCALES .map(file => file.replace('.json', '')) @@ -200,35 +207,29 @@ WebApp.connectHandlers.use('/locale', (req, res) => { ? [...locales, locale] : locales), []); - const regionDefault = usableLocales.find(locale => browserLocale[0] === locale); - - if (regionDefault) localeList.push(regionDefault); - if (!regionDefault && usableLocales.length) localeList.push(usableLocales[0]); - let normalizedLocale; - let messages = {}; if (browserLocale.length > 1) { normalizedLocale = `${browserLocale[0]}_${browserLocale[1].toUpperCase()}`; - localeList.push(normalizedLocale); + + const normDefault = usableLocales.find(locale => normalizedLocale === locale); + if (normDefault) localeFile = normDefault; } - localeList.forEach((locale) => { - try { - const data = Assets.getText(`locales/${locale}.json`); - messages = Object.assign(messages, JSON.parse(data)); - normalizedLocale = locale; - } catch (e) { - Logger.info(`'Could not process locale ${locale}:${e}`); - // Getting here means the locale is not available in the current locale files. - } - }); + const regionDefault = usableLocales.find(locale => browserLocale[0] === locale); + + if (localeFile === fallback && regionDefault !== localeFile) { + localeFile = regionDefault; + } res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ normalizedLocale, messages })); + res.end(JSON.stringify({ + normalizedLocale: localeFile, + regionDefaultLocale: (regionDefault && regionDefault !== localeFile) ? regionDefault : '', + })); }); -WebApp.connectHandlers.use('/locales', (req, res) => { +WebApp.connectHandlers.use('/locale-list', (req, res) => { if (!avaibleLocalesNamesJSON) { avaibleLocalesNamesJSON = JSON.stringify(generateLocaleOptions()); } diff --git a/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx b/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx index 3e5e63c6007e3cc1d3cb789b696bb7aa4f567ccc..938b7dde7df4c3b894d19734278d4ddf402c2f9b 100644 --- a/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx +++ b/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx @@ -307,11 +307,11 @@ class LayoutManager extends Component { let storageBreakoutRoomWidth; if (storageLData) { - storageUserListWidth = storageLData.userListSize.width; - storageChatWidth = storageLData.chatSize.width; - storagePollWidth = storageLData.pollSize.width; - storageNoteWidth = storageLData.noteSize.width; - storageBreakoutRoomWidth = storageLData.breakoutRoomSize.width; + storageUserListWidth = storageLData.userListSize?.width; + storageChatWidth = storageLData.chatSize?.width; + storagePollWidth = storageLData.pollSize?.width; + storageNoteWidth = storageLData.noteSize?.width; + storageBreakoutRoomWidth = storageLData.breakoutRoomSize?.width; } let newUserListSize; diff --git a/bigbluebutton-html5/imports/ui/components/legacy/component.jsx b/bigbluebutton-html5/imports/ui/components/legacy/component.jsx index f6c049f3452dc5cc425822c770f4ba119e834c2d..b859270bd6d4c88bf16b98e5fec0126d5cf2b665 100755 --- a/bigbluebutton-html5/imports/ui/components/legacy/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/legacy/component.jsx @@ -69,16 +69,17 @@ const FETCHING = 'fetching'; const FALLBACK = 'fallback'; const READY = 'ready'; const supportedBrowsers = ['chrome', 'firefox', 'safari', 'opera', 'edge', 'yandex']; +const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale; export default class Legacy extends Component { constructor(props) { super(props); const locale = navigator.languages ? navigator.languages[0] : false - || navigator.language - || Meteor.settings.public.app.defaultSettings.application.fallbackLocale; + || navigator.language; const url = `./locale?locale=${locale}`; + const localesPath = 'locales'; const that = this; this.state = { viewState: FETCHING }; @@ -90,9 +91,56 @@ export default class Legacy extends Component { return response.json(); }) - .then(({ messages, normalizedLocale }) => { - const dasherizedLocale = normalizedLocale.replace('_', '-'); - that.setState({ messages, normalizedLocale: dasherizedLocale, viewState: READY }); + .then(({ normalizedLocale, regionDefaultLocale }) => { + fetch(`${localesPath}/${DEFAULT_LANGUAGE}.json`) + .then((response) => { + if (!response.ok) { + return Promise.reject(); + } + return response.json(); + }) + .then((messages) => { + if (regionDefaultLocale !== '') { + fetch(`${localesPath}/${regionDefaultLocale}.json`) + .then((response) => { + if (!response.ok) { + return Promise.resolve(); + } + return response.json(); + }) + .then((regionDefaultMessages) => { + messages = Object.assign(messages, regionDefaultMessages); + this.setState({ messages}); + }); + } + + if (normalizedLocale && normalizedLocale !== DEFAULT_LANGUAGE && normalizedLocale !== regionDefaultLocale) { + fetch(`${localesPath}/${normalizedLocale}.json`) + .then((response) => { + if (!response.ok) { + return Promise.reject(); + } + return response.json(); + }) + .then((localeMessages) => { + messages = Object.assign(messages, localeMessages); + this.setState({ messages}); + }) + .catch(() => { + normalizedLocale = (regionDefaultLocale) || DEFAULT_LANGUAGE; + const dasherizedLocale = normalizedLocale.replace('_', '-'); + this.setState({ messages, normalizedLocale: dasherizedLocale, viewState: READY }); + }); + } + return messages; + }) + .then((messages) => { + const dasherizedLocale = normalizedLocale.replace('_', '-'); + this.setState({ messages, normalizedLocale: dasherizedLocale, viewState: READY }); + }) + .catch(() => { + that.setState({ viewState: FALLBACK }); + }); }) .catch(() => { that.setState({ viewState: FALLBACK }); diff --git a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx index a47575fcd678a49504151ef6deb05c5d0257c27b..4c9cf5a67b3915746a348d3e62737ccb0b79f445 100644 --- a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx @@ -489,6 +489,7 @@ class WebcamDraggable extends PureComponent { style={{ marginLeft: 0, marginRight: 0, + zIndex: 2, display: hideWebcams ? 'none' : undefined, }} > diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx index 647db1a3634e68756f6bd25d85faee3e169cb038..2878782e89be5212acca7d0270ba5981fc9976cc 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx @@ -11,6 +11,7 @@ import Rating from './rating/component'; import { styles } from './styles'; import logger from '/imports/startup/client/logger'; import Users from '/imports/api/users'; +import Meetings from '/imports/api/meetings'; import AudioManager from '/imports/ui/services/audio-manager'; import { meetingIsBreakout } from '/imports/ui/components/app/service'; @@ -35,6 +36,10 @@ const intlMessage = defineMessages({ id: 'app.meeting.endedMessage', description: 'message saying to go back to home screen', }, + messageEndedByUser: { + id: 'app.meeting.endedByUserMessage', + description: 'message informing who ended the meeting', + }, buttonOkay: { id: 'app.meeting.endNotification.ok.label', description: 'label okay for button', @@ -116,6 +121,17 @@ class MeetingEnded extends PureComponent { this.localUserRole = user.role; } + const meeting = Meetings.findOne({ id: user.meetingID }); + if (meeting) { + const endedBy = Users.findOne({ + userId: meeting.meetingEndedBy, + }, { fields: { name: 1 } }); + + if (endedBy) { + this.meetingEndedBy = endedBy.name; + } + } + this.setSelectedStar = this.setSelectedStar.bind(this); this.confirmRedirect = this.confirmRedirect.bind(this); this.sendFeedback = this.sendFeedback.bind(this); @@ -209,6 +225,11 @@ class MeetingEnded extends PureComponent { </h1> {!allowRedirectToLogoutURL() ? null : ( <div> + {this.meetingEndedBy ? ( + <div className={styles.text}> + {intl.formatMessage(intlMessage.messageEndedByUser, { 0: this.meetingEndedBy })} + </div> + ) : null} <div className={styles.text}> {intl.formatMessage(intlMessage.messageEnded)} </div> diff --git a/bigbluebutton-html5/imports/ui/components/poll/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/component.jsx index 479238daf3a535469deafb983858a2435b24680a..5ff55f15e9d32531557f0cfc17b9684f67823e0d 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/component.jsx @@ -9,6 +9,7 @@ import cx from 'classnames'; import Button from '/imports/ui/components/button/component'; import LiveResult from './live-result/component'; import { styles } from './styles.scss'; +import DragAndDrop from './dragAndDrop/component'; const intlMessages = defineMessages({ pollPaneTitle: { @@ -31,6 +32,10 @@ const intlMessages = defineMessages({ id: 'app.poll.activePollInstruction', description: 'instructions displayed when a poll is active', }, + dragDropPollInstruction: { + id: 'app.poll.dragDropPollInstruction', + description: 'instructions for upload poll options via drag and drop', + }, ariaInputCount: { id: 'app.poll.ariaInputCount', description: 'aria label for custom poll input field', @@ -148,6 +153,7 @@ const intlMessages = defineMessages({ const CHAT_ENABLED = Meteor.settings.public.chat.enabled; const MAX_CUSTOM_FIELDS = Meteor.settings.public.poll.max_custom; const MAX_INPUT_CHARS = 45; +const FILE_DRAG_AND_DROP_ENABLED = Meteor.settings.public.poll.allowDragAndDropFile; const validateInput = (i) => { let _input = i; @@ -206,6 +212,21 @@ class Poll extends Component { }); } + handleInputChange(index, event) { + this.handleInputTextChange(index, event.target.value); + } + + handleInputTextChange(index, text) { + const { optList } = this.state; + // This regex will replace any instance of 2 or more consecutive white spaces + // with a single white space character. + const option = text.replace(/\s{2,}/g, ' ').trim(); + + if (index < optList.length) optList[index].val = option === '' ? '' : option; + + this.setState({ optList }); + } + handleInputChange(e, index) { const { optList, type, error } = this.state; const list = [...optList]; @@ -222,6 +243,24 @@ class Poll extends Component { this.setState({ question: validateInput(e.target.value), error: clearError ? null : error }); } + pushToCustomPollValues(text) { + const lines = text.split('\n'); + for (let i = 0; i < MAX_CUSTOM_FIELDS; i += 1) { + let line = ''; + if (i < lines.length) { + line = lines[i]; + line = line.length > MAX_INPUT_CHARS ? line.substring(0, MAX_INPUT_CHARS) : line; + } + this.handleInputTextChange(i, line); + } + } + + handlePollValuesText(text) { + if (text && text.length > 0) { + this.pushToCustomPollValues(text); + } + } + handleRemoveOption(index) { const { optList } = this.state; const list = [...optList]; @@ -496,6 +535,9 @@ class Poll extends Component { }); }} /> + { + FILE_DRAG_AND_DROP_ENABLED && this.renderDragDrop() + } </div> ) } @@ -537,6 +579,25 @@ class Poll extends Component { return this.renderPollOptions(); } + + renderDragDrop() { + const { intl } = this.props; + return ( + <div> + <div className={styles.instructions}> + {intl.formatMessage(intlMessages.dragDropPollInstruction)} + </div> + <DragAndDrop + {...{ intl, MAX_INPUT_CHARS }} + handlePollValuesText={e => this.handlePollValuesText(e)} + > + <div className={styles.dragAndDropPollContainer} /> + </DragAndDrop> + </div> + ); + } + + render() { const { intl, diff --git a/bigbluebutton-html5/imports/ui/components/poll/dragAndDrop/component.jsx b/bigbluebutton-html5/imports/ui/components/poll/dragAndDrop/component.jsx new file mode 100755 index 0000000000000000000000000000000000000000..b31318d63f85cf7ac179fb68bf256a5796513ca4 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/poll/dragAndDrop/component.jsx @@ -0,0 +1,143 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withModalMounter } from '/imports/ui/components/modal/service'; + +import { defineMessages, injectIntl } from 'react-intl'; +import { styles } from './styles.scss'; +import Button from '/imports/ui/components/button/component'; + + +// src: https://medium.com/@650egor/simple-drag-and-drop-file-upload-in-react-2cb409d88929 + +const intlMessages = defineMessages({ + customPollTextArea: { + id: 'app.poll.customPollTextArea', + description: 'label for button to submit custom poll values', + }, +}); + +class DragAndDrop extends Component { + static handleDrag(e) { + e.preventDefault(); + e.stopPropagation(); + } + + constructor(props) { + super(props); + + this.state = { + drag: false, + pollValueText: '', + }; + + this.dropRef = React.createRef(); + } + + componentDidMount() { + this.dragCounter = 0; + const div = this.dropRef.current; + div.addEventListener('dragenter', e => this.handleDragIn(e)); + div.addEventListener('dragleave', e => this.handleDragOut(e)); + div.addEventListener('dragover', e => DragAndDrop.handleDrag(e)); + div.addEventListener('drop', e => this.handleDrop(e)); + } + + componentWillUnmount() { + const div = this.dropRef.current; + div.removeEventListener('dragenter', e => this.handleDragIn(e)); + div.removeEventListener('dragleave', e => this.handleDragOut(e)); + div.removeEventListener('dragover', e => DragAndDrop.handleDrag(e)); + div.removeEventListener('drop', e => this.handleDrop(e)); + } + + setPollValues() { + const { pollValueText } = this.state; + const { handlePollValuesText } = this.props; + if (pollValueText) { + handlePollValuesText(pollValueText); + } + } + + setPollValuesFromFile(file) { + const reader = new FileReader(); + reader.onload = async (e) => { + const text = e.target.result; + this.setPollValueText(text); + this.setPollValues(); + }; + reader.readAsText(file); + } + + setPollValueText(pollText) { + const { MAX_INPUT_CHARS } = this.props; + const arr = pollText.split('\n'); + const text = arr.map(line => (line.length > MAX_INPUT_CHARS ? line.substring(0, MAX_INPUT_CHARS) : line)).join('\n'); + this.setState({ pollValueText: text }); + } + + + handleTextInput(e) { + this.setPollValueText(e.target.value); + } + + + handleDragIn(e) { + DragAndDrop.handleDrag(e); + this.dragCounter += 1; + if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { + this.setState({ drag: true }); + } + } + + handleDragOut(e) { + DragAndDrop.handleDrag(e); + this.dragCounter -= 1; + if (this.dragCounter > 0) return; + this.setState({ drag: false }); + } + + handleDrop(e) { + DragAndDrop.handleDrag(e); + this.setState({ drag: false }); + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + this.setPollValuesFromFile(e.dataTransfer.files[0]); + this.dragCounter = 0; + } + } + + + render() { + const { intl, children } = this.props; + const { pollValueText, drag } = this.state; + return ( + <div + className={styles.dndContainer} + ref={this.dropRef} + > + <textarea + value={pollValueText} + className={drag ? styles.dndActive : styles.dndInActive} + onChange={e => this.handleTextInput(e)} + /> + <Button + onClick={() => this.setPollValues()} + label={intl.formatMessage(intlMessages.customPollTextArea)} + color="primary" + disabled={pollValueText < 1} + className={styles.btn} + /> + {children} + </div> + + ); + } +} export default withModalMounter(injectIntl(DragAndDrop)); + +DragAndDrop.propTypes = { + intl: PropTypes.shape({ + formatMessage: PropTypes.func.isRequired, + }).isRequired, + MAX_INPUT_CHARS: PropTypes.number.isRequired, + handlePollValuesText: PropTypes.func.isRequired, + children: PropTypes.element.isRequired, +}; diff --git a/bigbluebutton-html5/imports/ui/components/poll/dragAndDrop/styles.scss b/bigbluebutton-html5/imports/ui/components/poll/dragAndDrop/styles.scss new file mode 100755 index 0000000000000000000000000000000000000000..abd06217ecf4e5d8ce4d9b48333dfc7ab4eb1b12 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/poll/dragAndDrop/styles.scss @@ -0,0 +1,26 @@ +@import "/imports/ui/stylesheets/variables/_all"; + +.dndContainer { + height: 200px; +} + +.customPollValuesTextfield { + width: 100%; + height: 100%; + resize: none; + font-size: var(--font-size-small); +} + +.dndActive { + @extend .customPollValuesTextfield; + background: grey; +} + +.dndInActive { + @extend .customPollValuesTextfield; + background: white; +} + +.btn { + width: 100%; +} diff --git a/bigbluebutton-html5/imports/ui/components/poll/styles.scss b/bigbluebutton-html5/imports/ui/components/poll/styles.scss index d2e1953cdb266325c91c8c6d45ced32528841697..775d7d23a23700f4938566666300363e47b9f21a 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/poll/styles.scss @@ -356,3 +356,8 @@ color: var(--color-white) !important; } } + +.dragAndDropPollContainer { + width: 200px !important; + height: 200px !important; +} diff --git a/bigbluebutton-html5/imports/ui/components/settings/service.js b/bigbluebutton-html5/imports/ui/components/settings/service.js index 85af45dddd425ef6af2f48f04cd3115870e57262..42698cb30c268933700445e9841c7c8ee30ad0a4 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/service.js +++ b/bigbluebutton-html5/imports/ui/components/settings/service.js @@ -27,7 +27,7 @@ const updateSettings = (obj, msg) => { } }; -const getAvailableLocales = () => fetch('./locales').then(locales => locales.json()); +const getAvailableLocales = () => fetch('./locale-list').then(locales => locales.json()); export { getUserRoles, diff --git a/bigbluebutton-html5/imports/ui/components/text-input/component.jsx b/bigbluebutton-html5/imports/ui/components/text-input/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d3a1a3d1ebd9515071288515c33553ec4c48e967 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/text-input/component.jsx @@ -0,0 +1,81 @@ +import React, { PureComponent } from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import TextareaAutosize from 'react-autosize-textarea'; +import PropTypes from 'prop-types'; +import logger from '/imports/startup/client/logger'; +import Button from '/imports/ui/components/button/component'; +import { styles } from './styles.scss'; + +const propTypes = { + placeholder: PropTypes.string, + send: PropTypes.func.isRequired, +}; + +const defaultProps = { + placeholder: '', + send: () => logger.warn({ logCode: 'text_input_send_function' }, `Missing`), +}; + +const messages = defineMessages({ + sendLabel: { + id: 'app.textInput.sendLabel', + description: 'Text input send button label', + }, +}); + +class TextInput extends PureComponent { + constructor(props) { + super(props); + + this.state = { message: '' }; + } + + handleOnChange(e) { + const message = e.target.value; + this.setState({ message }); + } + + handleOnClick() { + const { send } = this.props; + const { message } = this.state; + + send(message); + this.setState({ message: '' }); + } + + render() { + const { + intl, + maxLength, + placeholder, + } = this.props; + + const { message } = this.state; + + return ( + <div className={styles.wrapper}> + <TextareaAutosize + className={styles.textarea} + maxLength={maxLength} + onChange={(e) => this.handleOnChange(e)} + placeholder={placeholder} + value={message} + /> + <Button + circle + className={styles.button} + color="primary" + hideLabel + icon="send" + label={intl.formatMessage(messages.sendLabel)} + onClick={() => this.handleOnClick()} + /> + </div> + ); + } +} + +TextInput.propTypes = propTypes; +TextInput.defaultProps = defaultProps; + +export default injectIntl(TextInput); diff --git a/bigbluebutton-html5/imports/ui/components/text-input/styles.scss b/bigbluebutton-html5/imports/ui/components/text-input/styles.scss new file mode 100644 index 0000000000000000000000000000000000000000..5f13ce4fa070a5f077caf4c37f03d6de4b9472ce --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/text-input/styles.scss @@ -0,0 +1,53 @@ +@import "/imports/ui/stylesheets/mixins/focus"; +@import "/imports/ui/stylesheets/mixins/_indicators"; +@import "/imports/ui/stylesheets/variables/_all"; + +.wrapper { + display: flex; + flex-direction: row; +} + +.textarea { + @include inputFocus(var(--color-blue-light)); + + flex: 1; + background: #fff; + background-clip: padding-box; + margin: 0; + color: var(--color-text); + -webkit-appearance: none; + padding: calc(var(--sm-padding-y) * 2.5) calc(var(--sm-padding-x) * 1.25); + resize: none; + transition: none; + border-radius: var(--border-radius); + font-size: var(--font-size-base); + min-height: 2.5rem; + max-height: 10rem; + border: 1px solid var(--color-gray-lighter); + box-shadow: 0 0 0 1px var(--color-gray-lighter); + + &:hover { + @include highContrastOutline(); + } + + &:active, + &:focus { + @include highContrastOutline(); + outline-style: solid; + } +} + +.button { + margin:0 0 0 var(--sm-padding-x); + align-self: center; + font-size: 0.9rem; + + [dir="rtl"] & { + margin: 0 var(--sm-padding-x) 0 0; + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); + } +} diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx b/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx index 94d48900110473774acdf4a4ca377e2730ea85e1..db6ee3d12c3a5993bc5acaf0beda90a37a343bd0 100755 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/component.jsx @@ -4,6 +4,7 @@ import { Session } from 'meteor/session'; import { defineMessages, injectIntl } from 'react-intl'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; import UserAvatar from '/imports/ui/components/user-avatar/component'; +import TextInput from '/imports/ui/components/text-input/component'; import Button from '/imports/ui/components/button/component'; import { styles } from './styles'; @@ -48,6 +49,14 @@ const intlMessages = defineMessages({ id: 'app.userList.guest.rememberChoice', description: 'Remember label for checkbox', }, + emptyMessage: { + id: 'app.userList.guest.emptyMessage', + description: 'Empty guest lobby message label', + }, + inputPlaceholder: { + id: 'app.userList.guest.inputPlaceholder', + description: 'Placeholder to guest lobby message input', + }, accept: { id: 'app.userList.guest.acceptLabel', description: 'Accept guest button label' @@ -153,6 +162,9 @@ const WaitingUsers = (props) => { guestUsers, guestUsersCall, changeGuestPolicy, + isGuestLobbyMessageEnabled, + setGuestLobbyMessage, + guestLobbyMessage, authenticatedGuest, } = props; @@ -234,6 +246,16 @@ const WaitingUsers = (props) => { /> </div> </header> + {isGuestLobbyMessageEnabled ? ( + <div className={styles.lobbyMessage}> + <TextInput + maxLength={128} + placeholder={intl.formatMessage(intlMessages.inputPlaceholder)} + send={setGuestLobbyMessage} + /> + <p><i>"{guestLobbyMessage.length > 0 ? guestLobbyMessage : intl.formatMessage(intlMessages.emptyMessage)}"</i></p> + </div> + ) : null} <div> <div> <p className={styles.mainTitle}>{intl.formatMessage(intlMessages.optionTitle)}</p> diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx b/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx index 7e6e879ae4038f9091894070e8b581c7fb7b2bee..0341da5a5daa35ba6496687121c5988b23fbb5fc 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx @@ -38,6 +38,9 @@ export default withTracker(() => { authenticatedUsers, guestUsersCall: Service.guestUsersCall, changeGuestPolicy: Service.changeGuestPolicy, + isGuestLobbyMessageEnabled: Service.isGuestLobbyMessageEnabled, + setGuestLobbyMessage: Service.setGuestLobbyMessage, + guestLobbyMessage: Service.getGuestLobbyMessage(), authenticatedGuest, }; })(WaitingContainer); diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/service.js b/bigbluebutton-html5/imports/ui/components/waiting-users/service.js index 23392f0c522fb66f18ee4c0e7cd1c78b1f3fdd4a..62d4165000c1d6912149c9a3f8f7310c14d79722 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/service.js +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/service.js @@ -1,9 +1,30 @@ +import Meetings from '/imports/api/meetings'; +import Auth from '/imports/ui/services/auth'; import { makeCall } from '/imports/ui/services/api'; const guestUsersCall = (guestsArray, status) => makeCall('allowPendingUsers', guestsArray, status); const changeGuestPolicy = policyRule => makeCall('changeGuestPolicy', policyRule); + +const isGuestLobbyMessageEnabled = Meteor.settings.public.app.enableGuestLobbyMessage; + +const getGuestLobbyMessage = () => { + const meeting = Meetings.findOne( + { meetingId: Auth.meetingID }, + { fields: { guestLobbyMessage: 1 } }, + ); + + if (meeting) return meeting.guestLobbyMessage; + + return ''; +}; + +const setGuestLobbyMessage = (message) => makeCall('setGuestLobbyMessage', message); + export default { guestUsersCall, changeGuestPolicy, + isGuestLobbyMessageEnabled, + getGuestLobbyMessage, + setGuestLobbyMessage, }; diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss b/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss index 95f43ac21c3f6de01342ed9b48406460913312ac..bc5791c572a2aed78bbe4736c9678127d836f81a 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/styles.scss @@ -186,6 +186,18 @@ text-overflow: ellipsis; } +.lobbyMessage { + border-bottom: 1px solid var(--color-gray-lightest); + + p { + background-color: var(--color-off-white); + box-sizing: border-box; + color: var(--color-gray); + padding: 1rem; + text-align: center; + } +} + .rememberContainer { margin: 1rem 1rem; height: 2rem; diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 6d651d680c9d8135b19350b7f6ee08b6424c8a0f..908c64dad69c2fb6348a0d7c04e2fe000d9af81c 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -22,6 +22,7 @@ public: # in some cases we want only custom logoutUrl to be used when provided on meeting create. Default value: true allowDefaultLogoutUrl: true allowUserLookup: false + enableGuestLobbyMessage: true enableNetworkInformation: false enableLimitOfViewersInWebcam: false enableMultipleCameras: true @@ -290,6 +291,7 @@ public: poll: enabled: true max_custom: 5 + allowDragAndDropFile: false captions: enabled: true enableDictation: false diff --git a/bigbluebutton-html5/private/static/guest-wait/guest-wait.html b/bigbluebutton-html5/private/static/guest-wait/guest-wait.html index 8de097f3027098496106b94e30f25909d1f2511b..e2181188dbd3e66fe4e4901175bc28668dd5ff8b 100755 --- a/bigbluebutton-html5/private/static/guest-wait/guest-wait.html +++ b/bigbluebutton-html5/private/static/guest-wait/guest-wait.html @@ -59,12 +59,24 @@ } } </style> - + <script type="text/javascript"> function updateMessage(message) { document.querySelector('#content > p').innerHTML = message; } + var lobbyMessage = ''; + function updateLobbyMessage(message) { + if (message !== lobbyMessage) { + lobbyMessage = message; + if (lobbyMessage.length !== 0) { + updateMessage(lobbyMessage); + } else { + updateMessage('Please wait for a moderator to approve you joining the meeting.'); + } + } + } + function findSessionToken() { return location.search .substr(1) @@ -75,7 +87,7 @@ }; function fetchGuestWait(sessionToken) { - const GUEST_WAIT_ENDPOINT = '/bigbluebutton/api/guestWait'; + const GUEST_WAIT_ENDPOINT = '/bigbluebutton/api/guestWait'; const urlTest = new URL(`${window.location.origin}${GUEST_WAIT_ENDPOINT}`); const concatedParams = sessionToken.concat('&redirect=false'); urlTest.search = concatedParams; @@ -95,7 +107,6 @@ fetchGuestWait(token) .then(async (resp) => await resp.json()) .then((data) => { - console.log("data=" + JSON.stringify(data)); var status = data.response.guestStatus; if (REDIRECT_STATUSES.includes(status)) { @@ -104,6 +115,8 @@ return; } + updateLobbyMessage(data.response.lobbyMessage); + return pollGuestStatus(token, attempt + 1, limit, everyMs); }); }, everyMs); diff --git a/bigbluebutton-html5/private/locales/ar.json b/bigbluebutton-html5/public/locales/ar.json similarity index 100% rename from bigbluebutton-html5/private/locales/ar.json rename to bigbluebutton-html5/public/locales/ar.json diff --git a/bigbluebutton-html5/private/locales/az.json b/bigbluebutton-html5/public/locales/az.json similarity index 100% rename from bigbluebutton-html5/private/locales/az.json rename to bigbluebutton-html5/public/locales/az.json diff --git a/bigbluebutton-html5/private/locales/bg_BG.json b/bigbluebutton-html5/public/locales/bg_BG.json similarity index 100% rename from bigbluebutton-html5/private/locales/bg_BG.json rename to bigbluebutton-html5/public/locales/bg_BG.json diff --git a/bigbluebutton-html5/private/locales/ca.json b/bigbluebutton-html5/public/locales/ca.json similarity index 100% rename from bigbluebutton-html5/private/locales/ca.json rename to bigbluebutton-html5/public/locales/ca.json diff --git a/bigbluebutton-html5/private/locales/cs_CZ.json b/bigbluebutton-html5/public/locales/cs_CZ.json similarity index 100% rename from bigbluebutton-html5/private/locales/cs_CZ.json rename to bigbluebutton-html5/public/locales/cs_CZ.json diff --git a/bigbluebutton-html5/private/locales/da.json b/bigbluebutton-html5/public/locales/da.json similarity index 100% rename from bigbluebutton-html5/private/locales/da.json rename to bigbluebutton-html5/public/locales/da.json diff --git a/bigbluebutton-html5/private/locales/de.json b/bigbluebutton-html5/public/locales/de.json similarity index 99% rename from bigbluebutton-html5/private/locales/de.json rename to bigbluebutton-html5/public/locales/de.json index 53555c2f9daf9d8546add45088bbbbc0e4cf9455..a2776b6914f13f8c6182961753fa6f71c630d896 100644 --- a/bigbluebutton-html5/private/locales/de.json +++ b/bigbluebutton-html5/public/locales/de.json @@ -219,6 +219,10 @@ "app.poll.hidePollDesc": "Versteckt das Umfragemenü", "app.poll.quickPollInstruction": "Wählen Sie eine der unten stehenden Optionen, um die Umfrage zu starten.", "app.poll.activePollInstruction": "Lassen Sie dieses Fenster offen, um auf die Antworten der Teilnehmer zu warten. Sobald Sie auf 'Umfrageergebnisse veröffentlichen' klicken, werden die Ergebnisse angezeigt und die Umfrage beendet.", + "app.poll.customPollLabel": "Benutzerdefinierte Umfrage", + "app.poll.startCustomLabel": "Benutzerdefinierte Umfrage starten", + "app.poll.dragDropPollInstruction": "Ziehen Sie per drag and Drop eine Textdatei mit den Umfrageoptionen auf das markierte Feld", + "app.poll.customPollTextArea": "Umfrageoptionen ausfüllen", "app.poll.publishLabel": "Umfrageergebnisse veröffentlichen", "app.poll.backLabel": "Umfrage starten", "app.poll.closeLabel": "Schließen", diff --git a/bigbluebutton-html5/private/locales/el_GR.json b/bigbluebutton-html5/public/locales/el_GR.json similarity index 100% rename from bigbluebutton-html5/private/locales/el_GR.json rename to bigbluebutton-html5/public/locales/el_GR.json diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/public/locales/en.json similarity index 99% rename from bigbluebutton-html5/private/locales/en.json rename to bigbluebutton-html5/public/locales/en.json index 2ac4f36d93bb81a3bf1e39ff4f2efa15ae11b71b..142097d196a2d0b939fca46710e7d1da026d4c95 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -49,6 +49,7 @@ "app.captions.pad.dictationStop": "Stop dictation", "app.captions.pad.dictationOnDesc": "Turns speech recognition on", "app.captions.pad.dictationOffDesc": "Turns speech recognition off", + "app.textInput.sendLabel": "Send", "app.note.title": "Shared Notes", "app.note.label": "Note", "app.note.hideNoteLabel": "Hide note", @@ -134,6 +135,7 @@ "app.meeting.ended": "This session has ended", "app.meeting.meetingTimeRemaining": "Meeting time remaining: {0}", "app.meeting.meetingTimeHasEnded": "Time ended. Meeting will close soon", + "app.meeting.endedByUserMessage": "The meeting was ended by {0}", "app.meeting.endedMessage": "You will be forwarded back to the home screen", "app.meeting.alertMeetingEndsUnderMinutesSingular": "Meeting is closing in one minute.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "Meeting is closing in {0} minutes.", @@ -220,6 +222,8 @@ "app.poll.hidePollDesc": "Hides the poll menu pane", "app.poll.quickPollInstruction": "Select an option below to start your poll.", "app.poll.activePollInstruction": "Leave this panel open to see live responses to your poll. When you are ready, select 'Publish polling results' to publish the results and end the poll.", + "app.poll.dragDropPollInstruction": "To fill the poll values, drag a text file with the poll values onto the highlighted field", + "app.poll.customPollTextArea": "Fill poll values", "app.poll.publishLabel": "Publish polling results", "app.poll.backLabel": "Start A Poll", "app.poll.closeLabel": "Close", @@ -539,6 +543,8 @@ "app.userList.guest.pendingGuestUsers": "{0} Pending Guest Users", "app.userList.guest.pendingGuestAlert": "Has joined the session and is waiting for your approval.", "app.userList.guest.rememberChoice": "Remember choice", + "app.userList.guest.emptyMessage": "There is currently no message", + "app.userList.guest.inputPlaceholder": "Message to the guests' lobby", "app.userList.guest.acceptLabel": "Accept", "app.userList.guest.denyLabel": "Deny", "app.user-info.title": "Directory Lookup", diff --git a/bigbluebutton-html5/private/locales/eo.json b/bigbluebutton-html5/public/locales/eo.json similarity index 100% rename from bigbluebutton-html5/private/locales/eo.json rename to bigbluebutton-html5/public/locales/eo.json diff --git a/bigbluebutton-html5/private/locales/es.json b/bigbluebutton-html5/public/locales/es.json similarity index 100% rename from bigbluebutton-html5/private/locales/es.json rename to bigbluebutton-html5/public/locales/es.json diff --git a/bigbluebutton-html5/private/locales/es_ES.json b/bigbluebutton-html5/public/locales/es_ES.json similarity index 100% rename from bigbluebutton-html5/private/locales/es_ES.json rename to bigbluebutton-html5/public/locales/es_ES.json diff --git a/bigbluebutton-html5/private/locales/es_MX.json b/bigbluebutton-html5/public/locales/es_MX.json similarity index 100% rename from bigbluebutton-html5/private/locales/es_MX.json rename to bigbluebutton-html5/public/locales/es_MX.json diff --git a/bigbluebutton-html5/private/locales/et.json b/bigbluebutton-html5/public/locales/et.json similarity index 100% rename from bigbluebutton-html5/private/locales/et.json rename to bigbluebutton-html5/public/locales/et.json diff --git a/bigbluebutton-html5/private/locales/eu.json b/bigbluebutton-html5/public/locales/eu.json similarity index 100% rename from bigbluebutton-html5/private/locales/eu.json rename to bigbluebutton-html5/public/locales/eu.json diff --git a/bigbluebutton-html5/private/locales/fa_IR.json b/bigbluebutton-html5/public/locales/fa_IR.json similarity index 100% rename from bigbluebutton-html5/private/locales/fa_IR.json rename to bigbluebutton-html5/public/locales/fa_IR.json diff --git a/bigbluebutton-html5/private/locales/fi.json b/bigbluebutton-html5/public/locales/fi.json similarity index 100% rename from bigbluebutton-html5/private/locales/fi.json rename to bigbluebutton-html5/public/locales/fi.json diff --git a/bigbluebutton-html5/private/locales/fr.json b/bigbluebutton-html5/public/locales/fr.json similarity index 100% rename from bigbluebutton-html5/private/locales/fr.json rename to bigbluebutton-html5/public/locales/fr.json diff --git a/bigbluebutton-html5/private/locales/gl.json b/bigbluebutton-html5/public/locales/gl.json similarity index 100% rename from bigbluebutton-html5/private/locales/gl.json rename to bigbluebutton-html5/public/locales/gl.json diff --git a/bigbluebutton-html5/private/locales/he.json b/bigbluebutton-html5/public/locales/he.json similarity index 100% rename from bigbluebutton-html5/private/locales/he.json rename to bigbluebutton-html5/public/locales/he.json diff --git a/bigbluebutton-html5/private/locales/hi_IN.json b/bigbluebutton-html5/public/locales/hi_IN.json similarity index 100% rename from bigbluebutton-html5/private/locales/hi_IN.json rename to bigbluebutton-html5/public/locales/hi_IN.json diff --git a/bigbluebutton-html5/private/locales/hr.json b/bigbluebutton-html5/public/locales/hr.json similarity index 100% rename from bigbluebutton-html5/private/locales/hr.json rename to bigbluebutton-html5/public/locales/hr.json diff --git a/bigbluebutton-html5/private/locales/hu_HU.json b/bigbluebutton-html5/public/locales/hu_HU.json similarity index 100% rename from bigbluebutton-html5/private/locales/hu_HU.json rename to bigbluebutton-html5/public/locales/hu_HU.json diff --git a/bigbluebutton-html5/private/locales/hy.json b/bigbluebutton-html5/public/locales/hy.json similarity index 100% rename from bigbluebutton-html5/private/locales/hy.json rename to bigbluebutton-html5/public/locales/hy.json diff --git a/bigbluebutton-html5/private/locales/id.json b/bigbluebutton-html5/public/locales/id.json similarity index 100% rename from bigbluebutton-html5/private/locales/id.json rename to bigbluebutton-html5/public/locales/id.json diff --git a/bigbluebutton-html5/private/locales/it_IT.json b/bigbluebutton-html5/public/locales/it_IT.json similarity index 100% rename from bigbluebutton-html5/private/locales/it_IT.json rename to bigbluebutton-html5/public/locales/it_IT.json diff --git a/bigbluebutton-html5/private/locales/ja.json b/bigbluebutton-html5/public/locales/ja.json similarity index 100% rename from bigbluebutton-html5/private/locales/ja.json rename to bigbluebutton-html5/public/locales/ja.json diff --git a/bigbluebutton-html5/private/locales/ka.json b/bigbluebutton-html5/public/locales/ka.json similarity index 100% rename from bigbluebutton-html5/private/locales/ka.json rename to bigbluebutton-html5/public/locales/ka.json diff --git a/bigbluebutton-html5/private/locales/kk.json b/bigbluebutton-html5/public/locales/kk.json similarity index 100% rename from bigbluebutton-html5/private/locales/kk.json rename to bigbluebutton-html5/public/locales/kk.json diff --git a/bigbluebutton-html5/private/locales/km.json b/bigbluebutton-html5/public/locales/km.json similarity index 100% rename from bigbluebutton-html5/private/locales/km.json rename to bigbluebutton-html5/public/locales/km.json diff --git a/bigbluebutton-html5/private/locales/kn.json b/bigbluebutton-html5/public/locales/kn.json similarity index 100% rename from bigbluebutton-html5/private/locales/kn.json rename to bigbluebutton-html5/public/locales/kn.json diff --git a/bigbluebutton-html5/private/locales/ko_KR.json b/bigbluebutton-html5/public/locales/ko_KR.json similarity index 100% rename from bigbluebutton-html5/private/locales/ko_KR.json rename to bigbluebutton-html5/public/locales/ko_KR.json diff --git a/bigbluebutton-html5/private/locales/lo_LA.json b/bigbluebutton-html5/public/locales/lo_LA.json similarity index 100% rename from bigbluebutton-html5/private/locales/lo_LA.json rename to bigbluebutton-html5/public/locales/lo_LA.json diff --git a/bigbluebutton-html5/private/locales/lt_LT.json b/bigbluebutton-html5/public/locales/lt_LT.json similarity index 100% rename from bigbluebutton-html5/private/locales/lt_LT.json rename to bigbluebutton-html5/public/locales/lt_LT.json diff --git a/bigbluebutton-html5/private/locales/lv.json b/bigbluebutton-html5/public/locales/lv.json similarity index 100% rename from bigbluebutton-html5/private/locales/lv.json rename to bigbluebutton-html5/public/locales/lv.json diff --git a/bigbluebutton-html5/private/locales/mn_MN.json b/bigbluebutton-html5/public/locales/mn_MN.json similarity index 100% rename from bigbluebutton-html5/private/locales/mn_MN.json rename to bigbluebutton-html5/public/locales/mn_MN.json diff --git a/bigbluebutton-html5/private/locales/nb_NO.json b/bigbluebutton-html5/public/locales/nb_NO.json similarity index 100% rename from bigbluebutton-html5/private/locales/nb_NO.json rename to bigbluebutton-html5/public/locales/nb_NO.json diff --git a/bigbluebutton-html5/private/locales/nl.json b/bigbluebutton-html5/public/locales/nl.json similarity index 100% rename from bigbluebutton-html5/private/locales/nl.json rename to bigbluebutton-html5/public/locales/nl.json diff --git a/bigbluebutton-html5/private/locales/oc.json b/bigbluebutton-html5/public/locales/oc.json similarity index 100% rename from bigbluebutton-html5/private/locales/oc.json rename to bigbluebutton-html5/public/locales/oc.json diff --git a/bigbluebutton-html5/private/locales/pl_PL.json b/bigbluebutton-html5/public/locales/pl_PL.json similarity index 100% rename from bigbluebutton-html5/private/locales/pl_PL.json rename to bigbluebutton-html5/public/locales/pl_PL.json diff --git a/bigbluebutton-html5/private/locales/pt.json b/bigbluebutton-html5/public/locales/pt.json similarity index 100% rename from bigbluebutton-html5/private/locales/pt.json rename to bigbluebutton-html5/public/locales/pt.json diff --git a/bigbluebutton-html5/private/locales/pt_BR.json b/bigbluebutton-html5/public/locales/pt_BR.json similarity index 100% rename from bigbluebutton-html5/private/locales/pt_BR.json rename to bigbluebutton-html5/public/locales/pt_BR.json diff --git a/bigbluebutton-html5/private/locales/ro_RO.json b/bigbluebutton-html5/public/locales/ro_RO.json similarity index 100% rename from bigbluebutton-html5/private/locales/ro_RO.json rename to bigbluebutton-html5/public/locales/ro_RO.json diff --git a/bigbluebutton-html5/private/locales/ru.json b/bigbluebutton-html5/public/locales/ru.json similarity index 100% rename from bigbluebutton-html5/private/locales/ru.json rename to bigbluebutton-html5/public/locales/ru.json diff --git a/bigbluebutton-html5/private/locales/ru_RU.json b/bigbluebutton-html5/public/locales/ru_RU.json similarity index 100% rename from bigbluebutton-html5/private/locales/ru_RU.json rename to bigbluebutton-html5/public/locales/ru_RU.json diff --git a/bigbluebutton-html5/private/locales/sk_SK.json b/bigbluebutton-html5/public/locales/sk_SK.json similarity index 100% rename from bigbluebutton-html5/private/locales/sk_SK.json rename to bigbluebutton-html5/public/locales/sk_SK.json diff --git a/bigbluebutton-html5/private/locales/sl.json b/bigbluebutton-html5/public/locales/sl.json similarity index 100% rename from bigbluebutton-html5/private/locales/sl.json rename to bigbluebutton-html5/public/locales/sl.json diff --git a/bigbluebutton-html5/private/locales/sr.json b/bigbluebutton-html5/public/locales/sr.json similarity index 100% rename from bigbluebutton-html5/private/locales/sr.json rename to bigbluebutton-html5/public/locales/sr.json diff --git a/bigbluebutton-html5/private/locales/sv_SE.json b/bigbluebutton-html5/public/locales/sv_SE.json similarity index 100% rename from bigbluebutton-html5/private/locales/sv_SE.json rename to bigbluebutton-html5/public/locales/sv_SE.json diff --git a/bigbluebutton-html5/private/locales/te.json b/bigbluebutton-html5/public/locales/te.json similarity index 100% rename from bigbluebutton-html5/private/locales/te.json rename to bigbluebutton-html5/public/locales/te.json diff --git a/bigbluebutton-html5/private/locales/th.json b/bigbluebutton-html5/public/locales/th.json similarity index 100% rename from bigbluebutton-html5/private/locales/th.json rename to bigbluebutton-html5/public/locales/th.json diff --git a/bigbluebutton-html5/private/locales/th_TH.json b/bigbluebutton-html5/public/locales/th_TH.json similarity index 100% rename from bigbluebutton-html5/private/locales/th_TH.json rename to bigbluebutton-html5/public/locales/th_TH.json diff --git a/bigbluebutton-html5/private/locales/tr.json b/bigbluebutton-html5/public/locales/tr.json similarity index 100% rename from bigbluebutton-html5/private/locales/tr.json rename to bigbluebutton-html5/public/locales/tr.json diff --git a/bigbluebutton-html5/private/locales/tr_TR.json b/bigbluebutton-html5/public/locales/tr_TR.json similarity index 100% rename from bigbluebutton-html5/private/locales/tr_TR.json rename to bigbluebutton-html5/public/locales/tr_TR.json diff --git a/bigbluebutton-html5/private/locales/uk_UA.json b/bigbluebutton-html5/public/locales/uk_UA.json similarity index 100% rename from bigbluebutton-html5/private/locales/uk_UA.json rename to bigbluebutton-html5/public/locales/uk_UA.json diff --git a/bigbluebutton-html5/private/locales/vi.json b/bigbluebutton-html5/public/locales/vi.json similarity index 100% rename from bigbluebutton-html5/private/locales/vi.json rename to bigbluebutton-html5/public/locales/vi.json diff --git a/bigbluebutton-html5/private/locales/vi_VN.json b/bigbluebutton-html5/public/locales/vi_VN.json similarity index 100% rename from bigbluebutton-html5/private/locales/vi_VN.json rename to bigbluebutton-html5/public/locales/vi_VN.json diff --git a/bigbluebutton-html5/private/locales/zh_CN.json b/bigbluebutton-html5/public/locales/zh_CN.json similarity index 100% rename from bigbluebutton-html5/private/locales/zh_CN.json rename to bigbluebutton-html5/public/locales/zh_CN.json diff --git a/bigbluebutton-html5/private/locales/zh_TW.json b/bigbluebutton-html5/public/locales/zh_TW.json similarity index 100% rename from bigbluebutton-html5/private/locales/zh_TW.json rename to bigbluebutton-html5/public/locales/zh_TW.json diff --git a/bigbluebutton-html5/transifex.sh b/bigbluebutton-html5/transifex.sh index de1efe3eaa73f91ca62b80f75b450bf59da70587..b11dc50f7ac3f69058c0106abd1dfa425230b819 100755 --- a/bigbluebutton-html5/transifex.sh +++ b/bigbluebutton-html5/transifex.sh @@ -5,6 +5,14 @@ RED='\033[0;31m' GREEN='\033[1;32m' NC='\033[0m' SOURCE_LANGUAGE="en" +LOCALES_DIRECTORY="./public/locales" +PULL_SOURCE=false + +if [[ ! -e $LOCALES_DIRECTORY ]]; then + echo -e "Directory ${RED}$LOCALES_DIRECTORY${NC} does not exist, creating" + mkdir $LOCALES_DIRECTORY + PULL_SOURCE=true +fi if [ "$#" = 0 ] then @@ -33,19 +41,19 @@ else echo "$AVAILABLE_TRANSLATIONS" | while read l do LOCALE=$( echo "$l" | tr -d '[:space:]' ) - if [ "$LOCALE" == "$SOURCE_LANGUAGE" ]; then - continue # do not pull the source file + if [ "$LOCALE" == "$SOURCE_LANGUAGE" ] && [ "$PULL_SOURCE" == false ]; then + continue # only pull source file if locales folder did not exist fi TRANSLATION=$(curl -L --user "$USER":"$PW" -X GET "https://www.transifex.com/api/2/project/bigbluebutton-v23-html5-client/resource/enjson/translation/$LOCALE/?mode=onlytranslated&file") NO_EMPTY_STRINGS=$(echo "$TRANSLATION" | sed '/: *\"\"/D' | sed '/}$/D') - if [ $(echo "$NO_EMPTY_STRINGS" | wc -l) == 1 ] + if [ $(echo "$NO_EMPTY_STRINGS" | wc -l) -lt 100 ] then - echo -e "${RED}WARN:${NC} translation file $LOCALE.json is empty\n${RED}WARN:${NC} $LOCALE.json not created" + echo -e "${RED}WARN:${NC} translation file $LOCALE.json contains less than 100 lines\n${RED}WARN:${NC} $LOCALE.json not created" continue else NO_TRAILING_COMMA=$(echo "$NO_EMPTY_STRINGS" | sed '$ s/,$//') - echo "$NO_TRAILING_COMMA" > ./private/locales/"$LOCALE".json - echo -e "\n}\n" >> ./private/locales/"$LOCALE".json + echo "$NO_TRAILING_COMMA" > "$LOCALES_DIRECTORY/$LOCALE".json + echo -e "\n}\n" >> "$LOCALES_DIRECTORY/$LOCALE".json echo -e "Added translation file $LOCALE.json : ${GREEN}✓${NC}" fi done @@ -56,13 +64,13 @@ else echo -e "${RED}Err${NC}: Translations not found for locale ->${RED}$ARG${NC}<-" else NO_EMPTY_STRINGS=$(echo "$TRANSLATION" | sed '/: *\"\"/D' | sed '/}$/D') - if [ $(echo "$NO_EMPTY_STRINGS" | wc -l) == 1 ] + if [ $(echo "$NO_EMPTY_STRINGS" | wc -l) -lt 100 ] then - echo -e "${RED}WARN:${NC} translation file $ARG.json is empty\n${RED}WARN:${NC} $ARG.json not created" + echo -e "${RED}WARN:${NC} translation file $ARG.json contains less than 100 lines\n${RED}WARN:${NC} $ARG.json not created" else NO_TRAILING_COMMA=$(echo "$NO_EMPTY_STRINGS" | sed '$ s/,//') - echo "$NO_TRAILING_COMMA" > ./private/locales/"$ARG".json - echo -e "\n}\n" >> ./private/locales/"$ARG".json + echo "$NO_TRAILING_COMMA" > "$LOCALES_DIRECTORY/$ARG".json + echo -e "\n}\n" >> "$LOCALES_DIRECTORY/$ARG".json echo -e "Added translation file $ARG.json :${GREEN} ✓${NC}" fi fi diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index 9e4fd1028f418bac486f7064fd2571033cf68235..77049971a325d2109d268c2ad0ce4cb289056fae 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -1323,6 +1323,7 @@ class ApiController { // Get the client url we stored in the join api call before // being told to wait. String clientURL = us.clientUrl; + String lobbyMsg = meeting.getGuestLobbyMessage() log.info("clientURL = " + clientURL) log.info("redirect = ." + redirectClient) if (!StringUtils.isEmpty(params.redirect)) { @@ -1412,6 +1413,7 @@ class ApiController { auth_token us.authToken session_token session[sessionToken] guestStatus guestWaitStatus + lobbyMessage lobbyMsg url destUrl } render(contentType: "application/json", text: builder.toPrettyString())