diff --git a/appinfo/routes.php b/appinfo/routes.php index 533b426e4..26ff1deb7 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -5,9 +5,12 @@ declare(strict_types=1); * Calendar App * * @author Georg Ehrke - * @copyright 2018 Georg Ehrke * @author Thomas Müller + * @author Jonas Heinrich + * + * @copyright 2018 Georg Ehrke * @copyright 2016 Thomas Müller + * @copyright 2023 Jonas Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -54,6 +57,8 @@ return [ ['name' => 'contact#searchAttendee', 'url' => '/v1/autocompletion/attendee', 'verb' => 'POST'], ['name' => 'contact#searchLocation', 'url' => '/v1/autocompletion/location', 'verb' => 'POST'], ['name' => 'contact#searchPhoto', 'url' => '/v1/autocompletion/photo', 'verb' => 'POST'], + // Circles + ['name' => 'contact#getCircleMembers', 'url' => '/v1/circles/getmembers', 'verb' => 'GET'], // Settings ['name' => 'settings#setConfig', 'url' => '/v1/config/{key}', 'verb' => 'POST'], // Tools diff --git a/lib/Controller/ContactController.php b/lib/Controller/ContactController.php index 34fce6f51..c7b6d9acd 100644 --- a/lib/Controller/ContactController.php +++ b/lib/Controller/ContactController.php @@ -7,10 +7,12 @@ declare(strict_types=1); * @author Georg Ehrke * @author Jakob Röhrl * @author Christoph Wurst + * @author Jonas Heinrich * * @copyright 2019 Georg Ehrke * @copyright 2019 Jakob Röhrl * @copyright 2019 Christoph Wurst + * @copyright 2023 Jonas Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -28,11 +30,16 @@ declare(strict_types=1); */ namespace OCA\Calendar\Controller; +use OCA\Calendar\Service\ServiceException; +use OCA\Circles\Exceptions\CircleNotFoundException; +use OCP\App\IAppManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\QueryException; use OCP\Contacts\IManager; use OCP\IRequest; +use OCP\IUserManager; /** * Class ContactController @@ -43,6 +50,12 @@ class ContactController extends Controller { /** @var IManager */ private $contactsManager; + /** @var IAppManager */ + private $appManager; + + /** @var IUserManager */ + private $userManager; + /** * ContactController constructor. * @@ -52,9 +65,13 @@ class ContactController extends Controller { */ public function __construct(string $appName, IRequest $request, - IManager $contacts) { + IManager $contacts, + IAppManager $appManager, + IUserManager $userManager) { parent::__construct($appName, $request); $this->contactsManager = $contacts; + $this->appManager = $appManager; + $this->userManager = $userManager; } /** @@ -173,6 +190,66 @@ class ContactController extends Controller { return new JSONResponse($contacts); } + /** + * Query members of a circle by circleId + * + * @param string $circleId CircleId to query for members + * @return JSONResponse + * @throws Exception + * @throws \OCP\AppFramework\QueryException + * + * @NoAdminRequired + */ + public function getCircleMembers(string $circleId):JSONResponse { + if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) { + return new JSONResponse(); + } + if (!$this->contactsManager->isEnabled()) { + return new JSONResponse(); + } + + try { + $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($circleId, true); + } catch (QueryException $ex) { + return new JSONResponse(); + } catch (CircleNotFoundException $ex) { + return new JSONResponse(); + } + + if (!$circle) { + return new JSONResponse(); + } + + $circleMembers = $circle->getInheritedMembers(); + + foreach ($circleMembers as $circleMember) { + if ($circleMember->isLocal()) { + + $circleMemberUserId = $circleMember->getUserId(); + + $user = $this->userManager->get($circleMemberUserId); + + if ($user === null) { + throw new ServiceException('Could not find organizer'); + } + + $contacts[] = [ + 'commonName' => $circleMember->getDisplayName(), + 'calendarUserType' => 'INDIVIDUAL', + 'email' => $user->getEMailAddress(), + 'isUser' => true, + 'avatar' => $circleMemberUserId, + 'hasMultipleEMails' => false, + 'dropdownName' => $circleMember->getDisplayName(), + 'member' => 'mailto:circle+' . $circleId . '@' . $circleMember->getInstance(), + ]; + } + } + + return new JSONResponse($contacts); + } + + /** * Get a contact's photo based on their email-address * diff --git a/lib/Controller/ViewController.php b/lib/Controller/ViewController.php index f82e7c9df..afc14aba2 100644 --- a/lib/Controller/ViewController.php +++ b/lib/Controller/ViewController.php @@ -6,8 +6,10 @@ declare(strict_types=1); * * @author Georg Ehrke * @author Richard Steinmetz + * @author Jonas Heinrich * @copyright 2019 Georg Ehrke * @copyright Copyright (c) 2022 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/ + * @copyright 2023 Jonas Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -25,6 +27,7 @@ declare(strict_types=1); */ namespace OCA\Calendar\Controller; +use OC\App\CompareVersion; use OCA\Calendar\Service\Appointments\AppointmentConfigService; use OCP\App\IAppManager; use OCP\AppFramework\Controller; @@ -51,6 +54,9 @@ class ViewController extends Controller { /** @var IAppManager */ private $appManager; + /** @var CompareVersion */ + private $compareVersion; + /** @var string */ private $userId; @@ -62,6 +68,7 @@ class ViewController extends Controller { AppointmentConfigService $appointmentConfigService, IInitialState $initialStateService, IAppManager $appManager, + CompareVersion $compareVersion, ?string $userId, IAppData $appData) { parent::__construct($appName, $request); @@ -69,6 +76,7 @@ class ViewController extends Controller { $this->appointmentConfigService = $appointmentConfigService; $this->initialStateService = $initialStateService; $this->appManager = $appManager; + $this->compareVersion = $compareVersion; $this->userId = $userId; $this->appData = $appData; } @@ -117,6 +125,11 @@ class ViewController extends Controller { $talkApiVersion = version_compare($this->appManager->getAppVersion('spreed'), '12.0.0', '>=') ? 'v4' : 'v1'; $tasksEnabled = $this->appManager->isEnabledForUser('tasks'); + $circleVersion = $this->appManager->getAppVersion('circles'); + $isCirclesEnabled = $this->appManager->isEnabledForUser('circles') === true; + // if circles is not installed, we use 0.0.0 + $isCircleVersionCompatible = $this->compareVersion->isCompatible($circleVersion ? $circleVersion : '0.0.0', '22'); + $this->initialStateService->provideInitialState('app_version', $appVersion); $this->initialStateService->provideInitialState('event_limit', $eventLimit); $this->initialStateService->provideInitialState('first_run', $firstRun); @@ -138,6 +151,7 @@ class ViewController extends Controller { $this->initialStateService->provideInitialState('disable_appointments', $disableAppointments); $this->initialStateService->provideInitialState('can_subscribe_link', $canSubscribeLink); $this->initialStateService->provideInitialState('show_resources', $showResources); + $this->initialStateService->provideInitialState('isCirclesEnabled', $isCirclesEnabled && $isCircleVersionCompatible); return new TemplateResponse($this->appName, 'main'); } diff --git a/lib/Service/ServiceException.php b/lib/Service/ServiceException.php new file mode 100644 index 000000000..2bafe90f4 --- /dev/null +++ b/lib/Service/ServiceException.php @@ -0,0 +1,8 @@ + + + + + @@ -48,6 +52,9 @@ + + + diff --git a/src/components/Editor/Invitees/InviteesList.vue b/src/components/Editor/Invitees/InviteesList.vue index a40f00bc8..13393e2da 100644 --- a/src/components/Editor/Invitees/InviteesList.vue +++ b/src/components/Editor/Invitees/InviteesList.vue @@ -27,6 +27,7 @@
{ - return attendee.attendeeProperty.userType === 'GROUP' + return this.invitees.filter(attendee => { + if (attendee.attendeeProperty.userType === 'GROUP') { + attendee.members = this.invitees.filter(invitee => { + return invitee.attendeeProperty.member + && invitee.attendeeProperty.member.includes(attendee.uri) + && attendee.attendeeProperty.userType === 'GROUP' + }) + return attendee.members.length > 0 + } + return false }) }, inviteesWithoutOrganizer() { @@ -146,36 +155,20 @@ export default { return this.invitees .filter(attendee => { // Filter attendees which are part of an invited group - let isMemberOfGroup = false - if (attendee.attendeeProperty.member) { - isMemberOfGroup = this.groups.some(function(group) { - return attendee.attendeeProperty.member.includes(group.uri) - && attendee.attendeeProperty.userType === 'INDIVIDUAL' - }) + if (this.groups.some(function(group) { + return attendee.attendeeProperty.member + && attendee.attendeeProperty.member.includes(group.uri) + && attendee.attendeeProperty.userType === 'INDIVIDUAL' + })) { + return false } - // Add attendee to group member list - if (isMemberOfGroup) { - this.groups.forEach(group => { - if (attendee.member.includes(group.uri)) { - if (!group.members) { - group.members = [] - } - group.members.push(attendee) - } - }) + // Filter empty groups + if (attendee.attendeeProperty.userType === 'GROUP') { + return attendee.members.length > 0 } - // Check if attendee is an empty group - let isEmptyGroup = attendee.attendeeProperty.userType === 'GROUP' - this.invitees.forEach(invitee => { - if (invitee.member && invitee.member.includes(attendee.uri)) { - isEmptyGroup = false - } - }) - return attendee.uri !== this.calendarObjectInstance.organizer.uri - && !isMemberOfGroup && !isEmptyGroup }) }, isOrganizer() { @@ -230,7 +223,7 @@ export default { }, }, methods: { - addAttendee({ commonName, email, calendarUserType, language, timezoneId }) { + addAttendee({ commonName, email, calendarUserType, language, timezoneId, member }) { this.$store.commit('addAttendee', { calendarObjectInstance: this.calendarObjectInstance, commonName, @@ -242,6 +235,7 @@ export default { language, timezoneId, organizer: this.$store.getters.getCurrentUserPrincipal, + member, }) }, removeAttendee(attendee) { diff --git a/src/components/Editor/Invitees/InviteesListSearch.vue b/src/components/Editor/Invitees/InviteesListSearch.vue index 8858229a5..ad9acfcb5 100644 --- a/src/components/Editor/Invitees/InviteesListSearch.vue +++ b/src/components/Editor/Invitees/InviteesListSearch.vue @@ -1,8 +1,10 @@