diff --git a/img/circles.svg b/img/circles.svg new file mode 100644 index 0000000..caf4559 --- /dev/null +++ b/img/circles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/CircleHelper.php b/lib/CircleHelper.php new file mode 100644 index 0000000..1a56b10 --- /dev/null +++ b/lib/CircleHelper.php @@ -0,0 +1,67 @@ +app = $app; + $this->appManager = $appManager; + } + + public function isInCircle(string $userId, string $circleId): bool { + return \in_array($circleId, $this->getCircleIds($userId)); + } + + public function getCircleIds(string $userId): array { + if (!\array_key_exists($userId, $this->cache)) { + $this->cache[$userId] = []; + + $api = $this->getCircleAPI(); + + if ($api !== false) { + $circles = $api->listCircles(\OCA\Circles\Api\v1\Circles::CIRCLES_ALL, '', \OCA\Circles\Api\v1\Circles::LEVEL_MEMBER); + + foreach ($circles as $circle) { + $this->cache[$userId][] = $circle->getUniqueId(); + } + } + } + + return $this->cache[$userId]; + } + + public function getCircleAPI() { + if ($this->api === null) { + if ($this->appManager->isEnabledForUser('circles') && class_exists('\OCA\Circles\Api\v1\Circles')) { + $container = $this->app->getContainer(); + $this->api = $container->query(\OCA\Circles\Api\v1\Circles::class); + } else { + $this->api = false; + } + } + + return $this->api; + } +} diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index c429421..ef73cb3 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -5,6 +5,7 @@ namespace OCA\BigBlueButton\Controller; use OCA\BigBlueButton\Service\RoomService; use OCA\BigBlueButton\Permission; use OCA\BigBlueButton\Db\Room; +use OCA\BigBlueButton\CircleHelper; use OCP\IRequest; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -25,6 +26,9 @@ class RoomController extends Controller { /** @var Permission */ private $permission; + /** @var CircleHelper */ + private $circleHelper; + /** @var string */ private $userId; @@ -37,6 +41,7 @@ class RoomController extends Controller { IUserManager $userManager, IGroupManager $groupManager, Permission $permission, + CircleHelper $circleHelper, $userId ) { parent::__construct($appName, $request); @@ -44,6 +49,7 @@ class RoomController extends Controller { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->permission = $permission; + $this->circleHelper = $circleHelper; $this->userId = $userId; } @@ -53,8 +59,9 @@ class RoomController extends Controller { public function index(): DataResponse { $user = $this->userManager->get($this->userId); $groupIds = $this->groupManager->getUserGroupIds($user); + $circleIds = $this->circleHelper->getCircleIds($this->userId); - return new DataResponse($this->service->findAll($this->userId, $groupIds)); + return new DataResponse($this->service->findAll($this->userId, $groupIds, $circleIds)); } /** diff --git a/lib/Controller/RoomShareController.php b/lib/Controller/RoomShareController.php index a049781..db69397 100644 --- a/lib/Controller/RoomShareController.php +++ b/lib/Controller/RoomShareController.php @@ -5,6 +5,7 @@ namespace OCA\BigBlueButton\Controller; use OCA\BigBlueButton\Db\RoomShare; use OCA\BigBlueButton\Service\RoomService; use OCA\BigBlueButton\Service\RoomShareNotFound; +use OCA\BigBlueButton\CircleHelper; use OCP\IRequest; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -26,6 +27,9 @@ class RoomShareController extends Controller { /** @var RoomService */ private $roomService; + /** @var CircleHelper */ + private $circleHelper; + use Errors; public function __construct( @@ -34,12 +38,14 @@ class RoomShareController extends Controller { RoomShareService $service, IUserManager $userManager, RoomService $roomService, + CircleHelper $circleHelper, $userId ) { parent::__construct($appName, $request); $this->service = $service; $this->userManager = $userManager; $this->roomService = $roomService; + $this->circleHelper = $circleHelper; $this->userId = $userId; } @@ -58,17 +64,38 @@ class RoomShareController extends Controller { } $roomShares = $this->service->findAll($roomId); + $shares = []; + + $circleAPI = $this->circleHelper->getCircleAPI(); /** @var RoomShare $roomShare */ foreach ($roomShares as $roomShare) { - $shareWithUser = $this->userManager->get($roomShare->getShareWith()); + if ($roomShare->getShareType() === RoomShare::SHARE_TYPE_USER) { + $shareWithUser = $this->userManager->get($roomShare->getShareWith()); + + if ($shareWithUser === null) { + continue; + } - if ($shareWithUser !== null) { $roomShare->setShareWithDisplayName($shareWithUser->getDisplayName()); + } elseif ($roomShare->getShareType() === RoomShare::SHARE_TYPE_CIRCLE) { + if ($circleAPI === false) { + continue; + } + + $circle = $circleAPI->detailsCircle($roomShare->getShareWith()); + + if ($circle === null) { + continue; + } + + $roomShare->setShareWithDisplayName($circle->getName()); } + + $shares[] = $roomShare; } - return new DataResponse($roomShares); + return new DataResponse($shares); } /** diff --git a/lib/Db/RoomMapper.php b/lib/Db/RoomMapper.php index a449d36..e2f6551 100644 --- a/lib/Db/RoomMapper.php +++ b/lib/Db/RoomMapper.php @@ -45,7 +45,7 @@ class RoomMapper extends QBMapper { /** * @return array */ - public function findAll(string $userId, array $groupIds): array { + public function findAll(string $userId, array $groupIds, array $circleIds): array { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->select('r.*') @@ -63,6 +63,11 @@ class RoomMapper extends QBMapper { $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)) ) ) ) diff --git a/lib/Db/RoomShare.php b/lib/Db/RoomShare.php index c6585eb..a9b053c 100644 --- a/lib/Db/RoomShare.php +++ b/lib/Db/RoomShare.php @@ -5,6 +5,7 @@ namespace OCA\BigBlueButton\Db; use JsonSerializable; use OCP\AppFramework\Db\Entity; +use OCP\Share\IShare; /** * @method int getRoomId() @@ -24,8 +25,9 @@ class RoomShare extends Entity implements JsonSerializable { public const PERMISSION_MODERATOR = 1; public const PERMISSION_USER = 2; - public const SHARE_TYPE_USER = 0; - public const SHARE_TYPE_GROUP = 1; + public const SHARE_TYPE_USER = IShare::TYPE_USER; + public const SHARE_TYPE_GROUP = IShare::TYPE_GROUP; + public const SHARE_TYPE_CIRCLE = IShare::TYPE_CIRCLE; protected $roomId; protected $shareType; diff --git a/lib/Permission.php b/lib/Permission.php index 7091878..475b140 100644 --- a/lib/Permission.php +++ b/lib/Permission.php @@ -29,18 +29,23 @@ class Permission { /** @var RoomShareService */ private $roomShareService; + /** @var CircleHelper */ + private $circleHelper; + public function __construct( IUserManager $userManager, IGroupManager $groupManager, RoomService $roomService, RestrictionService $restrictionService, - RoomShareService $roomShareService + RoomShareService $roomShareService, + CircleHelper $circleHelper ) { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->roomService = $roomService; $this->restrictionService = $restrictionService; $this->roomShareService = $roomShareService; + $this->circleHelper = $circleHelper; } public function getRestriction(string $uid): Restriction { @@ -51,7 +56,7 @@ class Permission { } public function isAllowedToCreateRoom(string $uid) { - $numberOfCreatedRooms = count($this->roomService->findAll($uid, [])); + $numberOfCreatedRooms = count($this->roomService->findAll($uid, [], [])); $restriction = $this->getRestriction($uid); return $restriction->getMaxRooms() < 0 || $restriction->getMaxRooms() > $numberOfCreatedRooms; @@ -104,6 +109,10 @@ class Permission { if ($this->groupManager->isInGroup($uid, $share->getShareWith())) { return true; } + } elseif ($share->getShareType() === RoomShare::SHARE_TYPE_CIRCLE) { + if ($this->circleHelper->isInCircle($uid, $share->getShareWith())) { + return true; + } } } diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index e1dbf9c..827b4da 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -19,8 +19,8 @@ class RoomService { $this->mapper = $mapper; } - public function findAll(string $userId, array $groupIds): array { - return $this->mapper->findAll($userId, $groupIds); + public function findAll(string $userId, array $groupIds, array $circleIds): array { + return $this->mapper->findAll($userId, $groupIds, $circleIds); } private function handleException(Exception $e): void { diff --git a/templates/manager.php b/templates/manager.php index 28f0879..994c65d 100644 --- a/templates/manager.php +++ b/templates/manager.php @@ -8,4 +8,4 @@ script('bbb', 'manager');
- \ No newline at end of file + diff --git a/tests/Integration/Db/RoomMapperTest.php b/tests/Integration/Db/RoomMapperTest.php index 80fd508..0a60381 100644 --- a/tests/Integration/Db/RoomMapperTest.php +++ b/tests/Integration/Db/RoomMapperTest.php @@ -80,18 +80,20 @@ class RoomMapperTest extends TestCase { $foreignRoom2 = $this->insert($this->getRandomString(), $this->getRandomString()); $foreignRoom3 = $this->insert($this->getRandomString(), $this->getRandomString()); - $this->assertCount(1, $this->mapper->findAll($this->userId, [])); + $this->assertCount(1, $this->mapper->findAll($this->userId, [], [])); $shares = []; $shares[] = $this->insertShare($foreignRoom1->getId(), RoomShare::SHARE_TYPE_USER, $this->userId); $shares[] = $this->insertShare($foreignRoom1->getId(), RoomShare::SHARE_TYPE_GROUP, 'foo bar'); $shares[] = $this->insertShare($foreignRoom2->getId(), RoomShare::SHARE_TYPE_GROUP, 'foo bar'); $shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_USER, $this->getRandomString()); + $shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_CIRCLE, 'c1'); + $shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_CIRCLE, 'c2'); $shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_USER, $this->userId, RoomShare::PERMISSION_MODERATOR); $shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_GROUP, 'foo bar', RoomShare::PERMISSION_USER); - $rooms = $this->mapper->findAll($this->userId, ['foo bar']); - $this->assertCount(3, $rooms); + $rooms = $this->mapper->findAll($this->userId, ['foo bar'], ['c1']); + $this->assertCount(4, $rooms); $this->mapper->delete($room); $this->mapper->delete($foreignRoom1); diff --git a/tests/Unit/Controller/RoomShareControllerTest.php b/tests/Unit/Controller/RoomShareControllerTest.php index 314d0f3..e91243e 100644 --- a/tests/Unit/Controller/RoomShareControllerTest.php +++ b/tests/Unit/Controller/RoomShareControllerTest.php @@ -9,6 +9,7 @@ use OCA\BigBlueButton\Controller\RoomShareController; use OCA\BigBlueButton\Db\Room; use OCA\BigBlueButton\Db\RoomShare; use OCA\BigBlueButton\Service\RoomShareService; +use OCA\BigBlueButton\CircleHelper; use OCP\AppFramework\Http; use OCP\IUserManager; @@ -16,6 +17,7 @@ class RoomShareControllerTest extends TestCase { private $request; private $service; private $roomService; + private $circleHelper; private $userManager; private $controller; @@ -28,6 +30,7 @@ class RoomShareControllerTest extends TestCase { $this->service = $this->createMock(RoomShareService::class); $this->userManager = $this->createMock(IUserManager::class); $this->roomService = $this->createMock(RoomService::class); + $this->circleHelper = $this->createMock(CircleHelper::class); $this->controller = new RoomShareController( 'bbb', @@ -35,6 +38,7 @@ class RoomShareControllerTest extends TestCase { $this->service, $this->userManager, $this->roomService, + $this->circleHelper, $this->userId ); } diff --git a/tests/Unit/PermissionTest.php b/tests/Unit/PermissionTest.php index 1e0fb3d..288fafe 100644 --- a/tests/Unit/PermissionTest.php +++ b/tests/Unit/PermissionTest.php @@ -9,6 +9,7 @@ use OCA\BigBlueButton\Permission; use OCA\BigBlueButton\Service\RoomService; use OCA\BigBlueButton\Service\RoomShareService; use OCA\BigBlueButton\Service\RestrictionService; +use OCA\BigBlueButton\CircleHelper; use OCP\IUserManager; use OCP\IGroupManager; use OCP\IUser; @@ -34,6 +35,9 @@ class PermissionTest extends TestCase { /** @var RestrictionService|MockObject */ private $restrictionService; + /** @var CircleHelper|MockObject */ + private $circleHelper; + public function setUp(): void { parent::setUp(); @@ -52,12 +56,16 @@ class PermissionTest extends TestCase { /** @var RoomShareService|MockObject */ $this->roomShareService = $this->createMock(RoomShareService::class); + /** @var CircleHelper|MockObject */ + $this->circleHelper = $this->createMock(CircleHelper::class); + $this->permission = new Permission( $this->userManager, $this->groupManager, $this->roomService, $this->restrictionService, - $this->roomShareService + $this->roomShareService, + $this->circleHelper ); } @@ -96,12 +104,13 @@ class PermissionTest extends TestCase { $room = $this->createRoom(1, 'foo'); $this->roomShareService - ->expects($this->exactly(4)) + ->expects($this->exactly(5)) ->method('findAll') ->will($this->returnValueMap([ [1, [ $this->createRoomShare(RoomShare::SHARE_TYPE_USER, 'user', RoomShare::PERMISSION_ADMIN), $this->createRoomShare(RoomShare::SHARE_TYPE_GROUP, 'group', RoomShare::PERMISSION_MODERATOR), + $this->createRoomShare(RoomShare::SHARE_TYPE_CIRCLE, 'circle', RoomShare::PERMISSION_USER), ]], [2, []], ])); @@ -113,6 +122,13 @@ class PermissionTest extends TestCase { ['group_user', 'group', true], ])); + $this->circleHelper + ->method('isInCircle') + ->will($this->returnValueMap([ + ['bar', 'circle', false], + ['circle_user', 'circle', true], + ])); + $this->assertFalse($this->permission->isUser($room, null), 'Test guest user'); $this->assertFalse($this->permission->isUser($room, 'bar'), 'Test no matching share'); $this->assertFalse($this->permission->isUser($this->createRoom(2, 'foo'), 'bar'), 'Test empty shares'); @@ -120,6 +136,7 @@ class PermissionTest extends TestCase { $this->assertTrue($this->permission->isUser($room, 'foo'), 'Test room owner'); $this->assertTrue($this->permission->isUser($room, 'user')); $this->assertTrue($this->permission->isUser($room, 'group_user')); + $this->assertTrue($this->permission->isUser($room, 'circle_user')); } private function createRoom(int $id, string $userId): Room { diff --git a/ts/Common/Api.ts b/ts/Common/Api.ts index f67f8ea..e601d28 100644 --- a/ts/Common/Api.ts +++ b/ts/Common/Api.ts @@ -1,6 +1,10 @@ import axios from '@nextcloud/axios'; -export enum ShareType { User, Group }; +export enum ShareType { + User = OC.Share.SHARE_TYPE_USER, + Group = OC.Share.SHARE_TYPE_GROUP, + Circle = OC.Share.SHARE_TYPE_CIRCLE +}; export enum Permission { Admin, Moderator, User }; @@ -67,6 +71,7 @@ export interface ShareWithOption { export interface ShareWith { users: ShareWithOption[]; groups: ShareWithOption[]; + circles: ShareWithOption[]; } class Api { @@ -228,6 +233,7 @@ class Api { return { users: response.data.ocs.data.exact.users, groups: response.data.ocs.data.exact.groups, + circles: response.data.ocs.data.exact.circles || [], }; } @@ -248,6 +254,7 @@ class Api { return { users: [...data.users, ...data.exact.users], groups: [...data.groups, ...data.exact.groups], + circles: [...(data.circles || []), ...(data.exact.circles || [])], }; } } diff --git a/ts/Common/ShareSelection.tsx b/ts/Common/ShareSelection.tsx index 61ad29d..8048d53 100644 --- a/ts/Common/ShareSelection.tsx +++ b/ts/Common/ShareSelection.tsx @@ -8,6 +8,7 @@ type Props = { excluded?: { groupIds?: string[]; userIds?: string[]; + circleIds?: string[]; }; placeholder?: string; } @@ -23,6 +24,7 @@ const ShareSelection: React.FC = (props) => { const excluded = { userIds: props.excluded?.userIds || [], groupIds: props.excluded?.groupIds || [], + circleIds: props.excluded?.circleIds || [], }; const placeholder = props.placeholder || t('bbb', 'Name, group, ...'); @@ -59,6 +61,7 @@ const ShareSelection: React.FC = (props) => { const results = options ? [ ...options.users.filter(user => !excluded.userIds.includes(user.value.shareWith)), ...options.groups.filter(group => !excluded.groupIds.includes(group.value.shareWith)), + ...options.circles.filter(circle => !excluded.circleIds.includes(circle.value.shareWith)), ] : []; const renderOption = (option: ShareWithOption) => { diff --git a/ts/Manager/ShareWith.scss b/ts/Manager/ShareWith.scss index f1214a4..c0f11a4 100644 --- a/ts/Manager/ShareWith.scss +++ b/ts/Manager/ShareWith.scss @@ -1,3 +1,8 @@ +.icon-circle-white { + background-size: 50%; + background-image: url('../../img/circles.svg'); +} + .bbb-shareWith { margin: 1em 0; @@ -15,7 +20,8 @@ width: 32px; overflow: hidden; - .icon-group-white { + .icon-group-white, + .icon-circle-white { display: block; height: 100%; width: 100%; diff --git a/ts/Manager/ShareWith.tsx b/ts/Manager/ShareWith.tsx index a55e373..d135123 100644 --- a/ts/Manager/ShareWith.tsx +++ b/ts/Manager/ShareWith.tsx @@ -18,6 +18,7 @@ const ShareWith: React.FC = ({ room, permission, shares: allShares, setSh const sharedUserIds = shares ? shares.filter(share => share.shareType === ShareType.User).map(share => share.shareWith) : []; const sharedGroupIds = shares ? shares.filter(share => share.shareType === ShareType.Group).map(share => share.shareWith) : []; + const sharedCircleIds = shares ? shares.filter(share => share.shareType === ShareType.Circle).map(share => share.shareWith) : []; async function addRoomShare(shareWith: string, shareType: number, displayName: string, permission: Permission) { const roomShare = await api.createRoomShare(room.id, shareType, shareWith, permission); @@ -80,6 +81,7 @@ const ShareWith: React.FC = ({ room, permission, shares: allShares, setSh
{avatarUrl && {`Avatar} {share.shareType === ShareType.Group && } + {share.shareType === ShareType.Circle && }
{displayName} @@ -115,7 +117,8 @@ const ShareWith: React.FC = ({ room, permission, shares: allShares, setSh {isOwner ? addRoomShare(shareOption.value.shareWith, shareOption.value.shareType, shareOption.label, permission)} - excluded={{userIds: sharedUserIds, groupIds: sharedGroupIds}}/> : + excluded={{userIds: sharedUserIds, groupIds: sharedGroupIds, circleIds: sharedCircleIds}} + shareType={[ShareType.User, ShareType.Group, ShareType.Circle]}/> : {t('bbb', 'You are not allowed to change this option, because this room is shared with you.')} diff --git a/ts/Nextcloud.d.ts b/ts/Nextcloud.d.ts index 7378431..0f812e0 100644 --- a/ts/Nextcloud.d.ts +++ b/ts/Nextcloud.d.ts @@ -27,6 +27,7 @@ declare namespace OC { const SHARE_TYPE_USER = 0; const SHARE_TYPE_GROUP = 1; const SHARE_TYPE_LINK = 3; + const SHARE_TYPE_CIRCLE = 7; } interface Plugin {