diff --git a/appinfo/routes.php b/appinfo/routes.php index 2b70b37..269123b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -14,6 +14,7 @@ return [ ['name' => 'server#check', 'url' => '/server/check', 'verb' => 'POST'], ['name' => 'server#version', 'url' => '/server/version', 'verb' => 'GET'], ['name' => 'server#delete_record', 'url' => '/server/record/{recordId}', 'verb' => 'DELETE'], + ['name' => 'server#publish_record', 'url' => '/server/record/{recordId}/publish', 'verb' => 'POST'], ['name' => 'join#index', 'url' => '/b/{token}/{moderatorToken}', 'verb' => 'GET', 'defaults' => ['moderatorToken' => '']], ['name' => 'restriction#user', 'url' => '/restrictions/user', 'verb' => 'GET'], ['name' => 'hook#meetingEnded', 'url' => '/hook/ended/{token}/{mac}', 'verb' => 'GET'], diff --git a/lib/BigBlueButton/API.php b/lib/BigBlueButton/API.php index 2176ca3..4912f4c 100644 --- a/lib/BigBlueButton/API.php +++ b/lib/BigBlueButton/API.php @@ -10,6 +10,7 @@ use BigBlueButton\Parameters\GetRecordingsParameters; use BigBlueButton\Parameters\InsertDocumentParameters; use BigBlueButton\Parameters\IsMeetingRunningParameters; use BigBlueButton\Parameters\JoinMeetingParameters; +use BigBlueButton\Parameters\PublishRecordingsParameters; use OCA\BigBlueButton\AppInfo\Application; use OCA\BigBlueButton\AvatarRepository; use OCA\BigBlueButton\Crypto; @@ -262,6 +263,14 @@ class API { return $response->isDeleted(); } + public function publishRecording(string $recordingId, bool $published): bool { + $publishParams = new PublishRecordingsParameters($recordingId, $published); + + $response = $this->getServer()->publishRecordings($publishParams); + + return $response->isPublished(); + } + /** * @return (array|bool|int|string)[] * diff --git a/lib/Controller/ServerController.php b/lib/Controller/ServerController.php index fd1eaf5..476cd0f 100644 --- a/lib/Controller/ServerController.php +++ b/lib/Controller/ServerController.php @@ -88,12 +88,18 @@ class ServerController extends Controller { return new DataResponse([], Http::STATUS_NOT_FOUND); } - if (!$this->permission->isAdmin($room, $this->userId)) { + if (!$this->permission->isUser($room, $this->userId)) { return new DataResponse([], Http::STATUS_FORBIDDEN); } $recordings = $this->server->getRecordings($room); + if (!$this->permission->isAdmin($room, $this->userId)) { + $recordings = array_values(array_filter($recordings, function ($recording) { + return $recording['published']; + })); + } + return new DataResponse($recordings); } @@ -118,6 +124,27 @@ class ServerController extends Controller { return new DataResponse($success); } + /** + * @NoAdminRequired + */ + public function publishRecord(string $recordId, bool $published): DataResponse { + $record = $this->server->getRecording($recordId); + + $room = $this->service->findByUid($record['meetingId']); + + if ($room === null) { + return new DataResponse(false, Http::STATUS_NOT_FOUND); + } + + if (!$this->permission->isAdmin($room, $this->userId)) { + return new DataResponse(false, Http::STATUS_FORBIDDEN); + } + + $success = $this->server->publishRecording($recordId, $published); + + return new DataResponse($success); + } + public function check(?string $url, ?string $secret): DataResponse { if ($url === null || empty($url) || $secret === null || empty($secret)) { return new DataResponse(false); diff --git a/lib/Db/Room.php b/lib/Db/Room.php index 7fb2816..1ab048e 100644 --- a/lib/Db/Room.php +++ b/lib/Db/Room.php @@ -74,6 +74,7 @@ class Room extends Entity implements JsonSerializable { public $cleanLayout; public $joinMuted; public $running; + public $permission; public function __construct() { $this->addType('maxParticipants', 'integer'); @@ -86,6 +87,7 @@ class Room extends Entity implements JsonSerializable { $this->addType('cleanLayout', 'boolean'); $this->addType('joinMuted', 'boolean'); $this->addType('running', 'boolean'); + $this->addType('permission', 'integer'); } public function jsonSerialize(): array { @@ -102,6 +104,7 @@ class Room extends Entity implements JsonSerializable { 'everyoneIsModerator' => boolval($this->everyoneIsModerator), 'requireModerator' => boolval($this->requireModerator), 'shared' => boolval($this->shared), + 'permission' => $this->permission, 'moderatorToken' => $this->moderatorToken, 'listenOnly' => boolval($this->listenOnly), 'mediaCheck' => boolval($this->mediaCheck), diff --git a/lib/Db/RoomMapper.php b/lib/Db/RoomMapper.php index a1a9c3c..8567ef3 100644 --- a/lib/Db/RoomMapper.php +++ b/lib/Db/RoomMapper.php @@ -12,6 +12,16 @@ class RoomMapper extends QBMapper { parent::__construct($db, 'bbb_rooms', Room::class); } + private function joinShares(IQueryBuilder $qb): IQueryBuilder { + $qb->select('r.*') + ->from($this->tableName, 'r') + ->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id')) + ->addSelect($qb->createFunction('count(case when `s`.`permission` IN ('. + RoomShare::PERMISSION_ADMIN.','.RoomShare::PERMISSION_MODERATOR.','.RoomShare::PERMISSION_USER + .') then 1 else null end) as shared')); + return $qb; + } + /** * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws DoesNotExistException @@ -19,10 +29,7 @@ class RoomMapper extends QBMapper { public function find(int $id): Room { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $qb->select('r.*') - ->from($this->tableName, 'r') - ->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id')) - ->addSelect($qb->createFunction('count(case when `s`.`permission` = 0 then 1 else null end) as shared')) + $this->joinShares($qb) ->where($qb->expr()->eq('r.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))) ->groupBy('r.id'); ; @@ -38,10 +45,7 @@ class RoomMapper extends QBMapper { public function findByUid(string $uid): Room { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $qb->select('r.*') - ->from($this->tableName, 'r') - ->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id')) - ->addSelect($qb->createFunction('count(case when `s`.`permission` = 0 then 1 else null end) as shared')) + $this->joinShares($qb) ->where($qb->expr()->eq('r.uid', $qb->createNamedParameter($uid))) ->groupBy('r.id'); ; @@ -70,25 +74,20 @@ class RoomMapper extends QBMapper { public function findAll(string $userId, array $groupIds, array $circleIds): array { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $qb->select('r.*') - ->from($this->tableName, 'r') - ->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id')) - ->addSelect($qb->createFunction('count(case when `s`.`permission` = 0 then 1 else null end) as shared')) + $this->joinShares($qb) + ->addSelect($qb->createFunction('min(case when '.$qb->expr()->eq('r.user_id', $qb->createNamedParameter($userId)).' then '.RoomShare::PERMISSION_ADMIN.' else `s`.`permission` end) as permission')) ->where( $qb->expr()->orX( $qb->expr()->eq('r.user_id', $qb->createNamedParameter($userId)), $qb->expr()->andX( - $qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)), $qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_USER, IQueryBuilder::PARAM_INT)), $qb->expr()->eq('s.share_with', $qb->createNamedParameter($userId)) ), $qb->expr()->andX( - $qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)), $qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_GROUP, IQueryBuilder::PARAM_INT)), $qb->expr()->in('s.share_with', $qb->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY)) ), $qb->expr()->andX( - $qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)), $qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)), $qb->expr()->in('s.share_with', $qb->createNamedParameter($circleIds, IQueryBuilder::PARAM_STR_ARRAY)) ) @@ -99,7 +98,7 @@ class RoomMapper extends QBMapper { /** @var array */ return $this->findEntities($qb); } - + /** * @return array */ diff --git a/ts/Common/Api.ts b/ts/Common/Api.ts index 6a2c278..e31c5b5 100644 --- a/ts/Common/Api.ts +++ b/ts/Common/Api.ts @@ -39,6 +39,7 @@ export interface Room { everyoneIsModerator: boolean; requireModerator: boolean; shared: boolean; + permission: Permission; moderatorToken: string; listenOnly: boolean, mediaCheck: boolean, @@ -200,6 +201,14 @@ class Api { return response.data; } + public async publishRecording(id: string, publish: boolean,) { + const response = await axios.post(this.getUrl(`server/record/${id}/publish`), { + published: publish, + }); + + return response.data; + } + public async storeRecording(recording: Recording, path: string) { const startDate = new Date(recording.startTime); const filename = `${encodeURIComponent(recording.name + ' ' + startDate.toISOString())}.url`; diff --git a/ts/Common/Translation.ts b/ts/Common/Translation.ts index d80eb5d..8d78cf7 100644 --- a/ts/Common/Translation.ts +++ b/ts/Common/Translation.ts @@ -1,4 +1,4 @@ -import { Access } from './Api'; +import { Access, Permission } from './Api'; export const AccessOptions = { [Access.Public]: t('bbb', 'Public'), @@ -8,3 +8,9 @@ export const AccessOptions = { [Access.Internal]: t('bbb', 'Internal'), [Access.InternalRestricted]: t('bbb', 'Internal restricted'), }; + +export const PermissionsOptions = { + [Permission.Admin]: t('bbb', 'admin'), + [Permission.Moderator]: t('bbb', 'moderator'), + [Permission.User]: t('bbb', 'user'), +}; diff --git a/ts/Manager/App.scss b/ts/Manager/App.scss index 1a0458c..96ff82d 100644 --- a/ts/Manager/App.scss +++ b/ts/Manager/App.scss @@ -282,6 +282,10 @@ pre { } } +.bbb-simple-menu { + min-width: auto; +} + .bbb-input-container { display: flex; } diff --git a/ts/Manager/EditRoomDialog.tsx b/ts/Manager/EditRoomDialog.tsx index a0b1bde..d54dbd9 100644 --- a/ts/Manager/EditRoomDialog.tsx +++ b/ts/Manager/EditRoomDialog.tsx @@ -123,17 +123,18 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op updateProperty('access', value); })} - {room.access === Access.InternalRestricted &&
- - {descriptions.internalRestrictedShareWith} -
} -
-