diff --git a/lib/BigBlueButton/API.php b/lib/BigBlueButton/API.php index 861d0fe..5a43328 100644 --- a/lib/BigBlueButton/API.php +++ b/lib/BigBlueButton/API.php @@ -211,4 +211,12 @@ class API { return $server->getApiVersion()->getVersion(); } + + public function isRunning(Room $room): bool { + $isMeetingRunningParams = new IsMeetingRunningParameters($room->getUid()); + + $response = $this->getServer()->isMeetingRunning($isMeetingRunningParams); + + return $response->success() && $response->isRunning(); + } } diff --git a/lib/Controller/JoinController.php b/lib/Controller/JoinController.php index b734f58..398baca 100644 --- a/lib/Controller/JoinController.php +++ b/lib/Controller/JoinController.php @@ -114,6 +114,13 @@ class JoinController extends Controller { return $response; } + if ($room->requireModerator && ($userId === null || !$this->permission->isModerator($room, $userId)) && !$this->api->isRunning($room)) { + return new TemplateResponse($this->appName, 'waiting', [ + 'room' => $room->name, + 'name' => $displayname, + ], 'guest'); + } + $creationDate = $this->api->createMeeting($room, $presentation); $joinUrl = $this->api->createJoinUrl($room, $creationDate, $displayname, $userId); diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index ef73cb3..ab2dfec 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -113,7 +113,8 @@ class RoomController extends Controller { int $maxParticipants, bool $record, string $access, - bool $everyoneIsModerator + bool $everyoneIsModerator, + bool $requireModerator ): DataResponse { $room = $this->service->find($id); @@ -136,8 +137,8 @@ class RoomController extends Controller { return new DataResponse('Access type not allowed.', Http::STATUS_BAD_REQUEST); } - return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $everyoneIsModerator, $access) { - return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator); + return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $requireModerator) { + return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $requireModerator); }); } diff --git a/lib/Db/Room.php b/lib/Db/Room.php index 9e1a590..c4321e4 100644 --- a/lib/Db/Room.php +++ b/lib/Db/Room.php @@ -18,6 +18,8 @@ use OCP\AppFramework\Db\Entity; * @method string getAccess() * @method string getPassword() * @method bool getEveryoneIsModerator() + * @method bool getRequireModerator() + * @method bool getEveryoneIsModerator() * @method void setUid(string $uid) * @method void setName(string $name) * @method void setAttendeePassword(string $pw) @@ -29,6 +31,7 @@ use OCP\AppFramework\Db\Entity; * @method void setAccess(string $access) * @method void setPassword(string $pw) * @method void setEveryoneIsModerator(bool $everyone) + * @method void setRequireModerator(bool $require) */ class Room extends Entity implements JsonSerializable { public const ACCESS_PUBLIC = 'public'; @@ -50,11 +53,13 @@ class Room extends Entity implements JsonSerializable { public $access = self::ACCESS_PUBLIC; public $password; public $everyoneIsModerator; + public $requireModerator = false; public function __construct() { $this->addType('maxParticipants', 'integer'); $this->addType('record', 'boolean'); $this->addType('everyoneIsModerator', 'boolean'); + $this->addType('requireModerator', 'boolean'); } public function jsonSerialize(): array { @@ -69,6 +74,7 @@ class Room extends Entity implements JsonSerializable { 'access' => $this->access, 'password' => $this->password, 'everyoneIsModerator' => boolval($this->everyoneIsModerator), + 'requireModerator' => boolval($this->requireModerator), ]; } } diff --git a/lib/Migration/Version000000Date20200829112301.php b/lib/Migration/Version000000Date20200829112301.php new file mode 100644 index 0000000..2a4eff4 --- /dev/null +++ b/lib/Migration/Version000000Date20200829112301.php @@ -0,0 +1,41 @@ +hasTable('bbb_rooms')) { + $table = $schema->getTable('bbb_rooms'); + + if (!$table->hasColumn('require_moderator')) { + $table->addColumn('require_moderator', 'boolean', [ + 'notnull' => true, + 'default' => false, + ]); + } + + return $schema; + } + + return null; + } +} diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index 827b4da..064839f 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -73,7 +73,7 @@ class RoomService { return $this->mapper->insert($room); } - public function update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator) { + public function update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $requireModerator) { try { $room = $this->mapper->find($id); @@ -87,6 +87,7 @@ class RoomService { $room->setRecord($record); $room->setAccess($access); $room->setEveryoneIsModerator($everyoneIsModerator); + $room->setRequireModerator($requireModerator); return $this->mapper->update($room); } catch (Exception $e) { diff --git a/templates/waiting.php b/templates/waiting.php new file mode 100644 index 0000000..e4134d2 --- /dev/null +++ b/templates/waiting.php @@ -0,0 +1,13 @@ + + +
+

+

t('Hello %s', $_['name'])); ?>

+ +

+
diff --git a/ts/Common/Api.ts b/ts/Common/Api.ts index e601d28..aab1f05 100644 --- a/ts/Common/Api.ts +++ b/ts/Common/Api.ts @@ -36,6 +36,7 @@ export interface Room { access: Access; password?: string; everyoneIsModerator: boolean; + requireModerator: boolean; } export interface RoomShare { diff --git a/ts/Manager/App.scss b/ts/Manager/App.scss index 6fa52bf..e2a3039 100644 --- a/ts/Manager/App.scss +++ b/ts/Manager/App.scss @@ -77,6 +77,10 @@ margin: 3em; } + p { + margin-bottom: 1em; + } + .icon { display: inline-block; opacity: 0; diff --git a/ts/Manager/EditRoomDialog.tsx b/ts/Manager/EditRoomDialog.tsx index 8b679bb..c8ddb85 100644 --- a/ts/Manager/EditRoomDialog.tsx +++ b/ts/Manager/EditRoomDialog.tsx @@ -12,6 +12,7 @@ const descriptions: { [key: string]: string } = { recording: t('bbb', 'If enabled, the moderator is able to start the recording.'), access: t('bbb', 'Public: Everyone knowing the link is able to join. Password: Guests have to provide a password. Waiting room: A moderator has to accept every guest before they can join. Internal: Only Nextcloud users can join.'), moderator: t('bbb', 'A moderator is able to manage all participants in a meeting including kicking, muting or selecting a presenter. Users with the role moderator are also able to close a meeting or change the default settings.'), + requireModerator: t('bbb', 'If enabled, normal users have to wait until a moderator is in the room.'), }; type Props = { @@ -127,7 +128,18 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('record', event.target.checked)} /> - {descriptions.recording} +

{descriptions.recording}

+ +
+
+ updateProperty('requireModerator', event.target.checked)} /> + +
+

{descriptions.requireModerator}

); diff --git a/ts/waiting.scss b/ts/waiting.scss new file mode 100644 index 0000000..37d6817 --- /dev/null +++ b/ts/waiting.scss @@ -0,0 +1,10 @@ +.bbb { + h2 { + font-size: 2em; + margin-bottom: 1em; + } + + h3 { + color: #ffffff; + } +} diff --git a/ts/waiting.ts b/ts/waiting.ts new file mode 100644 index 0000000..0fed10c --- /dev/null +++ b/ts/waiting.ts @@ -0,0 +1,14 @@ +import './waiting.scss'; + +$(() => { + let countdown = 30; + + const interval = window.setInterval(() => { + $('#bbb-waiting-text').text(t('bbb', 'This room is not open yet. We will try it again in {sec} seconds. Please wait.', {sec: (--countdown).toString()})); + + if (countdown === 0) { + window.location.reload(); + window.clearInterval(interval); + } + }, 1000); +}); diff --git a/webpack.common.js b/webpack.common.js index b907c19..e889a27 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -16,6 +16,9 @@ module.exports = { ], join: [ path.join(__dirname, 'ts', 'join.ts'), + ], + waiting: [ + path.join(__dirname, 'ts', 'waiting.ts'), ] }, output: {