diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala index aba3186cdc..a1ff8bd799 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Polls.scala @@ -255,6 +255,7 @@ object Polls { shape += "numRespondents" -> new Integer(result.numRespondents) shape += "numResponders" -> new Integer(result.numResponders) shape += "type" -> WhiteboardKeyUtil.POLL_RESULT_TYPE + shape += "pollType" -> result.questionType shape += "id" -> result.id shape += "status" -> WhiteboardKeyUtil.DRAW_END_STATUS @@ -644,7 +645,7 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I } def toSimplePollResultOutVO(): SimplePollResultOutVO = { - new SimplePollResultOutVO(id, questions(0).text, questions(0).toSimpleVotesOutVO(), numRespondents, _numResponders) + new SimplePollResultOutVO(id, questions(0).questionType, questions(0).text, questions(0).toSimpleVotesOutVO(), numRespondents, _numResponders) } } diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala index 1353a42527..50967fdaca 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala @@ -77,7 +77,7 @@ case class Meeting2x(defaultProps: DefaultProps, meetingStatus: MeetingStatus) case class SimpleAnswerOutVO(id: Int, key: String) case class SimplePollOutVO(id: String, answers: Array[SimpleAnswerOutVO]) case class SimpleVoteOutVO(id: Int, key: String, numVotes: Int) -case class SimplePollResultOutVO(id: String, questionText: Option[String], answers: Array[SimpleVoteOutVO], numRespondents: Int, numResponders: Int) +case class SimplePollResultOutVO(id: String, questionType: String, questionText: Option[String], answers: Array[SimpleVoteOutVO], numRespondents: Int, numResponders: Int) case class Responder(userId: String, name: String) case class AnswerVO(id: Int, key: String, text: Option[String], responders: Option[Array[Responder]]) case class QuestionVO(id: Int, questionType: String, multiResponse: Boolean, questionText: Option[String], answers: Option[Array[AnswerVO]]) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java index 40b81a2828..ccd588b4c9 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java @@ -81,6 +81,7 @@ public class ApiParams { // Needed for classes where teacher gets disconnected and can't get back in. Prevents // students from running amok. public static final String END_WHEN_NO_MODERATOR = "endWhenNoModerator"; + public static final String END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES = "endWhenNoModeratorDelayInMinutes"; private ApiParams() { throw new IllegalStateException("ApiParams is a utility class. Instanciation is forbidden."); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java index 5e95e27c39..e3754145c5 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java @@ -435,6 +435,15 @@ public class ParamsProcessorUtil { } } + int endWhenNoModeratorDelayInMinutes = defaultEndWhenNoModeratorDelayInMinutes; + if (!StringUtils.isEmpty(params.get(ApiParams.END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES))) { + try { + endWhenNoModeratorDelayInMinutes = Integer.parseInt(params.get(ApiParams.END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES)); + } catch (Exception ex) { + log.warn("Invalid param [endWhenNoModeratorDelayInMinutes] for meeting=[{}]", internalMeetingId); + } + } + String guestPolicy = defaultGuestPolicy; if (!StringUtils.isEmpty(params.get(ApiParams.GUEST_POLICY))) { guestPolicy = params.get(ApiParams.GUEST_POLICY); @@ -516,8 +525,8 @@ public class ParamsProcessorUtil { meeting.setUserActivitySignResponseDelayInMinutes(userActivitySignResponseDelayInMinutes); meeting.setUserInactivityThresholdInMinutes(userInactivityThresholdInMinutes); // meeting.setHtml5InstanceId(html5InstanceId); - meeting.setEndWhenNoModerator(defaultEndWhenNoModerator); - meeting.setEndWhenNoModeratorDelayInMinutes(defaultEndWhenNoModeratorDelayInMinutes); + meeting.setEndWhenNoModerator(endWhenNoModerator); + meeting.setEndWhenNoModeratorDelayInMinutes(endWhenNoModeratorDelayInMinutes); // Add extra parameters for breakout room if (isBreakout) { @@ -538,6 +547,12 @@ public class ParamsProcessorUtil { muteOnStart = Boolean.parseBoolean(params.get(ApiParams.MUTE_ON_START)); } + // when a moderator joins in a breakout room only with the audio, and the muteOnStart is set to true, + // the moderator is unable to unmute himself, because they don't have an icon to do so + if (isBreakout) { + muteOnStart = false; + } + meeting.setMuteOnStart(muteOnStart); Boolean meetingKeepEvents = defaultKeepEvents; diff --git a/bigbluebutton-html5/client/main.html b/bigbluebutton-html5/client/main.html index 09a8a2af05..11c972ede0 100755 --- a/bigbluebutton-html5/client/main.html +++ b/bigbluebutton-html5/client/main.html @@ -83,6 +83,17 @@ with BigBlueButton; if not, see . + + + + + + + + + + +
diff --git a/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js b/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js index e4efe88860..b82087d8bb 100644 --- a/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js +++ b/bigbluebutton-html5/imports/api/polls/server/handlers/userVoted.js @@ -7,6 +7,7 @@ export default function userVoted({ body }, meetingId) { check(meetingId, String); check(poll, { id: String, + questionType: String, questionText: String, answers: [ { diff --git a/bigbluebutton-html5/imports/api/polls/server/methods/startPoll.js b/bigbluebutton-html5/imports/api/polls/server/methods/startPoll.js index 728fbab6d7..f37caca858 100644 --- a/bigbluebutton-html5/imports/api/polls/server/methods/startPoll.js +++ b/bigbluebutton-html5/imports/api/polls/server/methods/startPoll.js @@ -3,7 +3,7 @@ import { check } from 'meteor/check'; import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; -export default function startPoll(pollType, pollId, question, answers) { +export default function startPoll(pollTypes, pollType, pollId, question, answers) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; let EVENT_NAME = 'StartPollReqMsg'; @@ -23,7 +23,7 @@ export default function startPoll(pollType, pollId, question, answers) { question, }; - if (pollType === 'custom') { + if (pollType === pollTypes.Custom) { EVENT_NAME = 'StartCustomPollReqMsg'; check(answers, Array); payload.answers = answers; diff --git a/bigbluebutton-html5/imports/startup/client/intl.jsx b/bigbluebutton-html5/imports/startup/client/intl.jsx index 235671f081..ad4fcbfeb3 100644 --- a/bigbluebutton-html5/imports/startup/client/intl.jsx +++ b/bigbluebutton-html5/imports/startup/client/intl.jsx @@ -6,6 +6,7 @@ import Settings from '/imports/ui/services/settings'; import LoadingScreen from '/imports/ui/components/loading-screen/component'; import getFromUserSettings from '/imports/ui/services/users-settings'; import _ from 'lodash'; +import { Session } from 'meteor/session'; const propTypes = { locale: PropTypes.string, @@ -14,7 +15,8 @@ const propTypes = { const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale; -const RTL_LANGUAGES = ['ar', 'he', 'fa']; +const RTL_LANGUAGES = ['ar', 'dv', 'fa', 'he']; +const LARGE_FONT_LANGUAGES = ['te', 'km']; const defaultProps = { locale: DEFAULT_LANGUAGE, @@ -30,6 +32,8 @@ class IntlStartup extends Component { document.body.parentNode.setAttribute('dir', 'ltr'); Settings.application.isRTL = false; } + Session.set('isLargeFont', LARGE_FONT_LANGUAGES.includes(localeName.substring(0, 2))); + window.dispatchEvent(new Event('localeChanged')); Settings.save(); } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx index 5b8ca67b47..70b2d64e04 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx @@ -111,11 +111,17 @@ const intlMessages = defineMessages({ id: 'app.userList.you', description: 'Text for identifying your user', }, + minimumDurationWarnBreakout: { + id: 'app.createBreakoutRoom.minimumDurationWarnBreakout', + description: 'minimum duration warning message label', + }, + }); const BREAKOUT_LIM = Meteor.settings.public.app.breakouts.breakoutRoomLimit; const MIN_BREAKOUT_ROOMS = 2; const MAX_BREAKOUT_ROOMS = BREAKOUT_LIM > MIN_BREAKOUT_ROOMS ? BREAKOUT_LIM : MIN_BREAKOUT_ROOMS; +const MIN_BREAKOUT_TIME = 5; const propTypes = { intl: PropTypes.shape({ @@ -177,6 +183,7 @@ class BreakoutRoom extends PureComponent { valid: true, record: false, numberOfRoomsIsValid: true, + durationIsValid: true, breakoutJoinedUsers: null, }; @@ -307,19 +314,26 @@ class BreakoutRoom extends PureComponent { freeJoin, record, numberOfRoomsIsValid, + durationIsValid, } = this.state; + const { numberOfRooms, durationTime } = this.state; + + if ((durationTime || 0) < MIN_BREAKOUT_TIME) { + this.setState({ durationIsValid: false }); + return; + } + if (users.length === this.getUserByRoom(0).length && !freeJoin) { this.setState({ valid: false }); return; } - if (!numberOfRoomsIsValid) { + if (!numberOfRoomsIsValid || !durationIsValid) { return; } this.setState({ preventClosing: false }); - const { numberOfRooms, durationTime } = this.state; const rooms = _.range(1, numberOfRooms + 1).map((value) => ({ users: this.getUserByRoom(value).map((u) => u.userId), name: intl.formatMessage(intlMessages.roomName, { @@ -466,17 +480,25 @@ class BreakoutRoom extends PureComponent { increaseDurationTime() { const { durationTime } = this.state; - this.setState({ durationTime: (1 * durationTime) + 1 }); + const number = ((1 * durationTime) + 1); + const newDurationTime = number > MIN_BREAKOUT_TIME ? number : MIN_BREAKOUT_TIME; + + this.setState({ durationTime: newDurationTime, durationIsValid: true }); } decreaseDurationTime() { const { durationTime } = this.state; const number = ((1 * durationTime) - 1); - this.setState({ durationTime: number < 1 ? 1 : number }); + const newDurationTime = number > MIN_BREAKOUT_TIME ? number : MIN_BREAKOUT_TIME; + + this.setState({ durationTime: newDurationTime, durationIsValid: true }); } changeDurationTime(event) { - this.setState({ durationTime: Number.parseInt(event.target.value, 10) || '' }); + const durationTime = Number.parseInt(event.target.value, 10) || ''; + const durationIsValid = durationTime >= MIN_BREAKOUT_TIME; + + this.setState({ durationTime, durationIsValid }); } blurDurationTime(event) { @@ -489,7 +511,7 @@ class BreakoutRoom extends PureComponent { this.setState({ numberOfRooms, numberOfRoomsIsValid: numberOfRooms <= MAX_BREAKOUT_ROOMS - && numberOfRooms >= MIN_BREAKOUT_ROOMS, + && numberOfRooms >= MIN_BREAKOUT_ROOMS, }); } @@ -552,6 +574,7 @@ class BreakoutRoom extends PureComponent { numberOfRooms, durationTime, numberOfRoomsIsValid, + durationIsValid, } = this.state; if (isInvitation) return null; @@ -580,7 +603,7 @@ class BreakoutRoom extends PureComponent { } -