diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/CreateGroupChatReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/CreateGroupChatReqMsgHdlr.scala
index 368383fb5c121b91db668cbb91b271b801b70337..fddebc0b0be64a68586d76bf1095cac897ce5f7d 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/CreateGroupChatReqMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/CreateGroupChatReqMsgHdlr.scala
@@ -26,7 +26,14 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
       if (user.role != Roles.MODERATOR_ROLE) {
         if (msg.body.access == GroupChatAccess.PRIVATE) {
           val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
-          chatLocked = user.locked && permissions.disablePrivChat
+          val modMembers = msg.body.users.filter(userId => Users2x.findWithIntId(liveMeeting.users2x, userId) match {
+            case Some(user) => user.role == Roles.MODERATOR_ROLE
+            case None       => false
+          })
+          // don't lock creation of private chats that involve a moderator
+          if (modMembers.length == 0) {
+            chatLocked = user.locked && permissions.disablePrivChat
+          }
         } else {
           chatLocked = true
         }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/SendGroupChatMessageMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/SendGroupChatMessageMsgHdlr.scala
index 09221ea54a3e82bab496dc030d24371a982258d2..b2b96a2c150f71e9a72dce42da90e717722bb791 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/SendGroupChatMessageMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/groupchats/SendGroupChatMessageMsgHdlr.scala
@@ -28,7 +28,14 @@ trait SendGroupChatMessageMsgHdlr {
       if (user.role != Roles.MODERATOR_ROLE && user.locked) {
         val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
         if (groupChat.access == GroupChatAccess.PRIVATE) {
-          chatLocked = permissions.disablePrivChat
+          val modMembers = groupChat.users.filter(cu => Users2x.findWithIntId(liveMeeting.users2x, cu.id) match {
+            case Some(user) => user.role == Roles.MODERATOR_ROLE
+            case None       => false
+          })
+          // don't lock private chats that involve a moderator
+          if (modMembers.length == 0) {
+            chatLocked = permissions.disablePrivChat
+          }
         } else {
           chatLocked = permissions.disablePubChat
         }
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatOptionsTab.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatOptionsTab.mxml
old mode 100644
new mode 100755
index bc78dfbaa514dd08ae6dbb3eabd871a8642df5d9..3e6e171838e66205cd595e68833490ddea8d54d2
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatOptionsTab.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatOptionsTab.mxml
@@ -217,10 +217,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 			}
 			
 			private function refreshListStatus():void {
-				
-				if (UsersUtil.amIModerator() || UsersUtil.amIPresenter()) return; // Settings only affect viewers.
-				
-				usersList.enabled = ! LiveMeeting.inst().me.disableMyPrivateChat;
+				if (LiveMeeting.inst().me.disableMyPrivateChat) {
+					handler.enableModeratorsFilter();
+				} else {
+					handler.disableModeratorsFilter();
+				}
+				users = removeMe(handler.users);
 			}
 			
 			public function sendSaveEvent():void{
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatTab.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatTab.mxml
index 5c69d8d21684a287769ce8b8c68ddef0496ab838..60aeefe5de2dfe7c6d2a56ade5521c77e4390d09 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatTab.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatTab.mxml
@@ -69,6 +69,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 		import org.bigbluebutton.core.events.LockControlEvent;
 		import org.bigbluebutton.core.events.UserStatusChangedEvent;
 		import org.bigbluebutton.core.model.LiveMeeting;
+		import org.bigbluebutton.core.model.users.User2x;
 		import org.bigbluebutton.main.events.BBBEvent;
 		import org.bigbluebutton.main.events.ShortcutEvent;
 		import org.bigbluebutton.main.events.UserJoinedEvent;
@@ -87,13 +88,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 		import org.bigbluebutton.modules.chat.model.ChatOptions;
 		import org.bigbluebutton.modules.chat.model.GroupChat;
 		import org.bigbluebutton.modules.chat.vo.ChatMessageVO;
+		import org.bigbluebutton.modules.chat.vo.GroupChatUser;
 		import org.bigbluebutton.modules.polling.events.StartCustomPollEvent;
 		import org.bigbluebutton.util.i18n.ResourceUtil;
 		
       private static const LOGGER:ILogger = getClassLogger(ChatTab);
       
       public var publicChat:Boolean = false;
-      public var chatWithUserID:String;
       public var chatWithUsername:String
       public var chatId: String = null;
       public var parentWindowId:String = null;
@@ -609,7 +610,18 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 		if (publicChat) {
           txtMsgArea.enabled = sendBtn.enabled = !LiveMeeting.inst().me.disableMyPublicChat;
         } else {
-          txtMsgArea.enabled = sendBtn.enabled = !LiveMeeting.inst().me.disableMyPrivateChat || LiveMeeting.inst().users.getUser(chatWithUserID).role == Role.MODERATOR;
+          var chattingWithMod:Boolean = false;
+          var gc:GroupChat = LiveMeeting.inst().chats.getGroupChat(chatId);
+          for (var i:int = 0; i < gc.users.length; i++) {
+            var chatUser:GroupChatUser = gc.users[i] as GroupChatUser;
+            if (chatUser.id != UsersUtil.getMyUserID()) {
+              var user:User2x = UsersUtil.getUser(chatUser.id);
+              if (user && user.role == Role.MODERATOR) {
+                chattingWithMod = true;
+              }
+            }
+          }
+          txtMsgArea.enabled = sendBtn.enabled = !LiveMeeting.inst().me.disableMyPrivateChat || chattingWithMod;
         }
       }
       
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml
index 0564867706b1b34a91bbc87cb1393369c8ca4b07..9febf4c8a4d24abcd3eda8cf53392f096521ae5a 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml
@@ -340,7 +340,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
         var chatBox:ChatTab = new ChatTab();
         
         chatBox.name = groupChatId;
-        chatBox.chatWithUserID = groupChatId;
         chatBox.parentWindowId = windowId;
         chatBox.tabIndexer.startIndex = tabIndexer.startIndex + 10;
 				chatBox.chatMessages = new ChatConversation(groupChatId);
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/QuickPollButton.as b/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/QuickPollButton.as
index a608823bd216cc2e1a6c3d527f0eb4b4d3ec2208..34f0d3611fad299d10442ce56fb5a6ae372d852c 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/QuickPollButton.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/polling/views/QuickPollButton.as
@@ -9,19 +9,27 @@ package org.bigbluebutton.modules.polling.views {
 	import org.bigbluebutton.modules.present.events.PageLoadedEvent;
 	import org.bigbluebutton.modules.present.model.Page;
 	import org.bigbluebutton.modules.present.model.PresentationModel;
+	import org.bigbluebutton.modules.present.model.PresentationPodManager;
 	import org.bigbluebutton.util.i18n.ResourceUtil;
 	
 	public class QuickPollButton extends Button {
-		private static const LOGGER:ILogger = getClassLogger(QuickPollButton);      
+		private static const LOGGER:ILogger = getClassLogger(QuickPollButton);
+		
+		private var currentPageId:String;
+		private var currentPodId:String;
 
 		override public function set visible(vsb:Boolean):void {
 			if (vsb) {
 				// This button should only be visible when there is a polling at the current slide's text
-//				var page:Page = PresentationModel.getInstance().getCurrentPage();
-//				super.visible = page != null ? parseSlideText(page.txtData) : false;
-			} else {
-				super.visible = false;
+				var presentationModel:PresentationModel = PresentationPodManager.getInstance().getPod(PresentationPodManager.DEFAULT_POD_ID);
+				if (presentationModel != null) {
+					var page:Page = presentationModel.getCurrentPage();
+					super.visible = page != null ? parseSlideText(page.txtData) : false;
+					return;
+				}
 			}
+			
+			super.visible = false;
 		}
 
 		public function QuickPollButton() {
@@ -34,7 +42,10 @@ package org.bigbluebutton.modules.polling.views {
 		}
 		
 		private function handlePageLoadedEvent(e:PageLoadedEvent):void {
-			visible = UsersUtil.amIPresenter();
+			// Only revalidate when it's the default pod that loaded a page
+			if (e.podId == PresentationPodManager.DEFAULT_POD_ID) {
+				visible = UsersUtil.amIPresenter();
+			}
 		}
 		
 		private function parseSlideText(text:String):Boolean {
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml
index c7bae41b02eb024b00da1e1517421883add3f5d0..e39d45ca49c74be2eb16e205ccbe9a529fdddd2e 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml
@@ -609,9 +609,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
 				
 				if (this.podId == PresentationPodManager.DEFAULT_POD_ID) { // only allow polling from the default pod
 					pollStartBtn.visible = showButtons;
+					quickPollBtn.visible = showButtons;
+				} else {
+					pollStartBtn.visible = false;
+					quickPollBtn.visible = false;
 				}
 				
-				quickPollBtn.visible = showButtons;
 				navigationControls.visible = showButtons;
 				zoomControls.visible = showButtons;
 				
diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf
index b9ec66e231843ebd291fe19ba79b3fe047a9e630..b77db5a6423fa528586be00e0f8e677cfd33ae81 100755
--- a/bigbluebutton-config/bin/bbb-conf
+++ b/bigbluebutton-config/bin/bbb-conf
@@ -1315,11 +1315,11 @@ check_state() {
  	check_no_value server_name /etc/nginx/sites-available/bigbluebutton $BBB_WEB
 
         COUNT=0
-        while [ $COUNT -lt 30 ]; do
+        while [ $COUNT -lt 45 ]; do
           let COUNT=COUNT+1
 	  timeout 1s wget $PROTOCOL://$BBB_WEB/bigbluebutton/api -O - --quiet | grep -q SUCCESS
 	  if [ $? -eq 0 ]; then
-            let COUNT=40
+            let COUNT=45
           else
             echo -n "."
             sleep 1
diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
index b9b1c6cc9b388177164d15de3b15f21619d7a084..75832fc0e6f03abb79667748069ef3aae56c1163 100644
--- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
@@ -46,7 +46,7 @@ export default function addMeeting(meeting) {
     recordProp: Match.ObjectIncluding({
       allowStartStopRecording: Boolean,
       autoStartRecording: Boolean,
-      record: Boolean
+      record: Boolean,
     }),
     password: {
       viewerPass: String,
@@ -66,6 +66,8 @@ export default function addMeeting(meeting) {
     metadataProp: Object,
   });
 
+  const newMeeting = meeting;
+
   const selector = {
     meetingId,
   };
@@ -81,10 +83,26 @@ export default function addMeeting(meeting) {
     setBy: 'temp',
   };
 
+  newMeeting.welcomeProp.welcomeMsg = newMeeting.welcomeProp.welcomeMsg.replace(
+    'href="event:',
+    'href="',
+  );
+
+  const insertBlankTarget = (s, i) => `${s.substr(0, i)} target="_blank"${s.substr(i)}`;
+  const linkWithoutTarget = new RegExp('<a href="(.*?)">', 'g');
+  linkWithoutTarget.test(newMeeting.welcomeProp.welcomeMsg);
+
+  if (linkWithoutTarget.lastIndex > 0) {
+    newMeeting.welcomeProp.welcomeMsg = insertBlankTarget(
+      newMeeting.welcomeProp.welcomeMsg,
+      linkWithoutTarget.lastIndex - 1,
+    );
+  }
+
   const modifier = {
     $set: Object.assign(
       { meetingId },
-      flat(meeting, { safe: true }),
+      flat(newMeeting, { safe: true }),
       { lockSettingsProp },
     ),
   };
diff --git a/bigbluebutton-html5/imports/api/polls/server/modifiers/addPoll.js b/bigbluebutton-html5/imports/api/polls/server/modifiers/addPoll.js
index 6ecd2253a0f30293a3aa9d83c16d376ae471d3b5..5b3e0d6150759abe827e0beac22a90c606a9f7bb 100644
--- a/bigbluebutton-html5/imports/api/polls/server/modifiers/addPoll.js
+++ b/bigbluebutton-html5/imports/api/polls/server/modifiers/addPoll.js
@@ -20,6 +20,7 @@ export default function addPoll(meetingId, requesterId, poll) {
   const userSelector = {
     meetingId,
     userId: { $ne: requesterId },
+    clientType: { $ne: 'dial-in-user' },
   };
 
   const userIds = Users.find(userSelector)
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
index 21f57eb0eecee9a313aaee7dbb7f198d0c576082..1a5d64be2bb43a38f7e8260c2a9f4acf9073eeca 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
@@ -1,7 +1,7 @@
 import _ from 'lodash';
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, intlShape } from 'react-intl';
+import { defineMessages, intlShape } from 'react-intl';
 import Button from '/imports/ui/components/button/component';
 import Dropdown from '/imports/ui/components/dropdown/component';
 import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
@@ -315,4 +315,4 @@ class ActionsDropdown extends Component {
 
 ActionsDropdown.propTypes = propTypes;
 
-export default withShortcutHelper(withModalMounter(injectIntl(ActionsDropdown)), 'openActions');
+export default withShortcutHelper(withModalMounter(ActionsDropdown), 'openActions');
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
index 600d9ff762bf860cf9647d0464200d21bb3b936c..56f8439d820fae6ba85ca64c7c8c7682e7d70d24 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
@@ -3,6 +3,7 @@ import cx from 'classnames';
 import { styles } from './styles.scss';
 import DesktopShare from './desktop-share/component';
 import ActionsDropdown from './actions-dropdown/component';
+import QuickPollDropdown from './quick-poll-dropdown/component';
 import AudioControlsContainer from '../audio/audio-controls/container';
 import JoinVideoOptionsContainer from '../video-provider/video-button/container';
 
@@ -33,6 +34,9 @@ class ActionsBar extends React.PureComponent {
       sendInvitation,
       getBreakouts,
       handleTakePresenter,
+      intl,
+      currentSlidHasContent,
+      parseCurrentSlideContent,
       isSharingVideo,
     } = this.props;
 
@@ -68,9 +72,18 @@ class ActionsBar extends React.PureComponent {
             sendInvitation,
             getBreakouts,
             handleTakePresenter,
+            intl,
             isSharingVideo,
           }}
           />
+          <QuickPollDropdown
+            {...{
+              currentSlidHasContent,
+              intl,
+              isUserPresenter,
+              parseCurrentSlideContent,
+            }}
+          />
         </div>
         <div
           className={
@@ -96,7 +109,7 @@ class ActionsBar extends React.PureComponent {
           />
         </div>
         <div className={styles.right}>
-          { isLayoutSwapped
+          {isLayoutSwapped
             ? (
               <PresentationOptionsContainer
                 toggleSwapLayout={toggleSwapLayout}
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
index 87751b6139a157b00e888041686ac55acd0a0d55..65a7a21512c899b4903f82c9e5e87f61a961bdcb 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx
@@ -1,8 +1,10 @@
 import React from 'react';
 import { withTracker } from 'meteor/react-meteor-data';
+import { injectIntl } from 'react-intl';
 import getFromUserSettings from '/imports/ui/services/users-settings';
 import Meetings from '/imports/api/meetings';
 import Auth from '/imports/ui/services/auth';
+import PresentationService from '/imports/ui/components/presentation/service';
 import ActionsBar from './component';
 import Service from './service';
 import VideoService from '../video-provider/service';
@@ -49,6 +51,8 @@ export default withTracker(() => {
     getBreakouts: Service.getBreakouts,
     getUsersNotAssigned: Service.getUsersNotAssigned,
     handleTakePresenter: Service.takePresenterRole,
+    currentSlidHasContent: PresentationService.currentSlidHasContent(),
+    parseCurrentSlideContent: PresentationService.parseCurrentSlideContent,
     isSharingVideo: Service.isSharingVideo(),
   };
-})(ActionsBarContainer);
+})(injectIntl(ActionsBarContainer));
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3ce81f32a1086c064b321f4d2c43335ccb0aaff9
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx
@@ -0,0 +1,107 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, intlShape } from 'react-intl';
+import _ from 'lodash';
+import { makeCall } from '/imports/ui/services/api';
+import Button from '/imports/ui/components/button/component';
+import Dropdown from '/imports/ui/components/dropdown/component';
+import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
+import DropdownContent from '/imports/ui/components/dropdown/content/component';
+import DropdownList from '/imports/ui/components/dropdown/list/component';
+import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
+import { styles } from '../styles';
+
+const intlMessages = defineMessages({
+  quickPollLabel: {
+    id: 'app.poll.quickPollTitle',
+    description: 'Quick poll button title',
+  },
+  trueOptionLabel: {
+    id: 'app.poll.t',
+    description: 'Poll true option value',
+  },
+  falseOptionLabel: {
+    id: 'app.poll.f',
+    description: 'Poll false option value',
+  },
+  yesOptionLabel: {
+    id: 'app.poll.y',
+    description: 'Poll yes option value',
+  },
+  noOptionLabel: {
+    id: 'app.poll.n',
+    description: 'Poll no option value',
+  },
+});
+
+const propTypes = {
+  intl: intlShape.isRequired,
+  parseCurrentSlideContent: PropTypes.func.isRequired,
+  isUserPresenter: PropTypes.bool.isRequired,
+
+};
+
+const handleClickQuickPoll = (slideId, poll) => {
+  const { type } = poll;
+  Session.set('openPanel', 'poll');
+  Session.set('forcePollOpen', true);
+
+  makeCall('startPoll', type, slideId);
+};
+
+
+const getAvailableQuickPolls = (slideId, parsedSlides) => parsedSlides.map((poll) => {
+  const { poll: label, type } = poll;
+  let itemLabel = label;
+
+  if (type !== 'YN' && type !== 'TF') {
+    const { options } = itemLabel;
+    itemLabel = options.join('/').replace(/[\n.)]/g, '');
+  }
+
+  return (
+    <DropdownListItem
+      label={itemLabel}
+      key={_.uniqueId('quick-poll-item')}
+      onClick={() => handleClickQuickPoll(slideId, poll)}
+    />);
+});
+
+const QuickPollDropdown = (props) => {
+  const { isUserPresenter, intl, parseCurrentSlideContent } = props;
+  const parsedSlide = parseCurrentSlideContent(
+    intl.formatMessage(intlMessages.yesOptionLabel),
+    intl.formatMessage(intlMessages.noOptionLabel),
+    intl.formatMessage(intlMessages.trueOptionLabel),
+    intl.formatMessage(intlMessages.falseOptionLabel),
+  );
+
+  const { slideId, quickPollOptions } = parsedSlide;
+
+  return isUserPresenter && quickPollOptions && quickPollOptions.length ? (
+    <Dropdown>
+      <DropdownTrigger tabIndex={0}>
+        <Button
+          aria-label={intl.formatMessage(intlMessages.quickPollLabel)}
+          circle
+          className={styles.button}
+          color="primary"
+          hideLabel
+          icon="polling"
+          label={intl.formatMessage(intlMessages.quickPollLabel)}
+          onClick={() => null}
+          size="lg"
+        />
+      </DropdownTrigger>
+      <DropdownContent placement="top left">
+        <DropdownList>
+          {getAvailableQuickPolls(slideId, quickPollOptions)}
+        </DropdownList>
+      </DropdownContent>
+    </Dropdown>
+  ) : null;
+};
+
+QuickPollDropdown.propTypes = propTypes;
+
+export default QuickPollDropdown;
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js
index 4ce6bae649357e37a72c7b72ae3ab8acc0537d96..ddb02f7550b9e91734b2ac77a7d25ceeceaba5ac 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js
@@ -26,7 +26,7 @@ const filterUsersNotAssigned = filterBreakoutUsers(currentBreakoutUsers);
 
 const mapUsersToNotAssined = mapFunction => users => users.map(mapFunction);
 
-const flatUsersArray = usersArray => usersArray.reduce((acc, users) => [...acc, users], []);
+const flatUsersArray = usersArray => usersArray.reduce((acc, users) => [...acc, ...users], []);
 
 /*
   The concept of pipe is simple
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss
index b7d8f42715468ff98d10381edc2919b3896c8ea6..3d84e405f2d47efc1ad00357fbe6ce9e9a085826 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss
@@ -33,21 +33,19 @@
 
   &.glow {
     border-radius: 50%;
-
+    
     [style~="--enableAnimation:1;"] & {
       animation: pulse 1s infinite ease-in;
     }
+    [style~="--enableAnimation:0;"] & span {
+      content: '';
+      outline: none !important;
+      background-clip: padding-box;
+      box-shadow: 0 0 0 4px rgba(255,255,255,.5);
+    }
   }
 }
 
-[style~="--enableAnimation:0;"] .button.glow span {
-  content: '';
-  outline: none !important;
-  background-clip: padding-box;
-  border: var(--border-size-large) solid transparent;
-  box-shadow: 0 0 0 var(--border-size) rgba(255,255,255,.5);
-}
-
 @keyframes pulse {
   0% {
     box-shadow: 0 0 0 0 white;
diff --git a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
index ae9266b58cdbfca6367af415876b1dc9db167fc8..8c6aef33c212c29026f531e1c1c8b4bdc2ab85ef 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
@@ -1,5 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
+import { Session } from 'meteor/session';
 import Button from '/imports/ui/components/button/component';
 import { defineMessages, intlShape, injectIntl } from 'react-intl';
 import { styles } from './styles';
@@ -39,6 +40,14 @@ class EchoTest extends Component {
     this.handleNo = props.handleNo.bind(this);
   }
 
+  componentDidMount() {
+    Session.set('inEchoTest', true);
+  }
+
+  componentWillUnmount() {
+    Session.set('inEchoTest', false);
+  }
+
   render() {
     const {
       intl,
diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js
index 8730e413a51178bb0bdcb24b912e8812cae2e17e..aa0bc5e47054aa8b4f81a92efd6ce45a0d445249 100755
--- a/bigbluebutton-html5/imports/ui/components/chat/service.js
+++ b/bigbluebutton-html5/imports/ui/components/chat/service.js
@@ -20,6 +20,8 @@ const PRIVATE_CHAT_TYPE = CHAT_CONFIG.type_private;
 const PUBLIC_CHAT_USER_ID = CHAT_CONFIG.system_userid;
 const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.system_messages_keys.chat_clear;
 
+const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
+
 const ScrollCollection = new Mongo.Collection(null);
 
 const UnsentMessagesCollection = new Mongo.Collection(null);
@@ -135,11 +137,14 @@ const isChatLocked = (receiverID) => {
   const user = Users.findOne({ userId: Auth.userID });
 
   if (meeting.lockSettingsProp !== undefined) {
-    const isPubChatLocked = meeting.lockSettingsProp.disablePubChat;
-    const isPrivChatLocked = meeting.lockSettingsProp.disablePrivChat;
-
-    return mapUser(user).isLocked
-      && ((isPublic && isPubChatLocked) || (!isPublic && isPrivChatLocked));
+    if (mapUser(user).isLocked) {
+      if (isPublic) {
+        return meeting.lockSettingsProp.disablePubChat;
+      }
+      const receivingUser = Users.findOne({ userId: receiverID });
+      const receiverIsMod = receivingUser && receivingUser.role === ROLE_MODERATOR;
+      return !receiverIsMod && meeting.lockSettingsProp.disablePrivChat;
+    }
   }
 
   return false;
diff --git a/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss b/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss
index a329f8a7c09212bb5ef3bd19ec31db041134ee21..0758bbff572084a18f26f15ae94da27546c107d9 100644
--- a/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/error-screen/styles.scss
@@ -48,6 +48,6 @@
 }
 
 .button {
-  width: 9rem;
+  min-width: 9rem;
   height: 2rem;
 }
\ No newline at end of file
diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx
index 6089016c5da43995a1210c4a07bbdb7e7bcd102e..b6a0fb3f510eefa6e26c7d40b0ff82443c54427b 100644
--- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx
@@ -15,6 +15,10 @@ class VideoPlayer extends Component {
     this.playerState = PlayerState.UNSTARTED;
     this.presenterCommand = false;
     this.preventStateChange = false;
+    this.state = {
+      mutedByEchoTest: false,
+    };
+
     this.opts = {
       playerVars: {
         width: '100%',
@@ -30,6 +34,7 @@ class VideoPlayer extends Component {
     this.handleResize = this.handleResize.bind(this);
     this.handleOnReady = this.handleOnReady.bind(this);
     this.handleStateChange = this.handleStateChange.bind(this);
+    this.changeState = this.changeState.bind(this);
     this.resizeListener = () => {
       setTimeout(this.handleResize, 0);
     };
@@ -39,8 +44,23 @@ class VideoPlayer extends Component {
     window.addEventListener('resize', this.resizeListener);
   }
 
-  componentDidUpdate(nextProps) {
-    if (!nextProps.videoId) {
+  componentDidUpdate(prevProps) {
+    const { inEchoTest } = this.props;
+    const {
+      mutedByEchoTest,
+    } = this.state;
+
+    if (inEchoTest && !this.player.isMuted() && !mutedByEchoTest) {
+      this.player.mute();
+      this.changeState(true);
+    }
+
+    if (!inEchoTest && prevProps.inEchoTest && mutedByEchoTest) {
+      this.player.unMute();
+      this.changeState(false);
+    }
+
+    if (!prevProps.videoId) {
       clearInterval(this.syncInterval);
     }
   }
@@ -53,6 +73,10 @@ class VideoPlayer extends Component {
     this.refPlayer = null;
   }
 
+  changeState(booleanValue) {
+    this.setState({ mutedByEchoTest: booleanValue });
+  }
+
   handleResize() {
     if (!this.player || !this.refPlayer) {
       return;
diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx
index 9a6445c72d46594251503d2f82ffa729c8346924..aca54887bf628b7b778478cb63199ebba75739c5 100644
--- a/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx
@@ -1,6 +1,7 @@
 import React from 'react';
 import { defineMessages, injectIntl } from 'react-intl';
 import { withTracker } from 'meteor/react-meteor-data';
+import { Session } from 'meteor/session';
 import ExternalVideo from './component';
 
 const intlMessages = defineMessages({
@@ -18,8 +19,9 @@ const ExternalVideoContainer = props => (
 
 export default injectIntl(withTracker(({ params, intl, isPresenter }) => {
   const title = intl.formatMessage(intlMessages.title);
-
+  const inEchoTest = Session.get('inEchoTest');
   return {
+    inEchoTest,
     title,
     isPresenter,
   };
diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx
index cc89e76ece87407a1d53998f5f43257683d44ec2..7a4fd90b3a3f709a80763b8d5598657c56f00ba1 100755
--- a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx
@@ -8,7 +8,7 @@ import getFromUserSettings from '/imports/ui/services/users-settings';
 import logoutRouteHandler from '/imports/utils/logoutRouteHandler';
 import Rating from './rating/component';
 import { styles } from './styles';
-
+import logger from '/imports/startup/client/logger';
 
 const intlMessage = defineMessages({
   410: {
@@ -121,6 +121,9 @@ class MeetingEnded extends React.PureComponent {
       },
     };
 
+    // client logger
+    logger.info({ feedback: message, logCode: 'feedback' }, 'Feedback');
+
     fetch(url, options)
       .then(() => {
         logoutRouteHandler();
diff --git a/bigbluebutton-html5/imports/ui/components/note/component.jsx b/bigbluebutton-html5/imports/ui/components/note/component.jsx
index f9e52d4e6034b098665ba2771dd7820cea7f22e8..7233ebc490dcd4e13f6f6a98d069625b0c623dd6 100644
--- a/bigbluebutton-html5/imports/ui/components/note/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/note/component.jsx
@@ -15,6 +15,10 @@ const intlMessages = defineMessages({
     id: 'app.note.title',
     description: 'Title for the shared notes',
   },
+  tipLabel: {
+    id: 'app.note.tipLabel',
+    description: 'Label for tip on how to escape iframe',
+  },
 });
 
 const propTypes = {
@@ -57,7 +61,11 @@ const Note = (props) => {
       <iframe
         title="etherpad"
         src={url}
+        aria-describedby="sharedNotesEscapeHint"
       />
+      <span id="sharedNotesEscapeHint" className={styles.hint} aria-hidden>
+        {intl.formatMessage(intlMessages.tipLabel)}
+      </span>
     </div>
   );
 };
diff --git a/bigbluebutton-html5/imports/ui/components/note/styles.scss b/bigbluebutton-html5/imports/ui/components/note/styles.scss
index 8f9be377f1fc9c1bacc5135f43184b70feddd714..63f8f336873b438546134f040fb13fc19f74145a 100644
--- a/bigbluebutton-html5/imports/ui/components/note/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/note/styles.scss
@@ -61,6 +61,22 @@
   }
 }
 
+.hint {
+  visibility: hidden;
+  position: absolute;
+
+  @media (pointer: none) {
+    visibility: visible;
+    position: relative;
+    color: var(--color-gray);
+    font-size: var(--font-size-small);
+    font-style: italic;
+    padding-top: var(--sm-padding-x);
+    padding-left: var(--lg-padding-y);
+    text-align: left;
+  }
+}
+
 iframe {
   display: flex;
   flex-flow: column;
@@ -70,4 +86,5 @@ iframe {
   overflow-x: hidden;
   overflow-y: auto;
   border-style: none;
+  border-bottom: 1px solid var(--color-gray-lightest);
 }
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
index 1f8c1c64b6bd5da60979f2fde4edee3564798fca..169d1dbf56a549b96d0355df2b1d40cac5f2833c 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
@@ -39,7 +39,8 @@ const intlMessages = defineMessages({
   },
 });
 
-const isFullscreen = () => document.fullscreenElement !== null;
+const isFullscreen = () => !(document.fullscreenElement === null
+    || document.webkitFullscreenElement === null); // Edge
 
 const renderPresentationClose = () => {
   if (!shouldEnableSwapLayout() || isFullscreen()) return null;
@@ -390,7 +391,14 @@ class PresentationArea extends Component {
     const { intl } = this.props;
     if (isFullscreen()) return null;
 
-    const full = () => this.refPresentationContainer.requestFullscreen();
+    const full = () => {
+      const presentation = this.refPresentationContainer;
+      if (presentation.requestFullscreen) {
+        presentation.requestFullscreen();
+      } else if (presentation.webkitRequestFullscreen) { // Edge
+        presentation.webkitRequestFullscreen();
+      }
+    };
 
     return (
       <FullscreenButton
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/service.js b/bigbluebutton-html5/imports/ui/components/presentation/service.js
index f8919fb25aa16fdc1567787818843f6f9460e38e..9edc8b4db71d4be79c59478c9388013c7b76c99e 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/service.js
+++ b/bigbluebutton-html5/imports/ui/components/presentation/service.js
@@ -56,6 +56,76 @@ const getCurrentSlide = (podId) => {
   );
 };
 
+const currentSlidHasContent = () => {
+  const currentSlide = getCurrentSlide('DEFAULT_PRESENTATION_POD');
+  if (!currentSlide) return false;
+
+  const { content } = currentSlide;
+
+  return !!content.length;
+};
+
+const parseCurrentSlideContent = (yesValue, noValue, trueValue, falseValue) => {
+  const currentSlide = getCurrentSlide('DEFAULT_PRESENTATION_POD');
+  const quickPollOptions = [];
+  if (!currentSlide) return quickPollOptions;
+
+  const { content } = currentSlide;
+
+  const pollRegex = /\n[^\s][.)]/g;
+  const optionsPoll = content.match(pollRegex) || [];
+
+  const ynPollString = `(${yesValue}\\s*\\/\\s*${noValue})|(${noValue}\\s*\\/\\s*${yesValue})`;
+  const ynOptionsRegex = new RegExp(ynPollString, 'gi');
+  const ynPoll = content.match(ynOptionsRegex) || [];
+
+  const tfPollString = `(${trueValue}\\s*\\/\\s*${falseValue})|(${falseValue}\\s*\\/\\s*${trueValue})`;
+  const tgOptionsRegex = new RegExp(tfPollString, 'gi');
+  const tfPoll = content.match(tgOptionsRegex) || [];
+
+  optionsPoll.reduce((acc, currentValue) => {
+    const lastElement = acc[acc.length - 1];
+
+    if (!lastElement) {
+      acc.push({
+        options: [currentValue],
+      });
+      return acc;
+    }
+
+    const { options } = lastElement;
+
+    const lastOption = options[options.length - 1];
+
+    const isLastOptionInteger = !!parseInt(lastOption.charAt(1), 10);
+    const isCurrentValueInteger = !!parseInt(currentValue.charAt(1), 10);
+
+    if (isLastOptionInteger === isCurrentValueInteger) {
+      if (currentValue.toLowerCase().charCodeAt(1) > lastOption.toLowerCase().charCodeAt(1)) {
+        options.push(currentValue);
+      } else {
+        acc.push({
+          options: [currentValue],
+        });
+      }
+    } else {
+      acc.push({
+        options: [currentValue],
+      });
+    }
+
+    return acc;
+  }, [])
+    .filter(({ options }) => options.length > 1 && options.length < 7)
+    .forEach(poll => quickPollOptions.push({ type: `A-${poll.options.length}`, poll }));
+
+  ynPoll.forEach(poll => quickPollOptions.push({ type: 'YN', poll }));
+
+  tfPoll.forEach(poll => quickPollOptions.push({ type: 'TF', poll }));
+
+  return { slideId: currentSlide.id, quickPollOptions };
+};
+
 const isPresenter = (podId) => {
   // a main presenter in the meeting always owns a default pod
   if (podId === 'DEFAULT_PRESENTATION_POD') {
@@ -83,4 +153,6 @@ export default {
   isPresentationDownloadable,
   downloadPresentationUri,
   getMultiUserStatus,
+  currentSlidHasContent,
+  parseCurrentSlideContent,
 };
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/styles.scss b/bigbluebutton-html5/imports/ui/components/presentation/styles.scss
index aa7a91c43ed793b8576fbb1fdfcc129acf3d5ce7..b9ae82a82bc74298eb90075c1efa1c032870656b 100644
--- a/bigbluebutton-html5/imports/ui/components/presentation/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/presentation/styles.scss
@@ -77,4 +77,4 @@
   clip: rect(0 0 0 0);
   height: 1px; width: 1px;
   margin: -1px; padding: 0; border: 0;
-}
\ No newline at end of file
+}
diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss
index f8a005c423d1b48449462bd0f11fa84341fa173c..94c2358b09c5657de5f26f96455216d72325ff87 100755
--- a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.scss
@@ -54,7 +54,7 @@
     z-index: 1;
 
     [style~="--enableAnimation:1;"] & {
-      transition: .3s ease-in-out;
+        transition: .3s ease-in-out;
     }
   }
 }
@@ -71,19 +71,21 @@
   [style~="--enableAnimation:1;"] & {
     animation: pulse 1s infinite ease-in;
   }
-}
 
-[style~="--enableAnimation:0;"] .talking::before {
-  content: '';
-  position: absolute;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  background-color: var(--user-color);
-  border-radius: inherit;
-  box-shadow: 0px 0px 0px 4px var(--user-color);
-  opacity: .5;
+  &::before {
+    [style~="--enableAnimation:0;"] & {
+      content: '';
+      position: absolute;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+      background-color: var(--user-color);
+      border-radius: inherit;
+      box-shadow: 0 0 0 4px var(--user-color);
+      opacity: .5;
+    }
+  }
 }
 
 @keyframes pulse {
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
index 42177bb19c6fdaffd55c08ee559a66110a0b75bb..a79bdf7d3321e6b2b9a75b1661079faa4b09f44f 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
@@ -226,7 +226,9 @@ class UserDropdown extends PureComponent {
     const enablePrivateChat = currentUser.isModerator
       ? allowedToChatPrivately
       : allowedToChatPrivately
-      && (!disablePrivChat || (disablePrivChat && hasPrivateChatBetweenUsers(currentUser, user)));
+      && (!(currentUser.isLocked && disablePrivChat)
+        || hasPrivateChatBetweenUsers(currentUser, user)
+        || user.isModerator);
 
     if (showNestedOptions) {
       if (allowedToChangeStatus) {
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
index 26229f2cf00ec0988b265413ceea44b0c2c19cad..88828591aa62357cf692aaf9266a8367d547e780 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx
@@ -104,20 +104,12 @@ class UserOptions extends PureComponent {
 
     this.onActionsShow = this.onActionsShow.bind(this);
     this.onActionsHide = this.onActionsHide.bind(this);
-    this.alterMenu = this.alterMenu.bind(this);
     this.handleCreateBreakoutRoomClick = this.handleCreateBreakoutRoomClick.bind(this);
     this.onCreateBreakouts = this.onCreateBreakouts.bind(this);
     this.onInvitationUsers = this.onInvitationUsers.bind(this);
     this.renderMenuItems = this.renderMenuItems.bind(this);
   }
 
-  componentDidUpdate(prevProps) {
-    const { isMeetingMuted } = this.props;
-    if (prevProps.isMeetingMuted !== isMeetingMuted) {
-      this.alterMenu();
-    }
-  }
-
   onActionsShow() {
     this.setState({
       isUserOptionsOpen: true,
@@ -164,44 +156,6 @@ class UserOptions extends PureComponent {
     );
   }
 
-  alterMenu() {
-    const {
-      intl,
-      isMeetingMuted,
-      toggleMuteAllUsers,
-      toggleMuteAllUsersExceptPresenter,
-    } = this.props;
-
-    if (isMeetingMuted) {
-      const menuButton = (
-        <DropdownListItem
-          key={_.uniqueId('list-item-')}
-          icon="unmute"
-          label={intl.formatMessage(intlMessages.unmuteAllLabel)}
-          description={intl.formatMessage(intlMessages.unmuteAllDesc)}
-          onClick={toggleMuteAllUsers}
-        />
-      );
-      this.menuItems.splice(1, 2, menuButton);
-    } else {
-      const muteMeetingButtons = [(<DropdownListItem
-        key={_.uniqueId('list-item-')}
-        icon="mute"
-        label={intl.formatMessage(intlMessages.muteAllLabel)}
-        description={intl.formatMessage(intlMessages.muteAllDesc)}
-        onClick={toggleMuteAllUsers}
-      />), (<DropdownListItem
-        key={_.uniqueId('list-item-')}
-        icon="mute"
-        label={intl.formatMessage(intlMessages.muteAllExceptPresenterLabel)}
-        description={intl.formatMessage(intlMessages.muteAllExceptPresenterDesc)}
-        onClick={toggleMuteAllUsersExceptPresenter}
-      />)];
-
-      this.menuItems.splice(1, 1, muteMeetingButtons[0], muteMeetingButtons[1]);
-    }
-  }
-
   renderMenuItems() {
     const {
       intl,
@@ -236,18 +190,20 @@ class UserOptions extends PureComponent {
       />),
       (<DropdownListItem
         key={this.muteAllId}
-        icon="mute"
-        label={intl.formatMessage(intlMessages.muteAllLabel)}
-        description={intl.formatMessage(intlMessages.muteAllDesc)}
+        icon={isMeetingMuted ? 'unmute' : 'mute'}
+        label={intl.formatMessage(intlMessages[isMeetingMuted ? 'unmuteAllLabel' : 'muteAllLabel'])}
+        description={intl.formatMessage(intlMessages[isMeetingMuted ? 'unmuteAllDesc' : 'muteAllDesc'])}
         onClick={toggleMuteAllUsers}
       />),
-      (<DropdownListItem
-        key={this.muteId}
-        icon="mute"
-        label={intl.formatMessage(intlMessages.muteAllExceptPresenterLabel)}
-        description={intl.formatMessage(intlMessages.muteAllExceptPresenterDesc)}
-        onClick={toggleMuteAllUsersExceptPresenter}
-      />),
+      (!isMeetingMuted ? (
+        <DropdownListItem
+          key={this.muteId}
+          icon="mute"
+          label={intl.formatMessage(intlMessages.muteAllExceptPresenterLabel)}
+          description={intl.formatMessage(intlMessages.muteAllExceptPresenterDesc)}
+          onClick={toggleMuteAllUsersExceptPresenter}
+        />) : null
+      ),
       (<DropdownListItem
         key={this.lockId}
         icon="lock"
@@ -278,10 +234,6 @@ class UserOptions extends PureComponent {
         : null),
     ]);
 
-    if (isMeetingMuted) {
-      this.alterMenu();
-    }
-
     return this.menuItems;
   }
 
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/fullscreen-button/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/fullscreen-button/component.jsx
index ece3326cca6e1642c308aede1bf13787c4836fcd..a17757549808334eccb24f456cb6184a2ec6b46d 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/fullscreen-button/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/fullscreen-button/component.jsx
@@ -29,7 +29,7 @@ const FullscreenButtonComponent = ({
 }) => {
   const formattedLabel = intl.formatMessage(
     intlMessages.fullscreenButton,
-    ({ 0: elementName ? elementName.toLowerCase() : '' }),
+    ({ 0: elementName || '' }),
   );
 
   return (
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss
index 8c25b9ed4a5c87da03764694dcce94b463886fa7..4e0943178bc91699f98a9f00789590b230de088a 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/styles.scss
@@ -30,6 +30,7 @@
 
 .videoListItem {
   display: flex;
+  overflow: hidden;
 
   &.focused {
     grid-column: 1 / span 2;
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
index ea5c4dfbaeaa27e410a2b17f063012295498cfb9..8d208ddb7650dca7f59609327964a40064308414 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
@@ -109,7 +109,12 @@ class VideoListItem extends Component {
   renderFullscreenButton() {
     const { user } = this.props;
     const full = () => {
-      this.videoTag.requestFullscreen();
+      const tag = this.videoTag;
+      if (tag.requestFullscreen) {
+        tag.requestFullscreen();
+      } else if (tag.webkitRequestFullscreen) { // Edge
+        tag.webkitRequestFullscreen();
+      }
     };
     return <FullscreenButton handleFullscreen={full} elementName={user.name} />;
   }
diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss
index e49d815bbe674512643e699e472ea0529fecaf48..0cfd6dd9f30ed2027f75cd0b563cb8a490f5da3a 100644
--- a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss
+++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss
@@ -7,6 +7,7 @@
   --color-gray-dark: #06172A;
   --color-gray-light: #8B9AA8;
   --color-gray-lighter: #a7b3bd;
+  --color-gray-lightest: #d4d9df;
 
   --color-blue-light: #54a1f3;
   --color-blue-lighter: #92BCEA;
diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml
index 8e755abe398835b337f4b79eb542e312d093e52f..9f584a13f3bfaa83766807ce5ecd00de42bcaeaf 100755
--- a/bigbluebutton-html5/private/config/settings.yml
+++ b/bigbluebutton-html5/private/config/settings.yml
@@ -13,7 +13,7 @@ public:
     bbbServerVersion: 2.2-dev
     copyright: "©2019 BigBlueButton Inc."
     html5ClientBuild: HTML5_CLIENT_VERSION
-    helpLink: https://bigbluebutton.org/videos/
+    helpLink: https://bigbluebutton.org/html5/
     lockOnJoin: true
     basename: "/html5client"
     askForFeedbackOnLogout: false
@@ -93,14 +93,14 @@ public:
     firefoxScreenshareSource: window
     cameraConstraints:
       width:
-        max: 640
+        max: 320
       height:
-        max: 480
+        max: 240
     enableScreensharing: false
     enableVideo: true
     enableVideoStats: false
     enableVideoMenu: true
-    enableListenOnly: false
+    enableListenOnly: true
     autoShareWebcam: false
   allowOutsideCommands:
     toggleRecording: false
diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json
index acb687ee9012e030f52e690d1d3b8429be8af315..5495cffa4802bb45a65858bb2c2665c7f61c0638 100755
--- a/bigbluebutton-html5/private/locales/en.json
+++ b/bigbluebutton-html5/private/locales/en.json
@@ -21,6 +21,7 @@
     "app.note.title": "Shared Notes",
     "app.note.label": "Note",
     "app.note.hideNoteLabel": "Hide note",
+    "app.note.tipLabel": "Press Esc to focus editor toolbar",
     "app.userList.usersTitle": "Users",
     "app.userList.participantsTitle": "Participants",
     "app.userList.messagesTitle": "Messages",
@@ -119,6 +120,7 @@
     "app.presentationUploder.removePresentationLabel": "Remove presentation",
     "app.presentationUploder.setAsCurrentPresentation": "Set presentation as current",
     "app.poll.pollPaneTitle": "Polling",
+    "app.poll.quickPollTitle": "Quick Poll",
     "app.poll.hidePollDesc": "Hides the poll menu pane",
     "app.poll.customPollInstruction": "To create a custom poll, select the button below and input your options.",
     "app.poll.quickPollInstruction": "Select an option below to start your poll.",
@@ -130,7 +132,11 @@
     "app.poll.closeLabel": "Close",
     "app.poll.ariaInputCount": "Input {0} of {1}",
     "app.poll.customPlaceholder": "Add poll option",
+    "app.poll.t": "True",
+    "app.poll.f": "False",
     "app.poll.tf": "True / False",
+    "app.poll.y": "Yes",
+    "app.poll.n": "No",
     "app.poll.yn": "Yes / No",
     "app.poll.a2": "A / B",
     "app.poll.a3": "A / B / C",
diff --git a/record-and-playback/core/lib/recordandplayback/events_archiver.rb b/record-and-playback/core/lib/recordandplayback/events_archiver.rb
index 484836279b29aa2bfbc72431f572a1a41dc1c546..c740d26e4baa49b81610cf1d3e74bad07405935d 100755
--- a/record-and-playback/core/lib/recordandplayback/events_archiver.rb
+++ b/record-and-playback/core/lib/recordandplayback/events_archiver.rb
@@ -328,8 +328,14 @@ module BigBlueButton
         # in the file, find the correct spot (it's usually no more than 1 or 2 off).
         # Make sure not to change the relative order of two events with the same timestamp.
         previous_event = recording.last_element_child
+        moved = 0
         while previous_event.name == 'event' && previous_event['timestamp'].to_i > event['timestamp'].to_i
           previous_event = previous_event.previous_element
+          moved += 1
+        end
+        if moved > 0
+          BigBlueButton.logger.info("Reordered event timestamp=#{res[TIMESTAMP]} module=#{res[MODULE]} " \
+                                    "eventname=#{res[EVENTNAME]} by #{moved} positions")
         end
         previous_event.add_next_sibling(event)
 
diff --git a/record-and-playback/core/scripts/rap-archive-worker.rb b/record-and-playback/core/scripts/rap-archive-worker.rb
index f8a27a9f099360c5f6f5b36bad058f90ca8183bd..f0fdd67f14cb9cbd138db57cfee4e26305b4573d 100755
--- a/record-and-playback/core/scripts/rap-archive-worker.rb
+++ b/record-and-playback/core/scripts/rap-archive-worker.rb
@@ -23,9 +23,6 @@ require 'rubygems'
 require 'yaml'
 require 'fileutils'
 
-# Number of seconds to delay archiving (red5 race condition workaround)
-ARCHIVE_DELAY_SECONDS = 120
-
 def archive_recorded_meetings(recording_dir)
   recorded_done_files = Dir.glob("#{recording_dir}/status/recorded/*.done")
 
@@ -45,11 +42,6 @@ def archive_recorded_meetings(recording_dir)
       next
     end
 
-    if File.mtime(recorded_done) + ARCHIVE_DELAY_SECONDS > Time.now
-      BigBlueButton.logger.info("Temporarily skipping #{recorded_done_base} for Red5 race workaround")
-      next
-    end
-
     archived_done = "#{recording_dir}/status/archived/#{recorded_done_base}.done"
     next if File.exists?(archived_done)