blob: 1981077377805e5be1ef1da7daf22746ef9cb9f6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License") + you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openmeetings.web.room;
import static de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal.BUTTON_MARKUP_ID;
import static java.time.Duration.ZERO;
import static org.apache.openmeetings.core.util.ChatWebSocketHelper.ID_USER_PREFIX;
import static org.apache.openmeetings.db.entity.calendar.Appointment.allowedStart;
import static org.apache.openmeetings.util.OmFileHelper.EXTENSION_PDF;
import static org.apache.openmeetings.web.app.WebSession.getDateFormat;
import static org.apache.openmeetings.web.app.WebSession.getUserId;
import static org.apache.openmeetings.web.room.wb.WbPanel.WB_JS_REFERENCE;
import static org.apache.openmeetings.mediaserver.remote.KurentoHandler.activityAllowed;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Calendar;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.openmeetings.core.util.WebSocketHelper;
import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
import org.apache.openmeetings.db.dao.file.FileItemDao;
import org.apache.openmeetings.db.dao.user.UserDao;
import org.apache.openmeetings.db.entity.basic.Client;
import org.apache.openmeetings.db.entity.calendar.Appointment;
import org.apache.openmeetings.db.entity.file.BaseFileItem;
import org.apache.openmeetings.db.entity.room.Room;
import org.apache.openmeetings.db.entity.room.Room.Right;
import org.apache.openmeetings.db.entity.room.Room.RoomElement;
import org.apache.openmeetings.db.entity.room.RoomGroup;
import org.apache.openmeetings.db.entity.server.SOAPLogin;
import org.apache.openmeetings.db.util.AuthLevelUtil;
import org.apache.openmeetings.db.util.ws.RoomMessage;
import org.apache.openmeetings.db.util.ws.RoomMessage.Type;
import org.apache.openmeetings.db.util.ws.TextRoomMessage;
import org.apache.openmeetings.util.NullStringer;
import org.apache.openmeetings.web.app.Application;
import org.apache.openmeetings.web.app.ClientManager;
import org.apache.openmeetings.web.app.QuickPollManager;
import org.apache.openmeetings.web.app.TimerService;
import org.apache.openmeetings.web.app.WebSession;
import org.apache.openmeetings.web.common.BasePanel;
import org.apache.openmeetings.web.room.activities.Activity;
import org.apache.openmeetings.web.room.menu.RoomMenuPanel;
import org.apache.openmeetings.web.room.sidebar.RoomSidebar;
import org.apache.openmeetings.web.room.wb.AbstractWbPanel;
import org.apache.openmeetings.web.room.wb.InterviewWbPanel;
import org.apache.openmeetings.web.room.wb.WbAction;
import org.apache.openmeetings.web.room.wb.WbPanel;
import org.apache.openmeetings.web.util.ExtendedClientProperties;
import org.apache.openmeetings.web.util.TouchPunchResourceReference;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.extensions.ajax.AjaxDownloadBehavior;
import org.apache.wicket.markup.head.HeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.head.PriorityHeaderItem;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.protocol.ws.api.BaseWebSocketBehavior;
import org.apache.wicket.protocol.ws.api.event.WebSocketPushPayload;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.request.resource.ResourceStreamResource;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.resource.FileResourceStream;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.string.Strings;
import org.apache.openmeetings.mediaserver.remote.KurentoHandler;
import org.apache.openmeetings.mediaserver.remote.StreamProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.openjson.JSONArray;
import com.github.openjson.JSONObject;
import com.googlecode.wicket.jquery.core.JQueryBehavior;
import com.googlecode.wicket.jquery.core.Options;
import com.googlecode.wicket.jquery.ui.interaction.droppable.Droppable;
import com.googlecode.wicket.jquery.ui.settings.JQueryUILibrarySettings;
import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapAjaxLink;
import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Alert;
import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal.Backdrop;
import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.TextContentModal;
@AuthorizeInstantiation("ROOM")
public class RoomPanel extends BasePanel {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(RoomPanel.class);
public static final String PARAM_ACTION = "action";
private static final String ACCESS_DENIED_ID = "access-denied";
private static final String EVENT_DETAILS_ID = "event-details";
public enum Action {
KICK("kick")
, MUTE_OTHERS("muteOthers")
, MUTE("mute")
, TOGGLE_RIGHT("toggleRight");
private final String jsName;
private Action(String jsName) {
this.jsName = jsName;
}
public static Action of(String jsName) {
return Stream.of(Action.values())
.filter(a -> a.jsName.equals(jsName))
.findAny()
.orElse(null);
}
}
private final Room r;
private final boolean interview;
private final WebMarkupContainer room = new WebMarkupContainer("roomContainer");
private final AbstractDefaultAjaxBehavior roomEnter = new AbstractDefaultAjaxBehavior() {
private static final long serialVersionUID = 1L;
@Override
protected void respond(AjaxRequestTarget target) {
log.debug("RoomPanel::roomEnter");
WebSession ws = WebSession.get();
Client c = getClient();
JSONObject options = VideoSettings.getInitJson(c.getSid())
.put("uid", c.getUid())
.put("userId", c.getUserId())
.put("rights", c.toJson(true).getJSONArray("rights"))
.put("interview", interview)
.put("audioOnly", r.isAudioOnly())
.put("allowRecording", r.isAllowRecording())
.put("questions", r.isAllowUserQuestions())
.put("showMicStatus", !r.getHiddenElements().contains(RoomElement.MICROPHONE_STATUS));
if (!Strings.isEmpty(r.getRedirectURL()) && (ws.getSoapLogin() != null || ws.getInvitation() != null)) {
options.put("reloadUrl", r.getRedirectURL());
}
StringBuilder sb = new StringBuilder("Room.init(").append(options.toString(new NullStringer())).append(");")
.append(wb.getInitScript())
.append(getQuickPollJs());
sb.append(sendClientsOnInit());
target.appendJavaScript(sb);
WebSocketHelper.sendRoom(new TextRoomMessage(r.getId(), c, RoomMessage.Type.ROOM_ENTER, c.getUid()));
// play video from other participants
initVideos(target);
getMainPanel().getChat().roomEnter(r, target);
if (r.isFilesOpened()) {
sidebar.setFilesActive(target);
}
if (Room.Type.PRESENTATION != r.getType()) {
boolean modsEmpty = noModerators();
log.debug("RoomPanel::roomEnter, mods IS EMPTY ? {}, is MOD ? {}", modsEmpty, c.hasRight(Room.Right.MODERATOR));
if (modsEmpty) {
showIdeaAlert(target, getString(r.isModerated() ? "641" : "498"));
}
}
if (r.isWaitRecording()) {
showIdeaAlert(target, getString("1315"));
}
wb.update(target);
jsInited = true;
}
private CharSequence sendClientsOnInit() {
Client c = getClient();
StringBuilder res = new StringBuilder();
if (c.hasRight(Room.Right.MODERATOR) || !r.isHidden(RoomElement.USER_COUNT)) {
res.append(createAddClientJs(c));
}
return res;
}
private void initVideos(AjaxRequestTarget target) {
StringBuilder sb = new StringBuilder();
JSONArray streams = new JSONArray();
cm.streamByRoom(getRoom().getId())
.map(Client::getStreams)
.flatMap(List::stream)
.forEach(sd -> streams.put(sd.toJson()));
if (streams.length() > 0) {
sb.append("VideoManager.play(").append(streams).append(", ").append(kHandler.getTurnServers(getClient())).append(");");
}
if (interview && streamProcessor.recordingAllowed(getClient())) {
sb.append("WbArea.setRecEnabled(true);");
}
if (!Strings.isEmpty(sb)) {
target.appendJavaScript(sb);
}
}
};
private RedirectMessageDialog roomClosed;
private Modal<String> clientKicked;
private Alert waitModerator;
private RoomMenuPanel menu;
private RoomSidebar sidebar;
private final AbstractWbPanel wb;
private String fuid;
private String ftype;
private final AjaxDownloadBehavior download = new AjaxDownloadBehavior(new ResourceStreamResource() {
private static final long serialVersionUID = 1L;
{
setCacheDuration(ZERO);
}
@Override
protected IResourceStream getResourceStream(Attributes attributes) {
setFileName(EXTENSION_PDF.equals(ftype) ? "whiteboard.pdf" : "slide.png");
return new FileResourceStream(Paths.get(System.getProperty("java.io.tmpdir"), fuid).toFile());
}
}) {
private static final long serialVersionUID = 1L;
@Override
protected void onDownloadCompleted(AjaxRequestTarget target) {
super.onDownloadCompleted(target);
try {
Files.deleteIfExists(Paths.get(System.getProperty("java.io.tmpdir"), fuid));
} catch (Exception e) {
log.error("unexcepted error while clean-up", e);
}
fuid = null;
ftype = null;
}
};
Component eventDetail = new WebMarkupContainer(EVENT_DETAILS_ID).setVisible(false);
private boolean avInited = false;
private boolean jsInited = false;
@SpringBean
private ClientManager cm;
@SpringBean
private UserDao userDao;
@SpringBean
private AppointmentDao apptDao;
@SpringBean
private QuickPollManager qpollManager;
@SpringBean
private KurentoHandler kHandler;
@SpringBean
private StreamProcessor streamProcessor;
@SpringBean
private TimerService timerService;
@SpringBean
private FileItemDao fileDao;
public RoomPanel(String id, Room r) {
super(id);
this.r = r;
this.interview = Room.Type.INTERVIEW == r.getType();
this.wb = interview ? new InterviewWbPanel("whiteboard", this) : new WbPanel("whiteboard", this);
}
public void startDownload(IPartialPageRequestHandler handler, String type, String fuid) {
this.fuid = fuid;
ftype = type;
download.initiate(handler);
}
@Override
protected void onInitialize() {
super.onInitialize();
//let's refresh user in client
Client c = getClient().updateUser(userDao);
Component accessDenied = new WebMarkupContainer(ACCESS_DENIED_ID).setVisible(false);
room.setOutputMarkupPlaceholderTag(true);
room.add(menu = new RoomMenuPanel("menu", this));
room.add(AttributeModifier.append("data-room-id", r.getId()));
if (interview) {
room.add(new WebMarkupContainer("wb-area").add(wb));
} else {
Droppable<BaseFileItem> wbArea = new Droppable<>("wb-area") {
private static final long serialVersionUID = 1L;
@Override
public void onConfigure(JQueryBehavior behavior) {
super.onConfigure(behavior);
behavior.setOption("hoverClass", Options.asString("droppable-hover"));
behavior.setOption("accept", Options.asString(".recorditem, .fileitem, .readonlyitem"));
}
@Override
public void onDrop(AjaxRequestTarget target, Component component) {
Object o = component.getDefaultModelObject();
if (wb.isVisible() && o instanceof BaseFileItem f) {
if (sidebar.getFilesPanel().isSelected(f)) {
for (Entry<String, BaseFileItem> e : sidebar.getFilesPanel().getSelected().entrySet()) {
wb.sendFileToWb(e.getValue(), false);
}
} else {
wb.sendFileToWb(f, false);
}
}
}
};
room.add(wbArea.add(wb));
}
room.add(roomEnter);
room.add(sidebar = new RoomSidebar("sidebar", this));
add(roomClosed = new RedirectMessageDialog("room-closed", "1098", r.isClosed(), r.getRedirectURL()));
if (r.isClosed()) {
room.setVisible(false);
} else if (cm.streamByRoom(r.getId()).count() >= r.getCapacity()) {
accessDenied = new ExpiredMessageDialog(ACCESS_DENIED_ID, getString("99"), menu);
room.setVisible(false);
} else if (r.getId().equals(WebSession.get().getRoomId())) {
// secureHash/invitationHash, already checked
} else {
boolean allowed = Application.get().isRoomAllowedToUser(r, c.getUser());
String deniedMessage = null;
if (r.isAppointment()) {
Appointment a = apptDao.getByRoom(r.getId());
if (allowed) {
Calendar cal = WebSession.getCalendar();
if (a.isOwner(getUserId()) || cal.getTime().after(allowedStart(a.getStart())) && cal.getTime().before(a.getEnd())) {
eventDetail = new EventDetailDialog(EVENT_DETAILS_ID, a);
} else {
allowed = false;
deniedMessage = String.format("%s %s - %s", getString("error.hash.period"), getDateFormat().format(a.getStart()), getDateFormat().format(a.getEnd()));
}
}
}
if (!allowed) {
if (deniedMessage == null) {
deniedMessage = getString("1599");
}
accessDenied = new ExpiredMessageDialog(ACCESS_DENIED_ID, deniedMessage, menu);
room.setVisible(false);
}
}
RepeatingView groupstyles = new RepeatingView("groupstyle");
add(groupstyles.setVisible(room.isVisible() && !r.getGroups().isEmpty()));
if (room.isVisible()) {
add(new NicknameDialog("nickname", this));
add(download);
add(new BaseWebSocketBehavior("media"));
for (RoomGroup rg : r.getGroups()) {
WebMarkupContainer groupstyle = new WebMarkupContainer(groupstyles.newChildId());
groupstyle.add(AttributeModifier.append("href"
, (String)RequestCycle.get().urlFor(new GroupCustomCssResourceReference(), new PageParameters().add("id", rg.getGroup().getId()))
));
groupstyles.add(groupstyle);
}
//We are setting initial rights here
final int count = cm.addToRoom(c.setRoom(getRoom()));
SOAPLogin soap = WebSession.get().getSoapLogin();
if (soap != null && soap.isModerator()) {
c.allow(Right.SUPER_MODERATOR);
} else {
Set<Right> rr = AuthLevelUtil.getRoomRight(c.getUser(), r, r.isAppointment() ? apptDao.getByRoom(r.getId()) : null, count);
if (!rr.isEmpty()) {
c.allow(rr);
log.info("Setting rights for client:: {} -> {}", rr, c.hasRight(Right.MODERATOR));
}
}
if (r.isModerated() && r.isWaitModerator()
&& !c.hasRight(Right.MODERATOR)
&& noModerators())
{
room.setVisible(false);
createWaitModerator(true);
getMainPanel().getChat().toggle(null, false);
}
timerService.scheduleModCheck(r);
} else {
add(new WebMarkupContainer("nickname").setVisible(false));
}
cm.update(c);
if (waitModerator == null) {
createWaitModerator(false);
}
add(room, accessDenied, eventDetail, waitModerator);
add(clientKicked = new TextContentModal("client-kicked", new ResourceModel("606")));
clientKicked
.header(new ResourceModel("797"))
.setCloseOnEscapeKey(false)
.setBackdrop(Backdrop.FALSE)
.addButton(new BootstrapAjaxLink<>(BUTTON_MARKUP_ID, Model.of(""), Buttons.Type.Outline_Primary, new ResourceModel("54")) {
private static final long serialVersionUID = 1L;
public void onClick(AjaxRequestTarget target) {
clientKicked.close(target);
menu.exit(target);
}
});
}
@Override
public void onEvent(IEvent<?> event) {
if (!jsInited) {
return;
}
Client curClient = getClient();
if (curClient != null && event.getPayload() instanceof WebSocketPushPayload) {
WebSocketPushPayload wsEvent = (WebSocketPushPayload) event.getPayload();
if (wsEvent.getMessage() instanceof RoomMessage m) {
IPartialPageRequestHandler handler = wsEvent.getHandler();
switch (m.getType()) {
case POLL_CREATED:
menu.updatePoll(handler, m.getUserId());
break;
case POLL_UPDATED:
menu.updatePoll(handler, null);
break;
case RECORDING_TOGGLED:
menu.update(handler);
updateInterviewRecordingButtons(handler);
break;
case SHARING_TOGGLED:
menu.update(handler);
break;
case RIGHT_UPDATED:
onRightUpdated(curClient, (TextRoomMessage)m, handler);
break;
case ROOM_ENTER:
onRoomEnter(curClient, (TextRoomMessage)m, handler);
break;
case ROOM_EXIT:
onRoomExit((TextRoomMessage)m, handler);
break;
case ROOM_CLOSED:
handler.add(room.setVisible(false));
roomClosed.show(handler);
break;
case REQUEST_RIGHT_MODERATOR:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_MODERATOR), handler);
break;
case REQUEST_RIGHT_PRESENTER:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_PRESENTER), handler);
break;
case REQUEST_RIGHT_WB:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_WB), handler);
break;
case REQUEST_RIGHT_SHARE:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_SHARE), handler);
break;
case REQUEST_RIGHT_REMOTE:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_REMOTE), handler);
break;
case REQUEST_RIGHT_A:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_A), handler);
break;
case REQUEST_RIGHT_AV:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_AV), handler);
break;
case REQUEST_RIGHT_MUTE_OTHERS:
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_MUTE_OTHERS), handler);
break;
case ACTIVITY_REMOVE:
sidebar.removeActivity(((TextRoomMessage)m).getText(), handler);
break;
case HAVE_QUESTION:
if (curClient.hasRight(Room.Right.MODERATOR) || getUserId().equals(m.getUserId())) {
sidebar.addActivity(new Activity((TextRoomMessage)m, Activity.Type.REQ_RIGHT_HAVE_QUESTION), handler);
}
break;
case KICK:
onKick(curClient, (TextRoomMessage)m, handler);
break;
case MUTE:
onMute(curClient, (TextRoomMessage)m, handler);
break;
case MUTE_OTHERS:
onMuteOthers((TextRoomMessage)m, handler);
break;
case QUICK_POLL_UPDATED:
menu.update(handler);
handler.appendJavaScript(getQuickPollJs());
break;
case KURENTO_STATUS:
menu.update(handler);
break;
case WB_RELOAD:
if (Room.Type.INTERVIEW != r.getType()) {
wb.reloadWb(handler);
}
break;
case MODERATOR_IN_ROOM:
if (!r.isModerated() || !r.isWaitModerator()) {
log.warn("Something weird: `moderatorInRoom` in wrong room {}", r);
} else if (!curClient.hasRight(Room.Right.MODERATOR)) {
boolean moderInRoom = Boolean.TRUE.equals(Boolean.valueOf(((TextRoomMessage)m).getText()));
log.warn("!! moderatorInRoom: {}", moderInRoom);
if (room.isVisible() != moderInRoom) {
handler.add(room.setVisible(moderInRoom));
getMainPanel().getChat().toggle(handler, moderInRoom && !r.isHidden(RoomElement.CHAT));
if (room.isVisible()) {
handler.appendJavaScript(roomEnter.getCallbackScript());
handler.add(waitModerator.setVisible(false));
} else {
handler.add(waitModerator.setVisible(true));
}
}
}
break;
case WB_PUT_FILE:
onWbPutFile((TextRoomMessage)m);
break;
case FILE_TREE_UPDATE:
onFileTreeUpdate(handler);
break;
}
}
}
super.onEvent(event);
}
private void onRightUpdated(Client curClient, TextRoomMessage m, IPartialPageRequestHandler handler) {
String uid = m.getText();
Client c = cm.get(uid);
if (c == null) {
log.error("Not existing user in rightUpdated {} !!!!", uid);
return;
}
boolean self = curClient.getUid().equals(c.getUid());
StringBuilder sb = new StringBuilder("Room.updateClient(")
.append(c.toJson(self).toString(new NullStringer()))
.append(");")
.append(sendClientsOnUpdate());
handler.appendJavaScript(sb);
sidebar.update(handler);
menu.update(handler);
wb.update(handler);
updateInterviewRecordingButtons(handler);
}
private void onRoomEnter(Client curClient, TextRoomMessage m, IPartialPageRequestHandler handler) {
sidebar.update(handler);
menu.update(handler);
String uid = m.getText();
Client c = cm.get(uid);
if (c == null) {
log.error("Not existing user in rightUpdated {} !!!!", uid);
return;
}
boolean self = curClient.getUid().equals(c.getUid());
if (self || curClient.hasRight(Room.Right.MODERATOR) || !r.isHidden(RoomElement.USER_COUNT)) {
handler.appendJavaScript("Room.addClient(["
+ c.toJson(self).toString(new NullStringer()) + "]);");
}
sidebar.addActivity(new Activity(m, Activity.Type.ROOM_ENTER), handler);
}
private void onRoomExit(TextRoomMessage m, IPartialPageRequestHandler handler) {
String uid = m.getText();
sidebar.update(handler);
sidebar.addActivity(new Activity(m, Activity.Type.ROOM_EXIT), handler);
handler.appendJavaScript("Room.removeClient('" + uid + "'); Chat.removeTab('" + ID_USER_PREFIX + m.getUserId() + "');");
}
private void onKick(Client curClient, TextRoomMessage m, IPartialPageRequestHandler handler) {
String uid = m.getText();
if (curClient.getUid().equals(uid)) {
handler.add(room.setVisible(false));
getMainPanel().getChat().toggle(handler, false);
clientKicked.show(handler);
cm.exitRoom(curClient);
}
}
private void onMute(Client curClient, TextRoomMessage m, IPartialPageRequestHandler handler) {
JSONObject obj = new JSONObject(m.getText());
Client c = cm.getBySid(obj.getString("sid"));
if (c == null) {
log.error("Not existing user in mute {} !!!!", obj);
return;
}
if (!curClient.getUid().equals(c.getUid())) {
handler.appendJavaScript(String.format("if (typeof(VideoManager) !== 'undefined') {VideoManager.mute('%s', %s);}", obj.getString("uid"), obj.getBoolean("mute")));
}
}
private void onMuteOthers(TextRoomMessage m, IPartialPageRequestHandler handler) {
String uid = m.getText();
Client c = cm.get(uid);
if (c == null) {
// no luck
return;
}
handler.appendJavaScript(String.format("if (typeof(VideoManager) !== 'undefined') {VideoManager.muteOthers('%s');}", uid));
}
private void onWbPutFile(TextRoomMessage m) {
JSONObject obj = new JSONObject(m.getText());
getWb().sendFileToWb(fileDao.getAny(obj.getLong("fileId")), obj.getBoolean("clean"));
}
private void onFileTreeUpdate(IPartialPageRequestHandler handler) {
sidebar.getFilesPanel().update(handler);
}
private String getQuickPollJs() {
return String.format("Room.quickPoll(%s);", qpollManager.toJson(r.getId()));
}
private void updateInterviewRecordingButtons(IPartialPageRequestHandler handler) {
Client curClient = getClient();
if (interview && curClient.hasRight(Right.MODERATOR)) {
if (streamProcessor.isRecording(r.getId())) {
handler.appendJavaScript("if (typeof(WbArea) === 'object') {WbArea.setRecStarted(true);}");
} else if (streamProcessor.recordingAllowed(getClient())) {
boolean hasStreams = cm.streamByRoom(r.getId())
.anyMatch(cl -> !cl.getStreams().isEmpty());
handler.appendJavaScript(String.format("if (typeof(WbArea) === 'object') {WbArea.setRecStarted(false);WbArea.setRecEnabled(%s);}", hasStreams));
}
}
}
public boolean isModerator(long userId, long roomId) {
return isModerator(cm, userId, roomId);
}
public static boolean isModerator(ClientManager cm, long userId, long roomId) {
return hasRight(cm, userId, roomId, Right.MODERATOR);
}
public static boolean hasRight(ClientManager cm, long userId, long roomId, Right r) {
return cm.streamByRoom(roomId)
.anyMatch(c -> c.sameUserId(userId) && c.hasRight(r));
}
@Override
public BasePanel onMenuPanelLoad(IPartialPageRequestHandler handler) {
getBasePage().getHeader().setVisible(false);
getMainPanel().getTopControls().setVisible(false);
Component loader = getBasePage().getLoader().setVisible(false);
if (r.isHidden(RoomElement.CHAT) || !isVisible()) {
getMainPanel().getChat().toggle(handler, false);
}
if (handler != null) {
handler.add(loader, getBasePage().getHeader(), getMainPanel().getTopControls());
if (isVisible()) {
handler.appendJavaScript("Room.load();");
}
}
return this;
}
public void show(IPartialPageRequestHandler handler) {
getMainPanel().getChat().toggle(handler, !r.isHidden(RoomElement.CHAT));
handler.add(this.setVisible(true));
handler.appendJavaScript("Room.load();");
}
@Override
public void cleanup(IPartialPageRequestHandler handler) {
if (eventDetail instanceof EventDetailDialog evtDialog) {
evtDialog.close(handler);
}
handler.add(getBasePage().getHeader().setVisible(true), getMainPanel().getTopControls().setVisible(true));
if (r.isHidden(RoomElement.CHAT)) {
getMainPanel().getChat().toggle(handler, true);
}
handler.appendJavaScript("if (typeof(Room) !== 'undefined') { Room.unload(); }");
cm.exitRoom(getClient());
getMainPanel().getChat().roomExit(r, handler);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(WB_JS_REFERENCE)));
response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(RoomPanel.class, "room.js"))) {
private static final long serialVersionUID = 1L;
@Override
public List<HeaderItem> getDependencies() {
return List.of(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(JQueryUILibrarySettings.get().getJavaScriptReference())));
}
});
response.render(JavaScriptHeaderItem.forReference(TouchPunchResourceReference.instance()));
if (room.isVisible()) {
response.render(OnDomReadyHeaderItem.forScript(roomEnter.getCallbackScript()));
}
}
public void requestRight(Right right, IPartialPageRequestHandler handler) {
RoomMessage.Type reqType = null;
if (noModerators()) {
if (r.isModerated()) {
showIdeaAlert(handler, getString("696"));
return;
} else {
// we found no-one we can ask, allow right
rightsUpdated(cm.update(getClient().allow(right)));
}
}
// ask
switch (right) {
case MODERATOR:
reqType = Type.REQUEST_RIGHT_MODERATOR;
break;
case PRESENTER:
reqType = Type.REQUEST_RIGHT_PRESENTER;
break;
case WHITEBOARD:
reqType = Type.REQUEST_RIGHT_WB;
break;
case SHARE:
reqType = Type.REQUEST_RIGHT_SHARE;
break;
case AUDIO:
reqType = Type.REQUEST_RIGHT_A;
break;
case MUTE_OTHERS:
reqType = Type.REQUEST_RIGHT_MUTE_OTHERS;
break;
case REMOTE_CONTROL:
reqType = Type.REQUEST_RIGHT_REMOTE;
break;
case VIDEO:
reqType = Type.REQUEST_RIGHT_AV;
break;
default:
break;
}
if (reqType != null) {
WebSocketHelper.sendRoom(new TextRoomMessage(getRoom().getId(), getClient(), reqType, getClient().getUid()));
}
}
public void allowRight(Client client, Right... rights) {
rightsUpdated(client.allow(rights));
}
public void denyRight(Client client, Right... rights) {
for (Right right : rights) {
client.deny(right);
}
if (client.hasActivity(Client.Activity.AUDIO) && !client.hasRight(Right.AUDIO)) {
client.remove(Client.Activity.AUDIO);
}
if (client.hasActivity(Client.Activity.VIDEO) && !client.hasRight(Right.VIDEO)) {
client.remove(Client.Activity.VIDEO);
}
rightsUpdated(client);
}
public void rightsUpdated(Client c) {
cm.update(c);
streamProcessor.rightsUpdated(c);
}
public void broadcast(Client client) {
cm.update(client);
WebSocketHelper.sendRoom(new TextRoomMessage(getRoom().getId(), getClient(), RoomMessage.Type.RIGHT_UPDATED, client.getUid()));
}
@Override
protected void process(IPartialPageRequestHandler handler, JSONObject o) throws IOException {
if (room.isVisible() && "room".equals(o.optString("area"))) {
final String type = o.optString("type");
if ("wb".equals(type)) {
WbAction a = WbAction.of(o.getString(PARAM_ACTION));
wb.processWbAction(a, o.optJSONObject("data"), handler);
} else if ("room".equals(type)) {
sidebar.roomAction(handler, o);
} else if ("av".equals(type)) {
ExtendedClientProperties cp = WebSession.get().getExtendedProperties();
Client c = cp.setSettings(o.optJSONObject("settings")).update(getClient());
if (!avInited) {
avInited = true;
if (Room.Type.CONFERENCE == r.getType()) {
if (!activityAllowed(c, Client.Activity.AUDIO, c.getRoom())) {
c.allow(Room.Right.AUDIO);
}
if (!c.getRoom().isAudioOnly() && !activityAllowed(c, Client.Activity.VIDEO, c.getRoom())) {
c.allow(Room.Right.VIDEO);
}
streamProcessor.onToggleActivity(c, c.getRoom().isAudioOnly()
? Client.Activity.AUDIO
: Client.Activity.AUDIO_VIDEO);
}
}
c.getCamStreams().forEach(sd -> {
sd.setWidth(c.getWidth());
sd.setHeight(c.getHeight());
});
broadcast(c);
}
}
}
public Room getRoom() {
return r;
}
public Client getClient() {
return getMainPanel().getClient();
}
public String getUid() {
return getMainPanel().getUid();
}
public boolean screenShareAllowed() {
Client c = getClient();
return c.getScreenStream().isPresent() || streamProcessor.screenShareAllowed(c);
}
public RoomSidebar getSidebar() {
return sidebar;
}
public AbstractWbPanel getWb() {
return wb;
}
public String getPublishingUser() {
return null;
}
public boolean isInterview() {
return interview;
}
private void createWaitModerator(final boolean autoopen) {
waitModerator = new Alert("wait-moderator", new ResourceModel("wait-moderator.message"), new ResourceModel("wait-moderator.title")) {
private static final long serialVersionUID = 1L;
@Override
protected Component createMessage(String markupId, IModel<String> message) {
return super.createMessage(markupId, message).setEscapeModelStrings(false);
}
};
waitModerator.type(Alert.Type.Warning).setCloseButtonVisible(false);
waitModerator.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true).setVisible(autoopen);
}
@Override
protected String getCssClass() {
String clazz = "room " + r.getType().name();
if (r.isHidden(RoomElement.TOP_BAR)) {
clazz += " no-menu";
}
if (r.isHidden(RoomElement.ACTIVITIES)) {
clazz += " no-activities";
}
if (r.isHidden(RoomElement.CHAT)) {
clazz += " no-chat";
}
if (!r.isHidden(RoomElement.MICROPHONE_STATUS)) {
clazz += " mic-status";
}
return clazz;
}
private void showIdeaAlert(IPartialPageRequestHandler handler, String msg) {
showAlert(handler, "info", msg, "far fa-lightbulb");
}
private void showAlert(IPartialPageRequestHandler handler, String type, String msg, String icon) {
handler.appendJavaScript("OmUtil.alert('" + type + "', '<i class=\"" + icon + "\"></i>&nbsp;"
+ StringEscapeUtils.escapeEcmaScript(msg)
+ "', 10000)");
}
private CharSequence createAddClientJs(Client c) {
JSONArray arr = new JSONArray();
cm.streamByRoom(r.getId()).forEach(cl -> arr.put(cl.toJson(c.getUid().equals(cl.getUid()))));
return new StringBuilder()
.append("Room.addClient(")
.append(arr.toString(new NullStringer()))
.append(");");
}
private CharSequence sendClientsOnUpdate() {
Client c = getClient();
StringBuilder res = new StringBuilder();
if (r.isHidden(RoomElement.USER_COUNT)) {
if (c.hasRight(Room.Right.MODERATOR)) {
res.append(createAddClientJs(c));
} else {
res.append("Room.removeOthers();");
}
}
return res;
}
private boolean noModerators() {
return cm.streamByRoom(r.getId())
.filter(cl -> cl.hasRight(Room.Right.MODERATOR))
.findAny()
.isEmpty();
}
}