diff --git a/akka-bbb-apps/src/main/java/org/bigbluebutton/core/pubsub/receivers/PresentationMessageListener.java b/akka-bbb-apps/src/main/java/org/bigbluebutton/core/pubsub/receivers/PresentationMessageListener.java index d91e5f38ded2a98c00aff0903d89c8865ac62305..f3d2ac2dcb86114b8635d788cfbced6982f4c345 100755 --- a/akka-bbb-apps/src/main/java/org/bigbluebutton/core/pubsub/receivers/PresentationMessageListener.java +++ b/akka-bbb-apps/src/main/java/org/bigbluebutton/core/pubsub/receivers/PresentationMessageListener.java @@ -1,7 +1,6 @@ package org.bigbluebutton.core.pubsub.receivers; import java.util.HashMap; -import java.util.Map; import org.bigbluebutton.common.messages.GetPresentationInfoMessage; import org.bigbluebutton.common.messages.GetSlideInfoMessage; @@ -15,19 +14,18 @@ import org.bigbluebutton.common.messages.SendCursorUpdateMessage; import org.bigbluebutton.common.messages.SendPageCountErrorMessage; import org.bigbluebutton.common.messages.SendSlideGeneratedMessage; import org.bigbluebutton.common.messages.SharePresentationMessage; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - import org.bigbluebutton.core.api.IBigBlueButtonInGW; -import com.google.gson.JsonParser; +import com.google.gson.Gson; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; public class PresentationMessageListener implements MessageHandler { public static final String OFFICE_DOC_CONVERSION_SUCCESS_KEY = "OFFICE_DOC_CONVERSION_SUCCESS"; public static final String OFFICE_DOC_CONVERSION_FAILED_KEY = "OFFICE_DOC_CONVERSION_FAILED"; + public static final String OFFICE_DOC_CONVERSION_INVALID_KEY = "OFFICE_DOC_CONVERSION_INVALID"; public static final String SUPPORTED_DOCUMENT_KEY = "SUPPORTED_DOCUMENT"; public static final String UNSUPPORTED_DOCUMENT_KEY = "UNSUPPORTED_DOCUMENT"; public static final String PAGE_COUNT_FAILED_KEY = "PAGE_COUNT_FAILED"; @@ -145,13 +143,14 @@ public class PresentationMessageListener implements MessageHandler { String conference = (String) map.get("conference"); String messageKey = (String) map.get("messageKey"); - if (messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_SUCCESS_KEY) || - messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_FAILED_KEY) || - messageKey.equalsIgnoreCase(SUPPORTED_DOCUMENT_KEY) || - messageKey.equalsIgnoreCase(UNSUPPORTED_DOCUMENT_KEY) || - messageKey.equalsIgnoreCase(GENERATING_THUMBNAIL_KEY) || - messageKey.equalsIgnoreCase(GENERATED_THUMBNAIL_KEY) || - messageKey.equalsIgnoreCase(PAGE_COUNT_FAILED_KEY)){ + if (messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_SUCCESS_KEY) || + messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_FAILED_KEY) || + messageKey.equalsIgnoreCase(OFFICE_DOC_CONVERSION_INVALID_KEY) || + messageKey.equalsIgnoreCase(SUPPORTED_DOCUMENT_KEY) || + messageKey.equalsIgnoreCase(UNSUPPORTED_DOCUMENT_KEY) || + messageKey.equalsIgnoreCase(GENERATING_THUMBNAIL_KEY) || + messageKey.equalsIgnoreCase(GENERATED_THUMBNAIL_KEY) || + messageKey.equalsIgnoreCase(PAGE_COUNT_FAILED_KEY)){ sendConversionUpdate(messageKey, conference, code, presId, filename); } else if(messageKey.equalsIgnoreCase(PAGE_COUNT_EXCEEDED_KEY)){ diff --git a/bbb-lti/grails-app/conf/lti-config.properties b/bbb-lti/grails-app/conf/lti-config.properties index c2a898c2d5a8f1efc3f804cc9487a1ccd0c5776d..6984fce90df8f13dabb302aa1fa86434e59903b5 100644 --- a/bbb-lti/grails-app/conf/lti-config.properties +++ b/bbb-lti/grails-app/conf/lti-config.properties @@ -20,14 +20,14 @@ # BigBlueButton integration information #---------------------------------------------------- -# This URL is where the BBB client is accessible. +# This URL is where the BBB client is accessible. bigbluebuttonURL=http://localhost/bigbluebutton # Salt which is used by 3rd-party apps to authenticate api calls bigbluebuttonSalt=bbb_salt # LTI basic information #---------------------------------------------------- -# This URL is where the LTI plugin is accessible. It can be a different server than the BigBluebutton one +# This URL is where the LTI plugin is accessible. It can be a different server than the BigBluebutton one # Only the hostname or IP address is required, plus the port number in case it is other than port 80 # e.g. localhost or localhost:port ltiEndPoint=localhost @@ -41,6 +41,9 @@ ltiMode=extended # Defines if LTI credentials are required # Format: [false|<true>] ltiRestrictedAccess=true +# Sets all the meetings to be recorded by default +# Format: [<false>|true] +ltiAllRecordedByDefault=false #---------------------------------------------------- # Inject configuration values into BigbluebuttonSrvice beans @@ -53,4 +56,4 @@ beans.ltiService.endPoint=${ltiEndPoint} beans.ltiService.consumers=${ltiConsumers} beans.ltiService.mode=${ltiMode} beans.ltiService.restrictedAccess=${ltiRestrictedAccess} - +beans.ltiService.recordedByDefault=${ltiAllRecordedByDefault} diff --git a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy index fbf2574b934fabc52b72424446c47223102d5e7e..600218fedfb3fb3544939bf3798492c7fb3d19d5 100644 --- a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy +++ b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy @@ -1,5 +1,5 @@ package org.bigbluebutton -/* +/* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). @@ -83,8 +83,8 @@ class ToolController { result = doJoinMeeting(params) } else { log.debug "LTI service running in extended mode." - if ( !Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) ) { - log.debug "No bbb_record parameter was sent; immediately redirecting to BBB session!" + if ( !Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) && !ltiService.allRecordedByDefault() ) { + log.debug "Parameter custom_record was not sent; immediately redirecting to BBB session!" result = doJoinMeeting(params) } } @@ -222,7 +222,7 @@ class ToolController { log.debug "Overriding default welcome message with: [" + welcome + "]" } - if ( params.containsKey(Parameter.CUSTOM_RECORD) && Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) ) { + if ( params.containsKey(Parameter.CUSTOM_RECORD) && Boolean.parseBoolean(params.get(Parameter.CUSTOM_RECORD)) || ltiService.allRecordedByDefault() ) { welcome += "<br><b>" + message(code: "bigbluebutton.welcome.record") + "</b><br>" log.debug "Adding record warning to welcome message, welcome is now: [" + welcome + "]" } diff --git a/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy index 1a9d90e29febb4a902ba56db71d97efb5e9cab98..297a6ce55422c33579812f4de11ead16c2791793 100644 --- a/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy +++ b/bbb-lti/grails-app/services/org/bigbluebutton/BigbluebuttonService.groovy @@ -1,5 +1,5 @@ package org.bigbluebutton -/* +/* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). @@ -92,7 +92,7 @@ class BigbluebuttonService { Integer duration = 0 if( "extended".equals(mode) ){ voiceBridge = getValidatedBBBVoiceBridge(params.get(Parameter.CUSTOM_VOICEBRIDGE)) - record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_RECORD)) + record = getValidatedBBBRecord(params.get(Parameter.CUSTOM_RECORD)) || ltiService.allRecordedByDefault() duration = getValidatedBBBDuration(params.get(Parameter.CUSTOM_DURATION)) } @@ -240,7 +240,7 @@ class BigbluebuttonService { private String getValidatedUserId(String userId){ return (userId == null)? "": userId } - + private Integer getValidatedBBBVoiceBridge(String voiceBridge){ return (voiceBridge != null )? voiceBridge.toInteger(): 0 } diff --git a/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy index 2048d06383b243f2df2467df6ad3cce834b95d65..c78c127ba0eecd608ccd347761b02c4cb495a86b 100644 --- a/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy +++ b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy @@ -1,5 +1,5 @@ package org.bigbluebutton -/* +/* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). @@ -32,9 +32,10 @@ class LtiService { def consumers = "demo:welcome" def mode = "simple" def restrictedAccess = "true" + def recordedByDefault = "false" Map<String, String> consumerMap - + def retrieveIconEndpoint() { return endPoint.replaceFirst("tool", "images/icon.ico") } @@ -42,16 +43,16 @@ class LtiService { def retrieveBasicLtiEndpoint() { return endPoint } - + private Map<String, String> getConsumer(consumerId) { Map<String, String> consumer = null - + if( this.consumerMap.containsKey(consumerId) ){ consumer = new HashMap<String, String>() consumer.put("key", consumerId); consumer.put("secret", this.consumerMap.get(consumerId)) } - + return consumer } @@ -66,19 +67,19 @@ class LtiService { this.consumerMap.put(consumer[0], consumer[1]) } } - + } - + public String sign(String sharedSecret, String data) throws Exception { Mac mac = setKey(sharedSecret) - + // Signed String must be BASE64 encoded. byte[] signBytes = mac.doFinal(data.getBytes("UTF8")); String signature = encodeBase64(signBytes); return signature; } - + private Mac setKey(String sharedSecret) throws Exception { Mac mac = Mac.getInstance("HmacSHA1"); @@ -137,10 +138,14 @@ class LtiService { log.debug("Exception: Message=" + e.getMessage()) } - return ssl_enabled + return ssl_enabled } def boolean hasRestrictedAccess() { return Boolean.parseBoolean(this.restrictedAccess); } + + def boolean allRecordedByDefault() { + return Boolean.parseBoolean(this.recordedByDefault); + } } diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ClientMessage.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ClientMessage.java index 627712d50bc42a5401291e3f0ee9bc1ffea969e5..94db9f56fabd58d684cc2b6efb921ba8b98fc35e 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ClientMessage.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ClientMessage.java @@ -21,4 +21,5 @@ package org.bigbluebutton.red5.client.messaging; public interface ClientMessage { + String getMessageName(); } diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ConnectionInvokerService.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ConnectionInvokerService.java index 2d23b2118c7bbf49c41e6262b7571179ec8610e0..89471e30cce9ac8602615f44d7fd6c429406cddd 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ConnectionInvokerService.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/ConnectionInvokerService.java @@ -21,7 +21,6 @@ package org.bigbluebutton.red5.client.messaging; import java.util.Set; import java.util.ArrayList; import java.util.List; -import java.util.HashSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -44,6 +43,8 @@ import org.red5.server.util.ScopeUtils; import org.slf4j.Logger; import com.google.gson.Gson; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; public class ConnectionInvokerService { private static Logger log = Red5LoggerFactory.getLogger(ConnectionInvokerService.class, "bigbluebutton"); @@ -75,15 +76,21 @@ public class ConnectionInvokerService { ClientMessage message; try { message = messages.take(); - sendMessageToClient(message); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (log.isTraceEnabled()) { + log.trace("Took message from queue: " + message.getMessageName()); + } + sendMessageToClient(message); + if (log.isTraceEnabled()) { + log.trace("Sent message to client: " + message.getMessageName()); + } + } catch (Exception e) { + Marker sendingException = MarkerFactory.getMarker("SENDING_EXCEPTION"); + log.error(sendingException, "Exception while sending message to client.", e); } } } }; - exec.execute(sender); + exec.execute(sender); } public void stop() { @@ -92,6 +99,9 @@ public class ConnectionInvokerService { } public void sendMessage(final ClientMessage message) { + if (log.isTraceEnabled()) { + log.trace("Queue message: " + message.getMessageName()); + } messages.offer(message); } @@ -124,7 +134,7 @@ public class ConnectionInvokerService { conn.close(); } } - } + } } private void handleDisconnectAllClientsMessage(DisconnectAllClientsMessage msg) { @@ -152,9 +162,9 @@ public class ConnectionInvokerService { log.info("Disconnecting user=[{}] from meeting=[{}]", msg.getUserId(), msg.getMeetingId()); conn.close(); } - } - } - } + } + } + } private void sendSharedObjectMessage(SharedObjectClientMessage msg) { IScope meetingScope = getScope(msg.getMeetingID()); @@ -167,7 +177,8 @@ public class ConnectionInvokerService { } } } - + + private void sendDirectMessage(final DirectClientMessage msg) { if (log.isTraceEnabled()) { Gson gson = new Gson(); @@ -200,9 +211,9 @@ public class ConnectionInvokerService { log.info("Cannot send message=[" + msg.getMessageName() + "] to [" + userId + "] as no such session on meeting=[" + msg.getMeetingID() + "]"); } - } + } } - }; + }; /** * We need to add a way to cancel sending when the thread is blocked. @@ -218,6 +229,7 @@ public class ConnectionInvokerService { f.get(timeLeft, TimeUnit.NANOSECONDS); } catch (ExecutionException e) { log.warn("ExecutionException while sending direct message on connection[" + userId + "]"); + log.warn("ExcecutionException cause: " + e.getMessage()); } catch (InterruptedException e) { log.warn("Interrupted exception while sending direct message on connection[" + userId + "]"); Thread.currentThread().interrupt(); @@ -226,7 +238,7 @@ public class ConnectionInvokerService { f.cancel(true); } } - + private void sendBroadcastMessage(final BroadcastClientMessage msg) { if (log.isTraceEnabled()) { Gson gson = new Gson(); @@ -241,11 +253,13 @@ public class ConnectionInvokerService { List<Object> params = new ArrayList<Object>(); params.add(msg.getMessageName()); params.add(msg.getMessage()); + if (log.isTraceEnabled()) { Gson gson = new Gson(); String json = gson.toJson(msg.getMessage()); log.trace("Broadcast message: " + msg.getMessageName() + " msg=" + json); } + ServiceUtils.invokeOnAllScopeConnections(meetingScope, "onMessageFromServer", params.toArray(), null); } } @@ -266,10 +280,10 @@ public class ConnectionInvokerService { } catch (ExecutionException e) { log.warn("ExecutionException while sending broadcast message[" + msg.getMessageName() + "]"); } catch (InterruptedException e) { - log.warn("Interrupted exception while sending direct message[" + msg.getMessageName() + "]"); + log.warn("Interrupted exception while sending broadcast message[" + msg.getMessageName() + "]"); Thread.currentThread().interrupt(); } catch (TimeoutException e) { - log.warn("Timeout exception while sending direct message[" + msg.getMessageName() + "]"); + log.warn("Timeout exception while sending broadcast message[" + msg.getMessageName() + "]"); f.cancel(true); } } diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllClientsMessage.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllClientsMessage.java index ddedc077c5d646546fd8f7d11fea278e352a2a55..d3f833842953ba4034a946fe3acbfac25e7d8038 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllClientsMessage.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllClientsMessage.java @@ -11,4 +11,8 @@ public class DisconnectAllClientsMessage implements ClientMessage { public String getMeetingId() { return meetingId; } + + public String getMessageName() { + return "DisconnectAllClientsMessage"; + } } diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllMessage.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllMessage.java index 080d0ee6e2a8abf3fa7e7979c10777c55c5ad52e..38fcbddc3a703439cf12608c4554f4ebd7eec913 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllMessage.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectAllMessage.java @@ -2,4 +2,7 @@ package org.bigbluebutton.red5.client.messaging; public class DisconnectAllMessage implements ClientMessage { + public String getMessageName() { + return "DisconnectAllMessage"; + } } diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectClientMessage.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectClientMessage.java index f0a9ba73ff34d8a8fe122fb97f2d6ca90c885264..fe8ce941263444f2fcdc5413b3d4c73b0eff5723 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectClientMessage.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/red5/client/messaging/DisconnectClientMessage.java @@ -17,4 +17,8 @@ public class DisconnectClientMessage implements ClientMessage { public String getUserId() { return userId; } + + public String getMessageName() { + return "DisconnectClientMessage"; + } } diff --git a/bigbluebutton-client/branding/default/style/css/BBBDefault.css b/bigbluebutton-client/branding/default/style/css/BBBDefault.css index 80da61a02d50572f8dc0000103a5fe52b1e431d4..fdda4d8cc12ea576a6ff4ffb467ec19ce346b717 100755 --- a/bigbluebutton-client/branding/default/style/css/BBBDefault.css +++ b/bigbluebutton-client/branding/default/style/css/BBBDefault.css @@ -1061,6 +1061,11 @@ EmojiGrid { horizontalGap: 6; } +RoomActionsRenderer { + paddingLeft : 5; + paddingRight : 5; +} + .breakoutRoomUserWindowHeadingStyle { fontWeight: bold; } diff --git a/bigbluebutton-client/locale/en_US/bbbResources.properties b/bigbluebutton-client/locale/en_US/bbbResources.properties index a8b2abb91ad4b4a97fcd915e2e49db3ff56d3928..fa6e9638ca21aaa64c925c16444ce98c19a91ba9 100755 --- a/bigbluebutton-client/locale/en_US/bbbResources.properties +++ b/bigbluebutton-client/locale/en_US/bbbResources.properties @@ -196,6 +196,7 @@ bbb.presentation.uploaded = uploaded. bbb.presentation.document.supported = The uploaded document is supported. Starting to convert... bbb.presentation.document.converted = Successfully converted the office document. bbb.presentation.error.document.convert.failed = Error: Unable to convert the office document. +bbb.presentation.error.document.convert.invalid = Please convert this document to PDF first. bbb.presentation.error.io = IO Error: Please contact administrator. bbb.presentation.error.security = Security Error: Please contact administrator. bbb.presentation.error.convert.notsupported = Error: The uploaded document is unsupported. Please upload a compatible file. @@ -641,10 +642,10 @@ bbb.lockSettings.lockOnJoin=Lock On Join bbb.users.breakout.breakoutRooms = Breakout Rooms bbb.users.breakout.updateBreakoutRooms = Update Breakout Rooms -bbb.users.breakout.remainingTimeBreakout = {0}: <b>{1} remaining</b> -bbb.users.breakout.remainingTimeParent = <b>{1} remaining</b> +bbb.users.breakout.timer = <b>{0}</b> +bbb.users.breakout.timer.toolTip = Time left for breakout rooms bbb.users.breakout.calculatingRemainingTime = Calculating remaining time... -bbb.users.breakout.remainingTimeEnded = Time ended, breakout room will close. +bbb.users.breakout.closing = Closing bbb.users.breakout.rooms = Rooms bbb.users.breakout.roomsCombo.accessibilityName = Number of rooms to create bbb.users.breakout.room = Room diff --git a/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js b/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js index 066aeedadfddd641e3b4e87d6f2e7ef780d0f747..05d07355da9193bc7b83609b76dc5f39cc9766be 100755 --- a/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js +++ b/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js @@ -1,7 +1,7 @@ var userID, callerIdName=null, conferenceVoiceBridge, userAgent=null, userMicMedia, userWebcamMedia, currentSession=null, callTimeout, callActive, callICEConnected, iceConnectedTimeout, callFailCounter, callPurposefullyEnded, uaConnected, transferTimeout, iceGatheringTimeout; var inEchoTest = true; -var html5StunTurn = {}; +var html5StunTurn = null; function webRTCCallback(message) { switch (message.status) { @@ -47,8 +47,10 @@ function callIntoConference(voiceBridge, callback, isListenOnly, stunTurn = null // if additional stun configuration is passed, store the information if (stunTurn != null) { - html5StunTurn['stunServers'] = stunTurn.stun; - html5StunTurn['turnServers'] = stunTurn.turn; + html5StunTurn = { + stunServers: stunTurn.stun, + turnServers: stunTurn.turn, + }; } // reset callerIdName diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as b/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as index d292cba3b61df05fdd9bcfccb0ecf46f2dbaeac2..99e0d3f532d8b0c69d0e8fa76c4d1120313bde49 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as +++ b/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as @@ -65,6 +65,7 @@ package org.bigbluebutton.core /** For Conversion Update Events **/ public static const OFFICE_DOC_CONVERSION_SUCCESS:String = "OfficeDocConversionSuccessEvent"; public static const OFFICE_DOC_CONVERSION_FAILED:String = "OfficeDocConversionFailedEvent"; + public static const OFFICE_DOC_CONVERSION_INVALID:String = "OfficeDocConversionInvalidEvent"; public static const SUPPORTED_DOCUMENT:String = "SupportedDocEvent"; public static const UNSUPPORTED_DOCUMENT:String = "UnsupportedDocEvent"; public static const PAGE_COUNT_FAILED:String = "PageCountFailedEvent"; diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as b/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as index 1ff3cc9ed38042a30868521d422a8e6e4b8a12c3..53df479fb403f3b61935536d5a0ac074cf03f0a5 100644 --- a/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as +++ b/bigbluebutton-client/src/org/bigbluebutton/core/TimerUtil.as @@ -36,13 +36,10 @@ package org.bigbluebutton.core { timer.addEventListener(TimerEvent.TIMER, function():void { var remainingSeconds:int = timer.repeatCount - timer.currentCount; var formattedTime:String = (Math.floor(remainingSeconds / 60)) + ":" + (remainingSeconds % 60 >= 10 ? "" : "0") + (remainingSeconds % 60); - label.htmlText = ResourceUtil.getInstance().getString( - UserManager.getInstance().getConference().isBreakout ? 'bbb.users.breakout.remainingTimeBreakout' : 'bbb.users.breakout.remainingTimeParent', - [UserManager.getInstance().getConference().meetingName, formattedTime] - ); + label.htmlText = ResourceUtil.getInstance().getString('bbb.users.breakout.timer', [formattedTime]); }); timer.addEventListener(TimerEvent.TIMER_COMPLETE, function():void { - label.text = ResourceUtil.getInstance().getString('bbb.users.breakout.remainingTimeEnded'); + label.text = ResourceUtil.getInstance().getString('bbb.users.breakout.closing'); }); } else { timer.stop(); diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as b/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as index 6a7e2123e4879d7719f60472df49122c9594db1a..a6bd4d1d864c078f8e55e0fdf9699f2c1370b575 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as @@ -51,6 +51,7 @@ package org.bigbluebutton.main.api import org.bigbluebutton.modules.present.events.CreatingThumbnailsEvent; import org.bigbluebutton.modules.present.events.GetListOfPresentationsReply; import org.bigbluebutton.modules.present.events.OfficeDocConvertFailedEvent; + import org.bigbluebutton.modules.present.events.OfficeDocConvertInvalidEvent; import org.bigbluebutton.modules.present.events.OfficeDocConvertSuccessEvent; import org.bigbluebutton.modules.present.events.UploadEvent; import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; @@ -317,6 +318,12 @@ package org.bigbluebutton.main.api payload.eventName = EventConstants.OFFICE_DOC_CONVERSION_SUCCESS; broadcastEvent(payload); } + + public function handleOfficeDocConversionInvalid(event:OfficeDocConvertInvalidEvent):void{ + var payload:Object = new Object(); + payload.eventName = EventConstants.OFFICE_DOC_CONVERSION_INVALID; + broadcastEvent(payload); + } public function handleOfficeDocConversionFailed(event:OfficeDocConvertFailedEvent):void{ var payload:Object = new Object(); diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml index 4d7dac46e19e837bfbcfe6b70b93d75efc1a5dd0..663eec955b2132a156990f3ec8b950958e8eb569 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml @@ -23,32 +23,33 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <EventMap xmlns="http://mate.asfusion.com/" xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ - import org.bigbluebutton.core.EventConstants; - import org.bigbluebutton.core.events.AmIPresenterQueryEvent; - import org.bigbluebutton.core.events.AmISharingWebcamQueryEvent; - import org.bigbluebutton.core.events.GetMyUserInfoRequestEvent; - import org.bigbluebutton.core.events.IsUserPublishingCamRequest; - import org.bigbluebutton.core.events.SwitchedLayoutEvent; - import org.bigbluebutton.main.api.ExternalApiCalls; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.events.LogoutEvent; - import org.bigbluebutton.main.events.SwitchedPresenterEvent; - import org.bigbluebutton.main.events.UserJoinedEvent; - import org.bigbluebutton.main.events.UserLeftEvent; - import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent; - import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent; - import org.bigbluebutton.main.model.users.events.StreamStartedEvent; - import org.bigbluebutton.modules.present.events.ConversionCompletedEvent; - import org.bigbluebutton.modules.present.events.ConversionPageCountError; - import org.bigbluebutton.modules.present.events.ConversionPageCountMaxed; - import org.bigbluebutton.modules.present.events.ConversionSupportedDocEvent; - import org.bigbluebutton.modules.present.events.ConversionUnsupportedDocEvent; - import org.bigbluebutton.modules.present.events.ConversionUpdateEvent; - import org.bigbluebutton.modules.present.events.CreatingThumbnailsEvent; - import org.bigbluebutton.modules.present.events.GetListOfPresentationsReply; - import org.bigbluebutton.modules.present.events.OfficeDocConvertFailedEvent; - import org.bigbluebutton.modules.present.events.OfficeDocConvertSuccessEvent; - import org.bigbluebutton.modules.present.events.UploadEvent; + import org.bigbluebutton.core.EventConstants; + import org.bigbluebutton.core.events.AmIPresenterQueryEvent; + import org.bigbluebutton.core.events.AmISharingWebcamQueryEvent; + import org.bigbluebutton.core.events.GetMyUserInfoRequestEvent; + import org.bigbluebutton.core.events.IsUserPublishingCamRequest; + import org.bigbluebutton.core.events.SwitchedLayoutEvent; + import org.bigbluebutton.main.api.ExternalApiCalls; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.LogoutEvent; + import org.bigbluebutton.main.events.SwitchedPresenterEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; + import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent; + import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent; + import org.bigbluebutton.main.model.users.events.StreamStartedEvent; + import org.bigbluebutton.modules.present.events.ConversionCompletedEvent; + import org.bigbluebutton.modules.present.events.ConversionPageCountError; + import org.bigbluebutton.modules.present.events.ConversionPageCountMaxed; + import org.bigbluebutton.modules.present.events.ConversionSupportedDocEvent; + import org.bigbluebutton.modules.present.events.ConversionUnsupportedDocEvent; + import org.bigbluebutton.modules.present.events.ConversionUpdateEvent; + import org.bigbluebutton.modules.present.events.CreatingThumbnailsEvent; + import org.bigbluebutton.modules.present.events.GetListOfPresentationsReply; + import org.bigbluebutton.modules.present.events.OfficeDocConvertFailedEvent; + import org.bigbluebutton.modules.present.events.OfficeDocConvertInvalidEvent; + import org.bigbluebutton.modules.present.events.OfficeDocConvertSuccessEvent; + import org.bigbluebutton.modules.present.events.UploadEvent; ]]> </mx:Script> <!-- @@ -149,6 +150,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <MethodInvoker generator="{ExternalApiCalls}" method="handleOfficeDocConversionSuccess" arguments="{event}" /> </EventHandlers> + <EventHandlers type="{OfficeDocConvertInvalidEvent.OFFICE_DOC_CONVERT_INVALID}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleOfficeDocConversionInvalid" arguments="{event}" /> + </EventHandlers> + <EventHandlers type="{OfficeDocConvertFailedEvent.OFFICE_DOC_CONVERT_FAILED}" > <MethodInvoker generator="{ExternalApiCalls}" method="handleOfficeDocConversionFailed" arguments="{event}" /> </EventHandlers> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/events/ExitApplicationEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/events/ExitApplicationEvent.as new file mode 100644 index 0000000000000000000000000000000000000000..312ab69be400f046282f59151e0d9a37b8705fb1 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/events/ExitApplicationEvent.as @@ -0,0 +1,30 @@ +/** + * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ + * + * Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below). + * + * This program is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation; either version 3.0 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along + * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + * + */ +package org.bigbluebutton.main.events { + import flash.events.Event; + + public class ExitApplicationEvent extends Event { + + public static const EXIT_APPLICATION:String = "EXIT_APPLICATION"; + + public function ExitApplicationEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false) { + super(type, bubbles, cancelable); + } + } +} diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as index c6b8a1202250a839c72ab9e469503aca87a9e3c9..91cad9a61f065aa100e11173d0915ac9f3fbdcf9 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as @@ -29,6 +29,7 @@ package org.bigbluebutton.main.model.users { import org.bigbluebutton.common.Role; import org.bigbluebutton.core.BBB; import org.bigbluebutton.core.model.Config; + import org.bigbluebutton.core.model.MeetingModel; import org.bigbluebutton.core.vo.CameraSettingsVO; import org.bigbluebutton.core.vo.LockSettingsVO; @@ -50,6 +51,8 @@ package org.bigbluebutton.main.model.users { public var isBreakout:Boolean; + public var iAskedToLogout:Boolean + [Bindable] public var record:Boolean; @@ -197,8 +200,13 @@ package org.bigbluebutton.main.model.users { } } return null; - } - + } + + public function userIsModerator(userId:String):Boolean { + var user:BBBUser = getUser(userId); + return user != null && user.role == Role.MODERATOR; + } + public function getPresenter():BBBUser { var p:BBBUser; for (var i:int = 0; i < users.length; i++) { @@ -409,14 +417,19 @@ package org.bigbluebutton.main.model.users { } users.refresh(); } - - public function sharedWebcam(userId:String, stream:String):void { - var aUser:BBBUser = getUser(userId); - if (aUser != null) { - aUser.sharedWebcam(stream) - } - users.refresh(); - } + + public function sharedWebcam(userId:String, stream:String):void { + var webcamsOnlyForModerator:Boolean = MeetingModel.getInstance().meeting.webcamsOnlyForModerator; + if (!webcamsOnlyForModerator || + (webcamsOnlyForModerator && (amIModerator() || userIsModerator(userId))) + ) { + var aUser:BBBUser = getUser(userId); + if (aUser != null) { + aUser.sharedWebcam(stream) + } + users.refresh(); + } + } public function unsharedWebcam(userId:String, stream:String):void { var aUser:BBBUser = getUser(userId); diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as index ba6043e0d05df3c7c619d928bed8829031f91b86..3e97d8f4a7e194effb6ca028ca2b23e15614802d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as @@ -18,7 +18,8 @@ */ package org.bigbluebutton.main.model.users { - import com.asfusion.mate.events.Dispatcher; + import com.asfusion.mate.events.Dispatcher; + import flash.events.AsyncErrorEvent; import flash.events.IOErrorEvent; import flash.events.NetStatusEvent; @@ -27,13 +28,12 @@ package org.bigbluebutton.main.model.users import flash.net.NetConnection; import flash.net.Responder; import flash.utils.Timer; + import org.as3commons.logging.api.ILogger; import org.as3commons.logging.api.getClassLogger; import org.bigbluebutton.core.BBB; import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.managers.ReconnectionManager; - import org.bigbluebutton.core.services.BandwidthMonitor; - import org.bigbluebutton.main.api.JSLog; import org.bigbluebutton.main.events.BBBEvent; import org.bigbluebutton.main.events.InvalidAuthTokenEvent; import org.bigbluebutton.main.model.ConferenceParameters; diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml index 1453adab799b262efacdb3897d614b283e364836..5254556d829630f57992ce1963ca8d2c61a4995e 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/LoggedOutWindow.mxml @@ -26,6 +26,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. x="168" y="86" layout="vertical" width="400" height="110" horizontalAlign="center"> <mx:Script> <![CDATA[ + import com.asfusion.mate.events.Dispatcher; + import flash.net.navigateToURL; import mx.managers.PopUpManager; @@ -35,6 +37,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.core.BBB; import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.main.events.ExitApplicationEvent; import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent; import org.bigbluebutton.util.i18n.ResourceUtil; @@ -62,11 +65,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. } private function exitApplication():void { - if (!UserManager.getInstance().getConference().isBreakout) { - navigateToURL(new URLRequest(BBB.getLogoutURL()), "_self"); - } else { - ExternalInterface.call("window.close"); - } + var d:Dispatcher = new Dispatcher(); + d.dispatchEvent(new ExitApplicationEvent(ExitApplicationEvent.EXIT_APPLICATION)); } private function handleComplete(e:Event):void { diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml index f0a90158456fa05ffe1b75d8eb8d111d0dfe9598..fba02f7be2d4375fa5c9c5bd2225ef5ce9a21b6f 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml @@ -51,6 +51,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mate:Listener type="{ConnectionFailedEvent.CONNECTION_CLOSED}" method="attemptReconnect" /> <mate:Listener type="{ConnectionFailedEvent.UNKNOWN_REASON}" method="attemptReconnect" /> <mate:Listener type="{ConnectionFailedEvent.CONNECTION_REJECTED}" method="attemptReconnect" /> + <mate:Listener type="{ExitApplicationEvent.EXIT_APPLICATION}" method="handleExitApplicationEvent" /> <mate:Listener type="{ConfigLoadedEvent.CONFIG_LOADED_EVENT}" method="initOptions" /> <mate:Listener type="{FlashMicSettingsEvent.FLASH_MIC_SETTINGS}" method="handleFlashMicSettingsEvent" /> <mate:Listener type="{WebRTCEchoTestEvent.WEBRTC_ECHO_TEST_CONNECTING}" method="handleWebRTCEchoTestConnectingEvent" /> @@ -77,6 +78,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import flash.events.IOErrorEvent; import flash.events.TextEvent; import flash.geom.Point; + import flash.net.navigateToURL; import mx.collections.ArrayCollection; import mx.controls.Alert; @@ -97,6 +99,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.common.events.OpenWindowEvent; import org.bigbluebutton.common.events.ToolbarButtonEvent; import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.events.LockControlEvent; import org.bigbluebutton.core.managers.UserManager; import org.bigbluebutton.core.vo.LockSettingsVO; @@ -105,6 +108,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.main.events.BreakoutRoomEvent; import org.bigbluebutton.main.events.ClientStatusEvent; import org.bigbluebutton.main.events.ConfigLoadedEvent; + import org.bigbluebutton.main.events.ExitApplicationEvent; import org.bigbluebutton.main.events.InvalidAuthTokenEvent; import org.bigbluebutton.main.events.MeetingNotFoundEvent; import org.bigbluebutton.main.events.ModuleLoadEvent; @@ -120,7 +124,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.modules.users.views.BreakoutRoomSettings; import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - import org.bigbluebutton.core.UsersUtil; private static const LOGGER:ILogger = getClassLogger(MainApplicationShell); @@ -507,53 +510,64 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private function handleMeetingNotFoundEvent(e:MeetingNotFoundEvent):void { showlogoutWindow(ResourceUtil.getInstance().getString('bbb.mainshell.meetingNotFound')); } - - private function showlogoutWindow(reason:String):void { - if (layoutOptions!= null && layoutOptions.showLogoutWindow) { - if (logoutWindow != null) return; - logoutWindow = LoggedOutWindow(PopUpManager.createPopUp( mdiCanvas, LoggedOutWindow, true)); - - var point1:Point = new Point(); - // Calculate position of TitleWindow in Application's coordinates. - point1.x = width/2; - point1.y = height/2; - logoutWindow.x = point1.x - (logoutWindow.width/2); - logoutWindow.y = point1.y - (logoutWindow.height/2); - - logoutWindow.setReason(reason); - mdiCanvas.removeAllPopUps(); - removeToolBars(); - } else { - mdiCanvas.removeAllPopUps(); - removeToolBars(); - var pageHost:String = FlexGlobals.topLevelApplication.url.split("/")[0]; - var pageURL:String = FlexGlobals.topLevelApplication.url.split("/")[2]; - LOGGER.debug("SingOut to [{0}//{1}/bigbluebutton/api/signOut]", [pageHost, pageURL]); - var request:URLRequest = new URLRequest(pageHost + "//" + pageURL + "/bigbluebutton/api/signOut"); - var urlLoader:URLLoader = new URLLoader(); - urlLoader.addEventListener(Event.COMPLETE, handleLogoutComplete); - urlLoader.addEventListener(IOErrorEvent.IO_ERROR, handleLogoutError); - urlLoader.load(request); - } - } - - /** - * Removes toolbars from the display list. - * Used only when the user completely logged out. - */ - private function removeToolBars():void{ - this.removeChild(toolbar); - this.removeChild(controlBar); - } - - - private function handleLogout(e:ConnectionFailedEvent):void { - if (e is ConnectionFailedEvent) { - showlogoutWindow((e as ConnectionFailedEvent).type); - } - else showlogoutWindow("You have logged out of the conference"); - } - + + private function showlogoutWindow(reason:String):void { + if (layoutOptions!= null && layoutOptions.showLogoutWindow) { + if (UserManager.getInstance().getConference().iAskedToLogout) { + handleExitApplicationEvent(); + return; + } + if (logoutWindow != null) return; + logoutWindow = LoggedOutWindow(PopUpManager.createPopUp( mdiCanvas, LoggedOutWindow, true)); + + var point1:Point = new Point(); + // Calculate position of TitleWindow in Application's coordinates. + point1.x = width/2; + point1.y = height/2; + logoutWindow.x = point1.x - (logoutWindow.width/2); + logoutWindow.y = point1.y - (logoutWindow.height/2); + + logoutWindow.setReason(reason); + mdiCanvas.removeAllPopUps(); + removeToolBars(); + } else { + mdiCanvas.removeAllPopUps(); + removeToolBars(); + var pageHost:String = FlexGlobals.topLevelApplication.url.split("/")[0]; + var pageURL:String = FlexGlobals.topLevelApplication.url.split("/")[2]; + LOGGER.debug("SingOut to [{0}//{1}/bigbluebutton/api/signOut]", [pageHost, pageURL]); + var request:URLRequest = new URLRequest(pageHost + "//" + pageURL + "/bigbluebutton/api/signOut"); + var urlLoader:URLLoader = new URLLoader(); + urlLoader.addEventListener(Event.COMPLETE, handleLogoutComplete); + urlLoader.addEventListener(IOErrorEvent.IO_ERROR, handleLogoutError); + urlLoader.load(request); + } + } + + /** + * Removes toolbars from the display list. + * Used only when the user completely logged out. + */ + private function removeToolBars():void { + this.removeChild(toolbar); + this.removeChild(controlBar); + } + + private function handleLogout(e:ConnectionFailedEvent):void { + if (e is ConnectionFailedEvent) { + showlogoutWindow((e as ConnectionFailedEvent).type); + } else + showlogoutWindow("You have logged out of the conference"); + } + + private function handleExitApplicationEvent(e:ExitApplicationEvent = null):void { + if (!UserManager.getInstance().getConference().isBreakout) { + navigateToURL(new URLRequest(BBB.getLogoutURL()), "_self"); + } else { + ExternalInterface.call("window.close"); + } + } + private function redirectToLogoutUrl ():void { var logoutURL:String = BBB.getLogoutURL(); var request:URLRequest = new URLRequest(logoutURL); @@ -591,12 +605,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private function handleWebRTCCallEndedEvent(e:WebRTCCallEvent):void { lblWebRTC.visible = lblWebRTC.includeInLayout = false; } - - private function handleInvalidAuthToken(event:InvalidAuthTokenEvent):void { - showlogoutWindow(ResourceUtil.getInstance().getString('bbb.mainshell.invalidAuthToken')); - globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.CANCEL_RECONNECTION_EVENT)); - } - + + private function handleInvalidAuthToken(event:InvalidAuthTokenEvent):void { + showlogoutWindow(ResourceUtil.getInstance().getString('bbb.mainshell.invalidAuthToken')); + globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.CANCEL_RECONNECTION_EVENT)); + } + private function handleRemoveToolbarComponent(event:ToolbarButtonEvent):void { if (addedBtns.contains(event.button as UIComponent)) addedBtns.removeChild(event.button as UIComponent); diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml index f73c205eb38f9be439a7efa1612abfbb035c3bc6..79b90afdbadbfade20c7d0e8ef64ea33693fd21a 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml @@ -54,6 +54,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.common.events.ToolbarButtonEvent; import org.bigbluebutton.core.BBB; import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.managers.UserManager; import org.bigbluebutton.main.events.BBBEvent; import org.bigbluebutton.main.events.ConfigEvent; import org.bigbluebutton.main.events.LogoutEvent; @@ -238,6 +239,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private function alertLogout(e:CloseEvent):void { // Check to see if the YES button was pressed. if (e.detail==Alert.YES) { + UserManager.getInstance().getConference().iAskedToLogout = true; /* * If doLogout() is called immediately there is a null exception in AlertAccImpl * line 185, but if we delay calling doLogout() until the next frame the Alert diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml index 152718c8e43b9dcf35a33d2844ba3615c064ab4b..705544162c3ecc45ce8af54b4d8e528f58c25f38 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml @@ -782,7 +782,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mx:HBox id="timerBox" styleName="breakoutRoomTimerBox" includeInLayout="false" visible="false" width="100%" height="0"> - <mx:Label id="timerLabel" text="{ResourceUtil.getInstance().getString('bbb.users.breakout.calculatingRemainingTime')}"/> + <mx:Label id="timerLabel" + text="{ResourceUtil.getInstance().getString('bbb.users.breakout.calculatingRemainingTime')}" + toolTip="{ResourceUtil.getInstance().getString('bbb.users.breakout.timer.toolTip')}"/> </mx:HBox> </mx:VBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/events/OfficeDocConvertInvalidEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/present/events/OfficeDocConvertInvalidEvent.as new file mode 100644 index 0000000000000000000000000000000000000000..c3bf2e68a2c52566279b2e7673b5b18f73b260c2 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/events/OfficeDocConvertInvalidEvent.as @@ -0,0 +1,11 @@ +package org.bigbluebutton.modules.present.events { + import flash.events.Event; + + public class OfficeDocConvertInvalidEvent extends Event { + public static const OFFICE_DOC_CONVERT_INVALID:String = "presentation office doc convert aborted event"; + + public function OfficeDocConvertInvalidEvent() { + super(OFFICE_DOC_CONVERT_INVALID, true, false); + } + } +} diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/Constants.as b/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/Constants.as index 68717fa2da3d38ae69a6d1545144827bdb2c3dc5..ee73d68e692377c5372cd1acbb7b50f44fb84e7d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/Constants.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/Constants.as @@ -3,7 +3,8 @@ package org.bigbluebutton.modules.present.services public class Constants { public static const OFFICE_DOC_CONVERSION_SUCCESS_KEY:String = "OFFICE_DOC_CONVERSION_SUCCESS"; - public static const OFFICE_DOC_CONVERSION_FAILED_KEY:String = "OFFICE_DOC_CONVERSION_FAILED"; + public static const OFFICE_DOC_CONVERSION_FAILED_KEY:String = "OFFICE_DOC_CONVERSION_FAILED"; + public static const OFFICE_DOC_CONVERSION_INVALID_KEY:String = "OFFICE_DOC_CONVERSION_INVALID"; public static const SUPPORTED_DOCUMENT_KEY:String = "SUPPORTED_DOCUMENT"; public static const UNSUPPORTED_DOCUMENT_KEY:String = "UNSUPPORTED_DOCUMENT"; public static const PAGE_COUNT_FAILED_KEY:String = "PAGE_COUNT_FAILED"; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/messaging/MessageReceiver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/messaging/MessageReceiver.as index c7a89d1b3970bb8b3e48712ead9deb24e27d8c1e..3dce38d50ddb1198d83ad51ac6c9417721fb520f 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/messaging/MessageReceiver.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/services/messaging/MessageReceiver.as @@ -34,6 +34,7 @@ package org.bigbluebutton.modules.present.services.messaging import org.bigbluebutton.modules.present.events.ConversionUpdateEvent; import org.bigbluebutton.modules.present.events.CreatingThumbnailsEvent; import org.bigbluebutton.modules.present.events.OfficeDocConvertFailedEvent; + import org.bigbluebutton.modules.present.events.OfficeDocConvertInvalidEvent; import org.bigbluebutton.modules.present.events.OfficeDocConvertSuccessEvent; import org.bigbluebutton.modules.present.events.UploadEvent; import org.bigbluebutton.modules.present.model.PresentationModel; @@ -243,6 +244,9 @@ package org.bigbluebutton.modules.present.services.messaging case Constants.OFFICE_DOC_CONVERSION_FAILED_KEY : dispatcher.dispatchEvent(new OfficeDocConvertFailedEvent()); break; + case Constants.OFFICE_DOC_CONVERSION_INVALID_KEY : + dispatcher.dispatchEvent(new OfficeDocConvertInvalidEvent()); + break; case Constants.SUPPORTED_DOCUMENT_KEY : dispatcher.dispatchEvent(new ConversionSupportedDocEvent()); break; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml index 410e40c51e043ae9827744d4fdb5644b72204f91..990a666584178b4a3fe67fe7ffd6d6cf8ed94aca 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml @@ -33,8 +33,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <mate:Listener type="{ConversionCompletedEvent.CONVERSION_COMPLETED}" method="handleConversionCompleted" /> <mate:Listener type="{ConversionUpdateEvent.CONVERSION_UPDATE}" method="handleConvertUpdate" /> <mate:Listener type="{CreatingThumbnailsEvent.CREATING_THUMBNAILS}" method="handleThumbnailsProgressEvent" /> - <mate:Listener type="{OfficeDocConvertFailedEvent.OFFICE_DOC_CONVERT_FAILED}" method="handleOfficeDocumentConversionFailed"/> - <mate:Listener type="{OfficeDocConvertSuccessEvent.OFFICE_DOC_CONVERT_SUCCESS}" method="handleOfficeDocumentConversionSuccess"/> + <mate:Listener type="{OfficeDocConvertFailedEvent.OFFICE_DOC_CONVERT_FAILED}" method="handleOfficeDocumentConversionFailed"/> + <mate:Listener type="{OfficeDocConvertInvalidEvent.OFFICE_DOC_CONVERT_INVALID}" method="handleOfficeDocumentConversionInvalid"/> + <mate:Listener type="{OfficeDocConvertSuccessEvent.OFFICE_DOC_CONVERT_SUCCESS}" method="handleOfficeDocumentConversionSuccess"/> <mate:Listener type="{ConversionSupportedDocEvent.SUPPORTED_DOC}" method="handleSupportedDocument"/> <mate:Listener type="{ConversionUnsupportedDocEvent.UNSUPPORTED_DOC}" method="handleUnsupportedDocument"/> <mate:Listener type="{ConversionPageCountError.PAGE_COUNT_ERROR}" method="handlePageCountFailed"/> @@ -44,10 +45,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <![CDATA[ import mx.collections.ArrayCollection; import mx.utils.StringUtil; - import org.bigbluebutton.core.UsersUtil; + + import org.as3commons.lang.StringUtils; import org.as3commons.logging.api.ILogger; import org.as3commons.logging.api.getClassLogger; import org.bigbluebutton.common.Images; + import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.modules.present.commands.UploadFileCommand; import org.bigbluebutton.modules.present.events.ConversionCompletedEvent; import org.bigbluebutton.modules.present.events.ConversionPageCountError; @@ -57,6 +60,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. import org.bigbluebutton.modules.present.events.ConversionUpdateEvent; import org.bigbluebutton.modules.present.events.CreatingThumbnailsEvent; import org.bigbluebutton.modules.present.events.OfficeDocConvertFailedEvent; + import org.bigbluebutton.modules.present.events.OfficeDocConvertInvalidEvent; import org.bigbluebutton.modules.present.events.OfficeDocConvertSuccessEvent; import org.bigbluebutton.modules.present.events.RemovePresentationEvent; import org.bigbluebutton.modules.present.events.UploadCompletedEvent; @@ -203,7 +207,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private function handleOfficeDocumentConversionFailed(e:OfficeDocConvertFailedEvent):void { enableControls(); displayAlert(ResourceUtil.getInstance().getString('bbb.presentation.error.document.convert.failed')); - } + } + + private function handleOfficeDocumentConversionInvalid(e:OfficeDocConvertInvalidEvent):void { + enableControls(); + displayAlert(ResourceUtil.getInstance().getString('bbb.presentation.error.document.convert.invalid')); + } private function handleOfficeDocumentConversionSuccess(e:OfficeDocConvertSuccessEvent):void { progressBar.label = ResourceUtil.getInstance().getString('bbb.presentation.document.converted'); @@ -248,7 +257,10 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. private function displayAlert(error:String, message:String = null):void { var okLabel:String = ResourceUtil.getInstance().getString('bbb.presentation.ok'); progressBar.setStyle("color", 0xFF0000); - progressBar.label = error + message; + progressBar.label = error; + if (!StringUtils.isEmpty(message)) { + progressBar.label += message; + } okCancelBtn.label = "Ok"; } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as index ea047ef44c45dca16224b68ed07941e7fa7680ac..ec7f8b4b51c5056ba42e28f8b8da4e74849baf35 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageReceiver.as @@ -548,18 +548,11 @@ package org.bigbluebutton.modules.users.services private function handleEmojiStatusHand(msg: Object): void { var map:Object = JSON.parse(msg.msg); UserManager.getInstance().getConference().emojiStatus(map.userId, map.emojiStatus); - } + } private function handleUserSharedWebcam(msg:Object):void { var map:Object = JSON.parse(msg.msg); - if (!MeetingModel.getInstance().meeting.webcamsOnlyForModerator) { - UserManager.getInstance().getConference().sharedWebcam(map.userId, map.webcamStream); - } else if ( - UserManager.getInstance().getConference().amIModerator() || - (UserManager.getInstance().getConference().getUser(map.userId) != null && UserManager.getInstance().getConference().getUser(map.userId).role == Role.MODERATOR) - ) { - UserManager.getInstance().getConference().sharedWebcam(map.userId, map.webcamStream); - } + UserManager.getInstance().getConference().sharedWebcam(map.userId, map.webcamStream); } private function handleUserUnsharedWebcam(msg: Object):void { diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml index ad652990da7f395add122120aba899e054af4f06..bcfeda8a92d3ebaf0170c333a31afb20a31a6050 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/views/UsersWindow.mxml @@ -140,10 +140,12 @@ amIModerator = UserManager.getInstance().getConference().amIModerator(); amIPresenter = UserManager.getInstance().getConference().amIPresenter; - + settingsBtn.visible = settingsBtn.includeInLayout = partOptions.enableSettingsButton && amIModerator; closeRoomsBtn.visible = closeRoomsBtn.includeInLayout = amIModerator; - + + emojiStatusBtn.visible = emojiStatusBtn.includeInLayout = partOptions.enableEmojiStatus; + BindingUtils.bindSetter(updateNumberofUsers, users, "length"); this.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown); @@ -206,10 +208,6 @@ resourcesChanged(); } - private function changeButtons(presenter:Boolean):void { - emojiStatusBtn.visible = emojiStatusBtn.includeInLayout = partOptions.enableEmojiStatus; - } - /* * Work around for a bug with the users grid. When you click on one of the buttons in an item renderer the client * locks up briefly and any mouse movements while the client is locked up are ignored. This means that roll outs @@ -602,6 +600,15 @@ private function breakoutRoomNameLabelFunction(item:Object, column:DataGridColumn) : String { return ResourceUtil.getInstance().getString('bbb.users.roomsGrid.room') + " " + item.sequence; } + + private function breakoutRoomsToolTip(item:Object):String { + var room:BreakoutRoom = item as BreakoutRoom; + var names:Array = []; + for (var i:int = 0; i < room.users.length; i++) { + names.push(room.users.getItemAt(i)["name"]); + } + return names.join("\n"); + } ]]> </mx:Script> @@ -629,17 +636,24 @@ width="100%" height="180"> <mx:HBox width="100%"> <mx:Label styleName="breakoutRoomUserWindowHeadingStyle" text="{ResourceUtil.getInstance().getString('bbb.users.breakout.breakoutRooms')}"/> - <mx:Label styleName="breakoutRoomUserWindowHeadingStyle" width="100%" textAlign="right" id="breakoutTimeLabel" text="..."/> + <mx:Label styleName="breakoutRoomUserWindowHeadingStyle" width="100%" textAlign="right" id="breakoutTimeLabel" + text="..." toolTip="{ResourceUtil.getInstance().getString('bbb.users.breakout.timer.toolTip')}"/> </mx:HBox> - <mx:DataGrid id="roomsGrid" editable="false" sortableColumns="false" dataProvider="{breakoutRoomsList}" + <mx:DataGrid id="roomsGrid" editable="false" sortableColumns="false" + dataProvider="{breakoutRoomsList}" dataTipFunction="breakoutRoomsToolTip" dragEnabled="false" width="100%" height="100%" draggableColumns="false" accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.breakout.breakoutRooms')}"> <mx:columns> - <mx:DataGridColumn labelFunction="breakoutRoomNameLabelFunction" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.room')}" /> - <mx:DataGridColumn dataField="numberOfUsers" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.users')}"/> - <mx:DataGridColumn dataField="meetingId" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.action')}" + <mx:DataGridColumn labelFunction="breakoutRoomNameLabelFunction" + showDataTips="true" + headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.room')}" /> + <mx:DataGridColumn dataField="numberOfUsers" + showDataTips="true" + headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.users')}"/> + <mx:DataGridColumn dataField="meetingId" visible="{amIModerator}" + headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.action')}" itemRenderer="org.bigbluebutton.modules.users.views.RoomActionsRenderer"/> </mx:columns> </mx:DataGrid> @@ -649,12 +663,11 @@ </mx:VBox> <mx:ControlBar width="100%"> - <mx:Button id="emojiStatusBtn" icon="{images.emoji_raiseHand}" width="30" height="30" + <mx:Button id="emojiStatusBtn" icon="{images.emoji_happy}" width="30" height="30" accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.emojiStatusBtn.toolTip')}" - toolTip="{ResourceUtil.getInstance().getString('bbb.users.emojiStatusBtn.toolTip')}" click="openEmojiStatusMenu()" - visible="true" /> + toolTip="{ResourceUtil.getInstance().getString('bbb.users.emojiStatusBtn.toolTip')}" click="openEmojiStatusMenu()" /> <mx:Button id="settingsBtn" icon="{images.users_settings}" width="30" height="30" - toolTip="{ResourceUtil.getInstance().getString('bbb.users.settings.buttonTooltip')}" click="openSettings()" visible="true" /> + toolTip="{ResourceUtil.getInstance().getString('bbb.users.settings.buttonTooltip')}" click="openSettings()" /> <mx:VBox> <mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.roomMuted.text')}" visible="{roomMuted}" includeInLayout="{roomMuted}" /> <mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.roomLocked.text')}" visible="{roomLocked}" includeInLayout="{roomLocked}" /> diff --git a/bigbluebutton-html5/imports/api/phone/index.js b/bigbluebutton-html5/imports/api/phone/index.js index 7cd680ca22da61f582b105e0803a7044a8a4bf6c..f5e17501e4b901ef2c53ca391b03d13978e1e691 100755 --- a/bigbluebutton-html5/imports/api/phone/index.js +++ b/bigbluebutton-html5/imports/api/phone/index.js @@ -106,7 +106,20 @@ function joinVoiceCallSIP(options) { turn: m.turns, }; - callIntoConference(extension, function () {}, options.isListenOnly, st); + callIntoConference(extension, function (audio) { + switch (audio.status) { + case 'failed': + let audioFailed = new CustomEvent('bbb.webrtc.failed', { + status: 'Failed' }); + window.dispatchEvent(audioFailed); + break; + case 'mediafail': + let mediaFailed = new CustomEvent('bbb.webrtc.mediaFailed', { + status: 'MediaFailed' }); + window.dispatchEvent(mediaFailed); + break; + } + }, options.isListenOnly, st); return; } } diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setUserPresenter.js b/bigbluebutton-html5/imports/api/users/server/methods/setUserPresenter.js index 23848c54280dae060cda59eb7b2a3c039427602b..220d542f60c585ea38ca6e605278de09626bbdb6 100755 --- a/bigbluebutton-html5/imports/api/users/server/methods/setUserPresenter.js +++ b/bigbluebutton-html5/imports/api/users/server/methods/setUserPresenter.js @@ -5,7 +5,7 @@ import { appendMessageHeader } from '/imports/api/common/server/helpers'; Meteor.methods({ //meetingId: the meeting where the user is //newPresenterId: the userid of the new presenter - //requesterSetPresenter: the userid of the user that wants to change the presenter + //requesterUserId: the userid of the user that wants to change the presenter //newPresenterName: user name of the new presenter //authToken: the authToken of the user that wants to kick setUserPresenter( @@ -13,7 +13,7 @@ Meteor.methods({ newPresenterId, newPresenterName) { const REDIS_CONFIG = Meteor.settings.redis; - const { meetingId, requesterSetPresenter, requesterToken } = credentials; + const { meetingId, requesterUserId } = credentials; let message; if (isAllowedTo('setPresenter', credentials)) { message = { @@ -21,7 +21,7 @@ Meteor.methods({ new_presenter_id: newPresenterId, new_presenter_name: newPresenterName, meeting_id: meetingId, - assigned_by: requesterSetPresenter, + assigned_by: requesterUserId, }, }; diff --git a/bigbluebutton-html5/imports/locales/en.json b/bigbluebutton-html5/imports/locales/en.json index f219046e68762adab632117ed9d9228dea97ccda..25442f286d1f4887ad1eab678694b87441d8ea65 100755 --- a/bigbluebutton-html5/imports/locales/en.json +++ b/bigbluebutton-html5/imports/locales/en.json @@ -81,6 +81,8 @@ "app.actionsBar.emojiMenu.thumbsupDesc": "Change your status to thumbs up", "app.actionsBar.emojiMenu.thumbsdownLabel": "Thumbs down", "app.actionsBar.emojiMenu.thumbsdownDesc": "Change your status to thumbs down", + "app.audioNotification.audioFailedMessage": "Your audio connection failed to connect. Try again.", + "app.audioNotification.mediaFailedMessage": "getUserMicMedia failed, Only secure origins are allowed", "app.breakoutJoinConfirmation.title": "Join Breakout Room", "app.breakoutJoinConfirmation.message": "Do you want to join", "app.breakoutJoinConfirmation.confirmLabel": "Join", diff --git a/bigbluebutton-html5/imports/startup/server/userPermissions.js b/bigbluebutton-html5/imports/startup/server/userPermissions.js index 8129963e7c95d33884143f890e1d8f5b14eb8f2f..5011b0d80a3a504134b8972cc6df27dfb197742b 100755 --- a/bigbluebutton-html5/imports/startup/server/userPermissions.js +++ b/bigbluebutton-html5/imports/startup/server/userPermissions.js @@ -27,6 +27,8 @@ const moderator = { // muting muteSelf: true, unmuteSelf: true, + muteOther: true, + unmuteOther: true, logoutSelf: true, @@ -76,9 +78,9 @@ const viewer = function (meetingId, userId) { // muting muteSelf: true, unmuteSelf: - !((meeting = Meetings.findOne({ meetingId: meetingId })) != null && + !((meeting = Meetings.findOne({ meetingId })) != null && meeting.roomLockSettings.disableMic) || - !((user = Users.findOne({ meetingId: meetingId, userId: userId })) != null && + !((user = Users.findOne({ meetingId, userId })) != null && user.user.locked), logoutSelf: true, @@ -88,15 +90,15 @@ const viewer = function (meetingId, userId) { subscribeChat: true, //chat - chatPublic: !((meeting = Meetings.findOne({ meetingId: meetingId })) != null && + chatPublic: !((meeting = Meetings.findOne({ meetingId })) != null && meeting.roomLockSettings.disablePublicChat) || - !((user = Users.findOne({ meetingId: meetingId, userId: userId })) != null && + !((user = Users.findOne({ meetingId, userId })) != null && user.user.locked) || (user != null && user.user.presenter), - chatPrivate: !((meeting = Meetings.findOne({ meetingId: meetingId })) != null && + chatPrivate: !((meeting = Meetings.findOne({ meetingId })) != null && meeting.roomLockSettings.disablePrivateChat) || - !((user = Users.findOne({ meetingId: meetingId, userId: userId })) != null && + !((user = Users.findOne({ meetingId, userId })) != null && user.user.locked) || (user != null && user.user.presenter), @@ -120,70 +122,42 @@ export function isAllowedTo(action, credentials) { const userId = credentials.requesterUserId; const authToken = credentials.requesterToken; - let user; - let validated; - - user = Users.findOne({ - meetingId: meetingId, - userId: userId, + const user = Users.findOne({ + meetingId, + userId, }); - if (user != null) { - validated = user.validated; - } - logger.info( - `in isAllowedTo: action-${action}, userId=${userId}, ` + - `authToken=${authToken} validated:${validated}` - ); - user = Users.findOne({ - meetingId: meetingId, - userId: userId, - }); + const allowedToInitiateRequest = + null != user && + authToken === user.authToken && + user.validated && + 'HTML5' === user.clientType && + null != user.user; + + if (allowedToInitiateRequest) { + let result = false; + + // check role specific actions + if ('MODERATOR' === user.user.role) { + logger.debug('user permissions moderator case'); + result = result || moderator[action]; + } else if ('VIEWER' === user.user.role) { + logger.debug('user permissions viewer case'); + result = result || viewer(meetingId, userId)[action]; + } - // logger.info "user=" + JSON.stringify user - if ((user != null) && authToken === user.authToken) { // check if the user is who he claims to be - if (user.validated && user.clientType === 'HTML5') { - - // PRESENTER - // check presenter specific actions or fallback to regular viewer actions - if (user.user != null && user.user.presenter) { - logger.info('user permissions presenter case'); - return presenter[action] || viewer(meetingId, userId)[action] || false; - - // VIEWER - } else if (user.user != null && user.user.role === 'VIEWER') { - logger.info('user permissions viewer case'); - return viewer(meetingId, userId)[action] || false; - - // MODERATOR - } else if (user.user != null && user.user.role === 'MODERATOR') { - logger.info('user permissions moderator case'); - return moderator[action] || false; - } else { - logger.warn(`UNSUCCESSFULL ATTEMPT FROM userid=${userId} to perform:${action}`); - return false; - } - } else { - // user was not validated - if (action === 'logoutSelf') { - // on unsuccessful sign-in - logger.warn( - 'a user was successfully removed from the ' + - 'meeting following an unsuccessful login' - ); - return true; - } - - return false; + // check presenter actions + if (user.user.presenter) { + logger.debug('user permissions presenter case'); + result = result || presenter[action]; } - } else { - logger.error( - `in meetingId=${meetingId} userId=${userId} tried to perform ${action} ` + - `without permission${'\n..while the authToken was ' + - (user != null && user.authToken != null ? user.authToken : void 0) + - " and the user's object is " + (JSON.stringify(user))}` - ); + logger.debug(`attempt from userId=${userId} to perform:${action}, allowed=${result}`); + + return result; + } else { + logger.error(`FAILED due to permissions:${action} ${JSON.stringify(credentials)}`); return false; } + }; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx index 6548ab3a9970727e627d746b23436dcb5b1a17d0..b2a0e9bc378af93be62e0d24bcd64e0c0e4f92e9 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/component.jsx @@ -5,12 +5,16 @@ import styles from '../styles.scss'; export default class MuteAudio extends React.Component { render() { - const { isInAudio, isMuted, callback } = this.props; + const { isInAudio, isMuted, callback, isTalking} = this.props; let label = !isMuted ? 'Mute' : 'Unmute'; - let icon = !isMuted ? 'audio-off' : 'audio'; + let icon = !isMuted ? 'audio' : 'audio-off'; let className = !isInAudio ? styles.invisible : null; let tabIndex = !isInAudio ? -1 : 0; + if (isInAudio && isTalking) { + className = styles.circleGlow; + } + return ( <Button onClick={callback} diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx index 9a8ad360884ad3df95df95a425e9b378de2d36c7..176bf187d38acda731c39b2ab0719dbf6e3acc7d 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/mute-button/container.jsx @@ -19,6 +19,8 @@ export default createContainer((params) => { const user = Users.findOne({ userId: userId }).user; const isMuted = user.voiceUser.muted; const isInAudio = user.voiceUser.joined; + const isTalking = user.voiceUser.talking; + let callback = () => {}; if (isInAudio && !isMuted) { @@ -33,6 +35,7 @@ export default createContainer((params) => { isInAudio, isMuted, callback, + isTalking, }; return data; }, MuteAudioContainer); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss index 04f69baa3d0c52de8347bfeea400d4ccc552508d..08cabd778238c58c5c85cb6219d3971b9eec43e4 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/styles.scss @@ -30,3 +30,7 @@ .invisible { visibility: hidden; } + +.circleGlow > :first-child{ + box-shadow: 0 0 .15rem #FFF !important; +} diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index 13f4d49fd0b437dcabfe83a8239f726858b2712f..1a8d49d3ecc5c8b1276e9cdb3a36c145f5b9185e 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -6,6 +6,7 @@ import LoadingScreen from '../loading-screen/component'; import KickedScreen from '../kicked-screen/component'; import NotificationsBarContainer from '../notifications-bar/container'; +import AudioNotificationContainer from '../audio-notification/container'; import LocalStorage from '/imports/ui/services/storage/local.js'; @@ -200,6 +201,7 @@ export default class App extends Component { return ( <main className={styles.main}> + <AudioNotificationContainer /> <NotificationsBarContainer /> <section className={styles.wrapper}> {this.renderUserList()} diff --git a/bigbluebutton-html5/imports/ui/components/audio-modal/component.jsx b/bigbluebutton-html5/imports/ui/components/audio-modal/component.jsx index 87d3d74157765e1eb6fb8b2eea783e20bc283543..9a827c971f3b22e59b7203cc127961edf3c0273a 100755 --- a/bigbluebutton-html5/imports/ui/components/audio-modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio-modal/component.jsx @@ -53,6 +53,7 @@ export default class Audio extends React.Component { render() { return ( <ModalBase + isTransparent={true} isOpen={true} onHide={null} onShow={null} diff --git a/bigbluebutton-html5/imports/ui/components/audio-notification/component.jsx b/bigbluebutton-html5/imports/ui/components/audio-notification/component.jsx new file mode 100755 index 0000000000000000000000000000000000000000..f9dbb42410402359ad4731bc875b4e3902e51cca --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/audio-notification/component.jsx @@ -0,0 +1,56 @@ +import React, { Component, PropTypes } from 'react'; +import styles from './styles.scss'; +import cx from 'classnames'; +import Button from '/imports/ui/components/button/component'; + +const COLORS = [ + 'default', 'primary', 'danger', 'success', +]; + +const propTypes = { + color: PropTypes.oneOf(COLORS), + message: PropTypes.string, +}; + +const defaultProps = { + color: 'default', +}; + +export default class AudioNotification extends Component { + constructor(props) { + super(props); + + this.handleClose = this.handleClose.bind(this); + } + + handleClose() { + this.props.handleClose(); + } + + render() { + const { color, message } = this.props; + + if(!color || !message ){ + return null; + }else{ + return ( + <div + role="alert" + className={cx(styles.audioNotifications, styles[this.props.color])}> + {message} + <Button className={styles.closeBtn} + label={'Close'} + icon={'close'} + size={'sm'} + circle={true} + hideLabel={true} + onClick={this.handleClose} + /> + </div> + ); + } + } +} + +AudioNotification.propTypes = propTypes; +AudioNotification.defaultProps = defaultProps; diff --git a/bigbluebutton-html5/imports/ui/components/audio-notification/container.jsx b/bigbluebutton-html5/imports/ui/components/audio-notification/container.jsx new file mode 100755 index 0000000000000000000000000000000000000000..eb802a453447ee1b574d982a59d1a18d07588c42 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/audio-notification/container.jsx @@ -0,0 +1,81 @@ +import { Meteor } from 'meteor/meteor'; +import { createContainer } from 'meteor/react-meteor-data'; +import React, { Component, PropTypes } from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import AudioNotification from './component'; +import styles from './styles.scss'; +import Button from '/imports/ui/components/button/component'; + +const intlMessages = defineMessages({ + audioFailed: { + id: 'app.audioNotification.audioFailedMessage', + description: 'The audio could not connect, Try again', + }, + mediaFailed: { + id: 'app.audioNotification.mediaFailedMessage', + description: 'Could not access getUserMicMedia, Try again', + }, +}); + +class AudioNotificationContainer extends Component { + constructor(props) { + super(props); + + this.color = null; + this.message = null; + + this.state = { + status: null, + } + + this.handleAudioFailure = this.handleAudioFailure.bind(this); + this.handleMediaFailure = this.handleMediaFailure.bind(this); + this.handleClose = this.handleClose.bind(this); + } + + componentDidMount() { + window.addEventListener("bbb.webrtc.failed", this.handleAudioFailure); + window.addEventListener("bbb.webrtc.mediaFailed", this.handleMediaFailure); + } + + componentWillUnmount() { + window.removeEventListener("bbb.webrtc.failed", this.handleAudioFailure); + window.removeEventListener("bbb.webrtc.mediaFailed", this.handleMediaFailure); + } + + handleClose(){ + this.color = null; + this.message = null; + this.setState({status: null}); + } + + handleAudioFailure() { + this.message = this.props.audioFailure; + this.setState({status: 'failed'}); + } + + handleMediaFailure() { + this.message = this.props.mediaFailure; + this.setState({status: 'failed'}); + } + + render() { + const handleClose = this.handleClose; + this.color = 'danger'; + + return( + <AudioNotification + color={this.color} + message={this.message} + handleClose={this.handleClose} + /> + ); + } +} + +export default injectIntl(createContainer(({ intl }) => { + let messages = {}; + messages.audioFailure = intl.formatMessage(intlMessages.audioFailed); + messages.mediaFailure = intl.formatMessage(intlMessages.mediaFailed); + return messages; +}, AudioNotificationContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/audio-notification/styles.scss b/bigbluebutton-html5/imports/ui/components/audio-notification/styles.scss new file mode 100755 index 0000000000000000000000000000000000000000..03a98eb6dba4ae46199e7be038f0f074618719fb --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/audio-notification/styles.scss @@ -0,0 +1,63 @@ +@import "../../stylesheets/variables/_all"; + +$nb-default-color: $color-gray; +$nb-default-bg: $color-white; +$nb-default-border: $color-white; + +$nb-primary-color: $color-white; +$nb-primary-bg: $color-primary; +$nb-primary-border: $color-primary; + +$nb-success-color: $color-white; +$nb-success-bg: $color-success; +$nb-success-border: $color-success; + +$nb-danger-color: $color-white; +$nb-danger-bg: $color-danger; +$nb-danger-border: $color-danger; + +.audioNotifications { + padding: $line-height-computed / 2; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-weight: 600; +} + +.closeBtn { + position: absolute; + right: 1.65em; + top: .5em; +} + +// Modifies the close button style +Button.closeBtn span:first-child { + color: $color-gray-light; + background: none; + border: none; + box-shadow: none; +} + + +@mixin nb-variant($color, $background, $border) { + color: $color; + background-color: $background; + border-color: $border; +} + +.default { + @include nb-variant($nb-default-color, $nb-default-bg, $nb-default-border); +} + +.primary { + @include nb-variant($nb-primary-color, $nb-primary-bg, $nb-primary-border); +} + +.success { + @include nb-variant($nb-success-color, $nb-success-bg, $nb-success-border); +} + +.danger { + @include nb-variant($nb-danger-color, $nb-danger-bg, $nb-danger-border); +} diff --git a/bigbluebutton-html5/imports/ui/components/button/component.jsx b/bigbluebutton-html5/imports/ui/components/button/component.jsx index 3b0f03e635a0b9a5ae1ff03fcab0ab3bbd1c8e07..c4f16c1efce238043b65398351cb48884d12c65d 100755 --- a/bigbluebutton-html5/imports/ui/components/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/button/component.jsx @@ -95,6 +95,7 @@ export default class Button extends BaseButton { } = this.props; let propClassNames = {}; + propClassNames[styles.button] = true; propClassNames[styles[size]] = true; propClassNames[styles[color]] = true; diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss index bc164c369610a585253c0290258690b1d1937f98..4d7b84befbd7a34c5ef5a8964481c52ebe7fea9b 100755 --- a/bigbluebutton-html5/imports/ui/components/button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss @@ -259,7 +259,6 @@ $btn-jumbo-padding: $jumbo-padding-y $jumbo-padding-x; } } - .circle { $btn-sm-padding-x: nth($btn-sm-padding, 2) / 2.75; $btn-md-padding-x: nth($btn-md-padding, 2) / 2.75; diff --git a/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx old mode 100644 new mode 100755 index ed6e2c57f45a1ea324a0edf60b1229968bec35df..c1b82ce8e88ed1fd02b45dd65c11e88838bd26b2 --- a/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/modal/base/component.jsx @@ -7,6 +7,7 @@ const propTypes = { isOpen: PropTypes.bool.isRequired, onShow: PropTypes.func, onHide: PropTypes.func, + isTransparent: PropTypes.bool }; const defaultProps = { @@ -41,12 +42,15 @@ export default class ModalBase extends Component { onShow, onHide, className, + isTransparent, } = this.props; + let styleOverlay = (isTransparent) ? styles.transparentOverlay : styles.overlay; + return ( <ReactModal className={cx(styles.modal, className)} - overlayClassName={styles.overlay} + overlayClassName={styleOverlay} portalClassName={styles.portal} isOpen={isOpen} onAfterOpen={this.handleAfterOpen} diff --git a/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss b/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss index 17d08d3cc6bb0f3d71a0c46b72051c30ab8543f1..d3384dee0165219a721db61de4a31a0e92719175 100755 --- a/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/modal/base/styles.scss @@ -20,11 +20,13 @@ } } -.overlay { +.transparentOverlay { + background: transparentize($color-gray-dark, .40) !important; +} + +.overlay, .transparentOverlay { z-index: 1000; - // background: transparentize($color-white, .35); background: #fff; - // background: transparentize($color-gray-dark, .40); display: flex; align-items: center; justify-content: center; diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx index 0db954613c53f0d79b88928359f58242841046cd..6f3ff8f7d5c8e3c2f20dcbfcfe30bcd0554ee2a5 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx @@ -82,7 +82,6 @@ class NavBar extends Component { </div> <div className={styles.center}> {this.renderPresentationTitle()} - <span className={styles.divider}></span> <RecordingIndicator beingRecorded={beingRecorded}/> </div> <div className={styles.right}> diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx index 2e13d6cf070ed49a54e5f537f6d30cfddab512e2..c23cd33c1f18cecc861c4ec3ee6ffa8d57a58797 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/component.jsx @@ -1,6 +1,5 @@ import React, { Component, PropTypes } from 'react'; import styles from './styles.scss'; -import cx from 'classnames'; export default class RecordingIndicator extends Component { constructor(props) { @@ -9,11 +8,11 @@ export default class RecordingIndicator extends Component { render() { const { beingRecorded } = this.props; - let classNames = {}; - classNames[styles.indicator] = beingRecorded; - return ( - <span className={cx(classNames)}></span> - ); + if (!beingRecorded) { + return null; + } + + return (<div className={styles.indicator}></div>); } }; diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss index f744600dc0ab59f9ab304822362e1a9073f8bb4a..8ff2652d9e0a1bc5a505f56ad77f300d19da0bc5 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/recording-indicator/styles.scss @@ -1,12 +1,22 @@ @import "../../../stylesheets/variables/_all"; .indicator { - display: inline-block; position: relative; + display: inline-block; width: $font-size-base; height: $font-size-base; border-radius: 50%; border: 1px solid $color-white; + margin-left: $line-height-computed; + + &:before { + content: ''; + position: absolute; + left: calc( -1px - #{($line-height-computed / 2)}); + width: 1px; + height: 100%; + background-color: $color-white; + } &:after { content: ''; diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss b/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss index c3c6be05675e8d9962e68a47658dc95a271b7ae0..80aa5ef1338b1816635efaf89fd7f6cc8321d951 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/styles.scss @@ -39,14 +39,6 @@ } } -.divider { - background: $color-white; - height: $font-size-base * 1.25; - width: 1px; - margin: 0 $line-height-computed / 2; - opacity: .75; -} - .btnWithNotificationDot { position: relative; diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index 9ac179e2e84183a998dd46d723d13cec4d780f4d..86e545c7448be77318006c867c53c714208b0ba3 100755 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -28,11 +28,12 @@ export default class ApplicationMenu extends BaseMenu { <div className={styles.row} role='presentation'> <label> <input type='checkbox' - tabIndex='7' - onChange={this.checkBoxHandler.bind(this, "audioNotifChat")} - checked={this.state.audioNotifChat} - aria-labelledby='audioNotifLabel' - aria-describedby='audioNotifDesc' /> + tabIndex='7' + onChange={this.checkBoxHandler.bind(this, "audioNotifChat")} + checked={this.state.audioNotifChat} + aria-labelledby='audioNotifLabel' + aria-describedby='audioNotifDesc' + /> Audio notifications for chat </label> <div id='audioNotifLabel' hidden>Audio notifications</div> diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js index 745ab68fce7fc02012cb4992453bb0fa676c6b3a..b892909c031bf2832255beb85927803922f4b331 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/service.js +++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js @@ -224,27 +224,22 @@ const userActions = { }, setPresenter: { label: 'Make Presenter', - handler: user => callServer('setUserPresenter', user.userid, user.name), + handler: user => callServer('setUserPresenter', user.id, user.name), icon: 'presentation', }, - promote: { - label: 'Promote', - handler: user => console.log('missing promote', user), - icon: 'promote', - }, kick: { label: 'Kick User', - handler: user => callServer('kickUser', user.userid), + handler: user => callServer('kickUser', user.id), icon: 'kick-user', }, mute: { label: 'Mute Audio', - handler: user=> callServer('muteUser', Auth.userID), + handler: user=> callServer('muteUser', user.id), icon: 'mute', }, unmute: { label: 'Unmute Audio', - handler: user=> callServer('unmuteUser', Auth.userID), + handler: user=> callServer('unmuteUser', user.id), icon: 'unmute', }, }; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx index 065669459098ef708b7198364f2be8d8228554aa..d40287927b66b279575910eb4c2e041cdd229378 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx @@ -108,35 +108,29 @@ class UserListItem extends Component { openChat, clearStatus, setPresenter, - promote, kick, mute, unmute, } = userActions; - let muteAudio, unmuteAudio; + const hasAuthority = currentUser.isModerator || user.isCurrent; + let allowedToChatPrivately = !user.isCurrent; + let allowedToMuteAudio = hasAuthority && user.isVoiceUser && user.isMuted; + let allowedToUnmuteAudio = hasAuthority && user.isVoiceUser && !user.isMuted; + let allowedToResetStatus = hasAuthority; - // Check the state of joining the audio currently for current user - if (user.isCurrent && user.isVoiceUser) { - if (user.isMuted) { - muteAudio = true; - } else { - unmuteAudio = true; - } - } + // if currentUser is a moderator, allow kicking other users + let allowedToKick = currentUser.isModerator && !user.isCurrent; - // if currentUser is a moderator or user is currently logged in, - // can clear status from the userlist. - let allowedToResetStatus = currentUser.isModerator || user.isCurrent ? true : false; + let allowedToSetPresenter = (currentUser.isModerator || currentUser.isPresenter) && !user.isPresenter; return _.compact([ - (!user.isCurrent ? this.renderUserAction(openChat, router, user) : null), - (muteAudio ? this.renderUserAction(unmute, user) : null), - (unmuteAudio ? this.renderUserAction(mute, user) : null), + (allowedToChatPrivately ? this.renderUserAction(openChat, router, user) : null), + (allowedToMuteAudio ? this.renderUserAction(unmute, user) : null), + (allowedToUnmuteAudio ? this.renderUserAction(mute, user) : null), (allowedToResetStatus ? this.renderUserAction(clearStatus, user) : null), - (currentUser.isModerator ? this.renderUserAction(setPresenter, user) : null), - (currentUser.isModerator ? this.renderUserAction(promote, user) : null), - (currentUser.isModerator ? this.renderUserAction(kick, user) : null), + (allowedToSetPresenter ? this.renderUserAction(setPresenter, user) : null), + (allowedToKick ? this.renderUserAction(kick, user) : null), ]); } @@ -162,9 +156,6 @@ class UserListItem extends Component { render() { const { - user, - currentUser, - userActions, compact, } = this.props; diff --git a/bigbluebutton-web/build.gradle b/bigbluebutton-web/build.gradle index 588866d978f8b0817f50e50bda2e38bb5969a83d..ce6c71534b855548c196e9dcc4fd72fbdbb20c7b 100755 --- a/bigbluebutton-web/build.gradle +++ b/bigbluebutton-web/build.gradle @@ -22,10 +22,11 @@ dependencies { compile 'commons-codec:commons-codec:1.10' compile 'com.google.code.gson:gson:1.7.1' compile 'commons-httpclient:commons-httpclient:3.1' + compile 'org.apache.poi:poi-ooxml:3.15' compile 'com.zaxxer:nuprocess:1.1.0' compile 'org.bigbluebutton:bbb-common-message:0.0.18-SNAPSHOT' - + // Logging // Commenting out as it results in build failure (ralam - may 11, 2014) //compile 'ch.qos.logback:logback-core:1.0.9@jar' @@ -53,7 +54,7 @@ dependencies { compile 'commons-codec:commons-codec:1.3' compile 'commons-httpclient:commons-httpclient:3.1' compile 'commons-io:commons-io:1.4' - compile 'com.artofsolving:jodconverter:2.2.1' + compile 'com.artofsolving:jodconverter:2.2.1' compile 'org.apache.geronimo.specs:geronimo-j2ee-connector_1.5_spec:1.0' compile 'org.openoffice:unoil:3.2.1' diff --git a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml index 148c479fcafec9c25d19a4caef259243718c1350..3218d0782f7c5de3c8388806e1ffec02b8eb806b 100755 --- a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml +++ b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml @@ -31,7 +31,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="imageToSwfSlidesGenerationService" ref="imageToSwfSlidesGenerationService"/> </bean> - <bean id="officeToPdfConversionService" class="org.bigbluebutton.presentation.imp.OfficeToPdfConversionService"/> + <bean id="officeDocumentValidator" class="org.bigbluebutton.presentation.imp.OfficeDocumentValidator"/> + + <bean id="officeToPdfConversionService" class="org.bigbluebutton.presentation.imp.OfficeToPdfConversionService"> + <property name="officeDocumentValidator" ref="officeDocumentValidator"/> + </bean> <bean id="pageExtractor" class="org.bigbluebutton.presentation.imp.GhostscriptPageExtractor"> <property name="ghostscriptExec" value="${ghostScriptExec}"/> diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/ConversionMessageConstants.java b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/ConversionMessageConstants.java index 53aa6b3c0c7a3988ce59f905cda783ea08a10c80..463d52d2efdd09c1aa323bd85e977882ce46c442 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/ConversionMessageConstants.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/ConversionMessageConstants.java @@ -20,20 +20,19 @@ package org.bigbluebutton.presentation; public class ConversionMessageConstants { - private ConversionMessageConstants() {} - - public static final String OFFICE_DOC_CONVERSION_SUCCESS_KEY = "OFFICE_DOC_CONVERSION_SUCCESS"; - public static final String OFFICE_DOC_CONVERSION_FAILED_KEY = "OFFICE_DOC_CONVERSION_FAILED"; - public static final String SUPPORTED_DOCUMENT_KEY = "SUPPORTED_DOCUMENT"; - public static final String UNSUPPORTED_DOCUMENT_KEY = "UNSUPPORTED_DOCUMENT"; - public static final String PAGE_COUNT_FAILED_KEY = "PAGE_COUNT_FAILED"; - public static final String PAGE_COUNT_EXCEEDED_KEY = "PAGE_COUNT_EXCEEDED"; - public static final String GENERATED_SLIDE_KEY = "GENERATED_SLIDE"; - public static final String GENERATING_THUMBNAIL_KEY = "GENERATING_THUMBNAIL"; - public static final String GENERATED_THUMBNAIL_KEY = "GENERATED_THUMBNAIL"; - public static final String GENERATING_TEXTFILES_KEY = "GENERATING_TEXTFILES"; - public static final String GENERATED_TEXTFILES_KEY = "GENERATED_TEXTFILES"; - public static final String GENERATING_SVGIMAGES_KEY = "GENERATING_SVGIMAGES"; - public static final String GENERATED_SVGIMAGES_KEY = "GENERATED_SVGIMAGES"; - public static final String CONVERSION_COMPLETED_KEY = "CONVERSION_COMPLETED"; + public static final String OFFICE_DOC_CONVERSION_SUCCESS_KEY = "OFFICE_DOC_CONVERSION_SUCCESS"; + public static final String OFFICE_DOC_CONVERSION_FAILED_KEY = "OFFICE_DOC_CONVERSION_FAILED"; + public static final String OFFICE_DOC_CONVERSION_INVALID_KEY = "OFFICE_DOC_CONVERSION_INVALID"; + public static final String SUPPORTED_DOCUMENT_KEY = "SUPPORTED_DOCUMENT"; + public static final String UNSUPPORTED_DOCUMENT_KEY = "UNSUPPORTED_DOCUMENT"; + public static final String PAGE_COUNT_FAILED_KEY = "PAGE_COUNT_FAILED"; + public static final String PAGE_COUNT_EXCEEDED_KEY = "PAGE_COUNT_EXCEEDED"; + public static final String GENERATED_SLIDE_KEY = "GENERATED_SLIDE"; + public static final String GENERATING_THUMBNAIL_KEY = "GENERATING_THUMBNAIL"; + public static final String GENERATED_THUMBNAIL_KEY = "GENERATED_THUMBNAIL"; + public static final String GENERATING_TEXTFILES_KEY = "GENERATING_TEXTFILES"; + public static final String GENERATED_TEXTFILES_KEY = "GENERATED_TEXTFILES"; + public static final String GENERATING_SVGIMAGES_KEY = "GENERATING_SVGIMAGES"; + public static final String GENERATED_SVGIMAGES_KEY = "GENERATED_SVGIMAGES"; + public static final String CONVERSION_COMPLETED_KEY = "CONVERSION_COMPLETED"; } diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java index 4a1d74ab79c958daaa37ef7627cb70d9c0e4d37c..ce0a002fbd8525a1c45d986583e149744d37afb1 100644 --- a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java @@ -27,57 +27,64 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DocumentConversionServiceImp implements DocumentConversionService { - private static Logger log = LoggerFactory.getLogger(DocumentConversionServiceImp.class); - - private MessagingService messagingService; - private OfficeToPdfConversionService officeToPdfConversionService; - private PdfToSwfSlidesGenerationService pdfToSwfSlidesGenerationService; - private ImageToSwfSlidesGenerationService imageToSwfSlidesGenerationService; - - public void processDocument(UploadedPresentation pres) { - SupportedDocumentFilter sdf = new SupportedDocumentFilter(messagingService); - log.info("Start presentation conversion. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " name=" + pres.getName()); + private static Logger log = LoggerFactory + .getLogger(DocumentConversionServiceImp.class); - if (sdf.isSupported(pres)) { - String fileType = pres.getFileType(); - - if (SupportedFileTypes.isOfficeFile(fileType)) { - officeToPdfConversionService.convertOfficeToPdf(pres); - OfficeToPdfConversionSuccessFilter ocsf = new OfficeToPdfConversionSuccessFilter(messagingService); - if (ocsf.didConversionSucceed(pres)) { - // Successfully converted to pdf. Call the process again, this time it should be handled by - // the PDF conversion service. - processDocument(pres); - } - } else if (SupportedFileTypes.isPdfFile(fileType)) { - pdfToSwfSlidesGenerationService.generateSlides(pres); - } else if (SupportedFileTypes.isImageFile(fileType)) { - imageToSwfSlidesGenerationService.generateSlides(pres); - } else { - - } - - } else { - // TODO: error log - } - - log.info("End presentation conversion. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " name=" + pres.getName()); + private MessagingService messagingService; + private OfficeToPdfConversionService officeToPdfConversionService; + private PdfToSwfSlidesGenerationService pdfToSwfSlidesGenerationService; + private ImageToSwfSlidesGenerationService imageToSwfSlidesGenerationService; - } - - public void setMessagingService(MessagingService m) { - messagingService = m; - } - - public void setOfficeToPdfConversionService(OfficeToPdfConversionService s) { - officeToPdfConversionService = s; - } - - public void setPdfToSwfSlidesGenerationService(PdfToSwfSlidesGenerationService s) { - pdfToSwfSlidesGenerationService = s; - } - - public void setImageToSwfSlidesGenerationService(ImageToSwfSlidesGenerationService s) { - imageToSwfSlidesGenerationService = s; - } + public void processDocument(UploadedPresentation pres) { + SupportedDocumentFilter sdf = new SupportedDocumentFilter(messagingService); + log.info("Start presentation conversion. meetingId=" + pres.getMeetingId() + + " presId=" + pres.getId() + " name=" + pres.getName()); + + if (sdf.isSupported(pres)) { + String fileType = pres.getFileType(); + + if (SupportedFileTypes.isOfficeFile(fileType)) { + pres = officeToPdfConversionService.convertOfficeToPdf(pres); + OfficeToPdfConversionSuccessFilter ocsf = new OfficeToPdfConversionSuccessFilter( + messagingService); + if (ocsf.didConversionSucceed(pres)) { + // Successfully converted to pdf. Call the process again, this time it + // should be handled by + // the PDF conversion service. + processDocument(pres); + } + } else if (SupportedFileTypes.isPdfFile(fileType)) { + pdfToSwfSlidesGenerationService.generateSlides(pres); + } else if (SupportedFileTypes.isImageFile(fileType)) { + imageToSwfSlidesGenerationService.generateSlides(pres); + } else { + + } + + } else { + // TODO: error log + } + + log.info("End presentation conversion. meetingId=" + pres.getMeetingId() + + " presId=" + pres.getId() + " name=" + pres.getName()); + + } + + public void setMessagingService(MessagingService m) { + messagingService = m; + } + + public void setOfficeToPdfConversionService(OfficeToPdfConversionService s) { + officeToPdfConversionService = s; + } + + public void setPdfToSwfSlidesGenerationService( + PdfToSwfSlidesGenerationService s) { + pdfToSwfSlidesGenerationService = s; + } + + public void setImageToSwfSlidesGenerationService( + ImageToSwfSlidesGenerationService s) { + imageToSwfSlidesGenerationService = s; + } } diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java index 6564edf6fa4dffbd9676c27254a6aef25ccbe952..51ab8cbb5e0edc502b5c9191ae26cf06b8fd1ea6 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/OfficeToPdfConversionSuccessFilter.java @@ -30,49 +30,58 @@ import org.slf4j.LoggerFactory; import com.google.gson.Gson; public class OfficeToPdfConversionSuccessFilter { - private static Logger log = LoggerFactory.getLogger(OfficeToPdfConversionSuccessFilter.class); + private static Logger log = LoggerFactory + .getLogger(OfficeToPdfConversionSuccessFilter.class); - private final MessagingService messagingService; - - public OfficeToPdfConversionSuccessFilter(MessagingService m) { - messagingService = m; - } - - public boolean didConversionSucceed(UploadedPresentation pres) { - notifyProgressListener(pres); - return pres.isLastStepSuccessful(); - } + private final MessagingService messagingService; - private void notifyProgressListener(UploadedPresentation pres) { - Map<String, Object> msg = new HashMap<String, Object>(); - msg.put("conference", pres.getMeetingId()); - msg.put("room", pres.getMeetingId()); - msg.put("returnCode", "CONVERT"); - msg.put("presentationId", pres.getId()); - msg.put("presentationName", pres.getId()); - msg.put("filename", pres.getName()); - - if (pres.isLastStepSuccessful()) { - log.info("Notifying of OFFICE_DOC_CONVERSION_SUCCESS for " + pres.getUploadedFile().getAbsolutePath()); - msg.put("message", "Office document successfully converted."); - msg.put("messageKey", "OFFICE_DOC_CONVERSION_SUCCESS"); - } else { - log.info("Notifying of OFFICE_DOC_CONVERSION_FAILED for " + pres.getUploadedFile().getAbsolutePath()); - msg.put("message", "Failed to convert Office document."); - msg.put("messageKey", "OFFICE_DOC_CONVERSION_FAILED"); - } - - sendNotification(msg); - } - - private void sendNotification(Map<String, Object> msg) { - if (messagingService != null){ - Gson gson = new Gson(); - String updateMsg = gson.toJson(msg); - log.debug("sending: " + updateMsg); - messagingService.send(MessagingConstants.TO_PRESENTATION_CHANNEL, updateMsg); - } else { - log.warn("MessagingService has not been set!."); - } - } + private static Map<String, String> conversionMessagesMap; + + public OfficeToPdfConversionSuccessFilter(MessagingService m) { + messagingService = m; + conversionMessagesMap = new HashMap<String, String>(); + conversionMessagesMap.put( + ConversionMessageConstants.OFFICE_DOC_CONVERSION_SUCCESS_KEY, + "Office document successfully converted."); + conversionMessagesMap.put( + ConversionMessageConstants.OFFICE_DOC_CONVERSION_FAILED_KEY, + "Failed to convert Office document."); + conversionMessagesMap.put( + ConversionMessageConstants.OFFICE_DOC_CONVERSION_INVALID_KEY, + "Invalid Office document detected, it will not be converted."); + } + + public boolean didConversionSucceed(UploadedPresentation pres) { + notifyProgressListener(pres); + return pres + .getConversionStatus() == ConversionMessageConstants.OFFICE_DOC_CONVERSION_SUCCESS_KEY; + } + + private void notifyProgressListener(UploadedPresentation pres) { + Map<String, Object> msg = new HashMap<String, Object>(); + msg.put("conference", pres.getMeetingId()); + msg.put("room", pres.getMeetingId()); + msg.put("returnCode", "CONVERT"); + msg.put("presentationId", pres.getId()); + msg.put("presentationName", pres.getId()); + msg.put("filename", pres.getName()); + msg.put("message", conversionMessagesMap.get(pres.getConversionStatus())); + msg.put("messageKey", pres.getConversionStatus()); + + log.info("Notifying of " + pres.getConversionStatus() + " for " + + pres.getUploadedFile().getAbsolutePath()); + sendNotification(msg); + } + + private void sendNotification(Map<String, Object> msg) { + if (messagingService != null) { + Gson gson = new Gson(); + String updateMsg = gson.toJson(msg); + log.debug("sending: " + updateMsg); + messagingService.send(MessagingConstants.TO_PRESENTATION_CHANNEL, + updateMsg); + } else { + log.warn("MessagingService has not been set!."); + } + } } diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/UploadedPresentation.java b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/UploadedPresentation.java index ffa5da576c38d1d509f1c4e85c0c260b175bd160..9f01e55b050cd0c558a1b2d44550377bb6376d78 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/UploadedPresentation.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/UploadedPresentation.java @@ -22,71 +22,68 @@ package org.bigbluebutton.presentation; import java.io.File; public final class UploadedPresentation { - private final String meetingId; - private final String id; - private final String name; - private File uploadedFile; - private String fileType = "unknown"; - private int numberOfPages = 0; - private boolean lastStepSuccessful = false; - private final String baseUrl; - - public UploadedPresentation(String meetingId, String id, - String name, - String baseUrl) { - this.meetingId = meetingId; - this.id = id; - this.name = name; - this.baseUrl = baseUrl; - } - - public File getUploadedFile() { - return uploadedFile; - } - - public void setUploadedFile(File uploadedFile) { - this.uploadedFile = uploadedFile; - } - - public String getMeetingId() { - return meetingId; - } - - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public String getBaseUrl() { - return baseUrl; - } - - public String getFileType() { - return fileType; - } - - public void setFileType(String fileType) { - this.fileType = fileType; - } - - public int getNumberOfPages() { - return numberOfPages; - } - - public void setNumberOfPages(int numberOfPages) { - this.numberOfPages = numberOfPages; - } - - public boolean isLastStepSuccessful() { - return lastStepSuccessful; - } - - public void setLastStepSuccessful(boolean lastStepSuccessful) { - this.lastStepSuccessful = lastStepSuccessful; - } - - + private final String meetingId; + private final String id; + private final String name; + private File uploadedFile; + private String fileType = "unknown"; + private int numberOfPages = 0; + private String conversionStatus; + private final String baseUrl; + + public UploadedPresentation(String meetingId, String id, String name, + String baseUrl) { + this.meetingId = meetingId; + this.id = id; + this.name = name; + this.baseUrl = baseUrl; + } + + public File getUploadedFile() { + return uploadedFile; + } + + public void setUploadedFile(File uploadedFile) { + this.uploadedFile = uploadedFile; + } + + public String getMeetingId() { + return meetingId; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getBaseUrl() { + return baseUrl; + } + + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } + + public int getNumberOfPages() { + return numberOfPages; + } + + public void setNumberOfPages(int numberOfPages) { + this.numberOfPages = numberOfPages; + } + + public String getConversionStatus() { + return conversionStatus; + } + + public void setConversionStatus(String conversionStatus) { + this.conversionStatus = conversionStatus; + } } diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/imp/OfficeDocumentValidator.java b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/imp/OfficeDocumentValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..3bc8284892b61314fc7617c76fc384750da9096e --- /dev/null +++ b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/imp/OfficeDocumentValidator.java @@ -0,0 +1,93 @@ +package org.bigbluebutton.presentation.imp; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.io.FilenameUtils; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.xslf.usermodel.XMLSlideShow; +import org.apache.poi.xslf.usermodel.XSLFPictureData; +import org.bigbluebutton.presentation.FileTypeConstants; +import org.bigbluebutton.presentation.UploadedPresentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OfficeDocumentValidator { + private static Logger log = LoggerFactory + .getLogger(OfficeDocumentValidator.class); + + public boolean isValid(UploadedPresentation pres) { + boolean valid = true; + if (FilenameUtils.isExtension(pres.getUploadedFile().getName(), + FileTypeConstants.PPTX)) { + XMLSlideShow xmlSlideShow; + try { + xmlSlideShow = new XMLSlideShow( + new FileInputStream(pres.getUploadedFile())); + valid &= !embedsEmf(xmlSlideShow); + valid &= !containsTinyTileBackground(xmlSlideShow); + // Close the resource once we finished reading it + xmlSlideShow.close(); + } catch (IOException e) { + log.error("Cannot open PPTX file " + pres.getName()); + valid = false; + } + } + return valid; + } + + /** + * Checks if the slide-show file embeds any EMF document + * + * @param xmlSlideShow + * @return + */ + private boolean embedsEmf(XMLSlideShow xmlSlideShow) { + EmfPredicate emfPredicate = new EmfPredicate(); + ArrayList<XSLFPictureData> embeddedEmfFiles = (ArrayList<XSLFPictureData>) CollectionUtils + .select(xmlSlideShow.getPictureData(), emfPredicate); + if (embeddedEmfFiles.size() > 0) { + log.warn( + "Found " + embeddedEmfFiles.size() + " EMF files in presentation."); + return true; + } + return false; + } + + /** + * Checks if the slide-show contains a small background tile image + * + * @param xmlSlideShow + * @return + */ + private boolean containsTinyTileBackground(XMLSlideShow xmlSlideShow) { + TinyTileBackgroundPredicate tinyTileCondition = new TinyTileBackgroundPredicate(); + ArrayList<XSLFPictureData> tileImage = (ArrayList<XSLFPictureData>) CollectionUtils + .select(xmlSlideShow.getPictureData(), tinyTileCondition); + if (tileImage.size() > 0) { + log.warn("Found small background tile image."); + return true; + } + return false; + } + + private final class EmfPredicate implements Predicate<XSLFPictureData> { + @Override + public boolean evaluate(XSLFPictureData img) { + return img.getContentType().equals("image/x-emf"); + } + } + + private final class TinyTileBackgroundPredicate + implements Predicate<XSLFPictureData> { + @Override + public boolean evaluate(XSLFPictureData img) { + return img.getContentType() != null + && img.getContentType().equals("image/jpeg") + && LittleEndian.getLong(img.getChecksum()) == 4114937224L; + } + } +} diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java index 9dd17f4f17f7478dfb9173a569f0a21c6c86b20f..a74bfebf651b8fd9894d87aed44c434e2d246abe 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/presentation/imp/OfficeToPdfConversionService.java @@ -20,6 +20,8 @@ package org.bigbluebutton.presentation.imp; import java.io.File; + +import org.bigbluebutton.presentation.ConversionMessageConstants; import org.bigbluebutton.presentation.PageConverter; import org.bigbluebutton.presentation.SupportedFileTypes; import org.bigbluebutton.presentation.UploadedPresentation; @@ -27,44 +29,64 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class OfficeToPdfConversionService { - private static Logger log = LoggerFactory.getLogger(OfficeToPdfConversionService.class); + private static Logger log = LoggerFactory + .getLogger(OfficeToPdfConversionService.class); + + private OfficeDocumentValidator officeDocumentValidator; /* - * Convert the Office document to PDF. If successful, update + * Convert the Office document to PDF. If successful, update * UploadPresentation.uploadedFile with the new PDF out and * UploadPresentation.lastStepSuccessful to TRUE. */ public UploadedPresentation convertOfficeToPdf(UploadedPresentation pres) { initialize(pres); if (SupportedFileTypes.isOfficeFile(pres.getFileType())) { - File pdfOutput = setupOutputPdfFile(pres); + boolean valid = officeDocumentValidator.isValid(pres); + if (!valid) { + log.warn("Problems detected prior to converting the file to PDF."); + pres.setConversionStatus( + ConversionMessageConstants.OFFICE_DOC_CONVERSION_INVALID_KEY); + return pres; + } + File pdfOutput = setupOutputPdfFile(pres); if (convertOfficeDocToPdf(pres, pdfOutput)) { log.info("Successfully converted office file to pdf."); makePdfTheUploadedFileAndSetStepAsSuccess(pres, pdfOutput); } else { - log.warn("Failed to convert " + pres.getUploadedFile().getAbsolutePath() + " to Pdf."); + log.warn("Failed to convert " + pres.getUploadedFile().getAbsolutePath() + + " to Pdf."); } } return pres; } public void initialize(UploadedPresentation pres) { - pres.setLastStepSuccessful(false); + pres.setConversionStatus( + ConversionMessageConstants.OFFICE_DOC_CONVERSION_FAILED_KEY); } - private File setupOutputPdfFile(UploadedPresentation pres) { + private File setupOutputPdfFile(UploadedPresentation pres) { File presentationFile = pres.getUploadedFile(); - String filenameWithoutExt = presentationFile.getAbsolutePath().substring(0, presentationFile.getAbsolutePath().lastIndexOf(".")); + String filenameWithoutExt = presentationFile.getAbsolutePath().substring(0, + presentationFile.getAbsolutePath().lastIndexOf(".")); return new File(filenameWithoutExt + ".pdf"); } - private boolean convertOfficeDocToPdf(UploadedPresentation pres, File pdfOutput) { + private boolean convertOfficeDocToPdf(UploadedPresentation pres, + File pdfOutput) { PageConverter converter = new Office2PdfPageConverter(); return converter.convert(pres.getUploadedFile(), pdfOutput, 0, pres); } - private void makePdfTheUploadedFileAndSetStepAsSuccess(UploadedPresentation pres, File pdf) { + private void makePdfTheUploadedFileAndSetStepAsSuccess( + UploadedPresentation pres, File pdf) { pres.setUploadedFile(pdf); - pres.setLastStepSuccessful(true); + pres.setConversionStatus( + ConversionMessageConstants.OFFICE_DOC_CONVERSION_SUCCESS_KEY); + } + + public void setOfficeDocumentValidator(OfficeDocumentValidator v) { + officeDocumentValidator = v; } } diff --git a/record-and-playback/core/lib/recordandplayback/deskshare_archiver.rb b/record-and-playback/core/lib/recordandplayback/deskshare_archiver.rb index 2b85c699504a551cb00ddf48c4fc5aced8b9996d..ee880067ac4486972a72ef390c63fe1a0860901e 100755 --- a/record-and-playback/core/lib/recordandplayback/deskshare_archiver.rb +++ b/record-and-playback/core/lib/recordandplayback/deskshare_archiver.rb @@ -29,7 +29,7 @@ module BigBlueButton raise MissingDirectoryException, "Directory not found #{to_dir}" if not BigBlueButton.dir_exists?(to_dir) raise FileNotFoundException, "No recording for #{meeting_id} in #{from_dir}" if Dir.glob("#{from_dir}").empty? - Dir.glob("#{from_dir}/#{meeting_id}-*.flv").each { |file| + Dir.glob("#{from_dir}/#{meeting_id}-*").each { |file| puts "deskshare #{file} to #{to_dir}" FileUtils.cp(file, to_dir) } diff --git a/record-and-playback/core/scripts/sanity/sanity.rb b/record-and-playback/core/scripts/sanity/sanity.rb index 0dc2889b2fe4baf7dbbc5ee4ba5133ed0c1ae853..cf11b5e8aa7f860cf8347c065c2d86e06b7274e9 100755 --- a/record-and-playback/core/scripts/sanity/sanity.rb +++ b/record-and-playback/core/scripts/sanity/sanity.rb @@ -105,6 +105,22 @@ def check_webcam_files(raw_dir, meeting_id) end def check_deskshare_files(raw_dir, meeting_id) + meeting_dir = "#{raw_dir}/#{meeting_id}" + + BigBlueButton.logger.info("Repairing red5 serialized streams") + cp="/usr/share/red5/red5-server.jar:/usr/share/red5/lib/*" + if File.directory?("#{meeting_dir}/deskshare") + FileUtils.cd("#{meeting_dir}/deskshare") do + Dir.glob("*.flv.ser").each do |ser| + BigBlueButton.logger.info("Repairing #{ser}") + ret = BigBlueButton.exec_ret('java', '-cp', cp, 'org.red5.io.flv.impl.FLVWriter', ser, '0', '7') + if ret != 0 + BigBlueButton.logger.warn("Failed to repair #{ser}") + end + end + end + end + desktops = BigBlueButton::Events.get_start_deskshare_events("#{raw_dir}/#{meeting_id}/events.xml") desktops.each do |desktop| raw_desktop_file = "#{raw_dir}/#{meeting_id}/deskshare/#{desktop[:stream]}"