From 109a7d62be105d173456ce06e8c0058359ee7b8a Mon Sep 17 00:00:00 2001 From: Tortue Torche Date: Wed, 1 Apr 2020 17:04:58 +0200 Subject: [PATCH] Add `group_backend` option to mirroring the circle as a group The main goal is to have better integrations with Nextcloud applications and clients. The group name is automatically suffixed by ` Circle` And you can customize the prefix and/or suffix group name with the `group_backend_name_prefix` and `group_backend_name_suffix` options You should also hide these mirrored circles in Nextcloud applications and clients, because their equivalent group will be used instead, via the `allow_listed_circles` option set to `0`. Fix https://github.com/nextcloud/circles/issues/363 Signed-off-by: Tortue Torche --- README.md | 20 + lib/AppInfo/Application.php | 108 ++++- lib/Db/CirclesRequest.php | 33 +- lib/Db/CirclesRequestBuilder.php | 3 +- lib/Db/MembersRequest.php | 9 +- .../Version0017Date20200221173726.php | 81 ++++ lib/Model/BaseCircle.php | 19 + lib/Model/Circle.php | 3 +- lib/Service/ConfigService.php | 77 ++- lib/Service/GroupsBackendService.php | 442 ++++++++++++++++++ lib/Service/GroupsService.php | 2 +- lib/Service/MembersService.php | 29 +- 12 files changed, 789 insertions(+), 37 deletions(-) create mode 100644 lib/Migration/Version0017Date20200221173726.php create mode 100644 lib/Service/GroupsBackendService.php diff --git a/README.md b/README.md index acb4602fc..aa6a7abeb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,26 @@ In non-SSL environments (like on development setups) it is necessary to set two `./occ config:app:set circles --value 1 local_is_non_ssl` +## Allow mirroring circles as groups + +```bash +./occ maintenance:mode --on + +./occ config:app:set circles --value 1 group_backend # Mirroring circles as groups +./occ config:app:set circles --value 0 allow_listed_circles # Hide circles in shared list, useful with the 'group_backend' option + +# ./occ config:app:set circles --value "🌀 " group_backend_name_prefix # You can customize group name prefix +# ./occ config:app:set circles --value " " group_backend_name_suffix # Remove default group name suffix with a `space` character + +./occ config:app:set circles --value 12 allow_circles # Only show 'public' and 'closed' circles +./occ config:app:set circles --value 1 skip_invitation_to_closed_circles + +./occ config:app:set circles --value 0 allow_files_filtered_by_circles # Disable files list filtering by circles in the 'files' application +./occ config:app:set circles --value 0 allow_adding_any_group_members # Adding group members only for groups where the current user is a member or global administrators + +./occ maintenance:mode --off +``` + # Credits App Icon by [Madebyoliver](http://www.flaticon.com/authors/madebyoliver) under Creative Commons BY 3.0 diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 1b3c247dc..4e5762a7c 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -29,16 +29,18 @@ namespace OCA\Circles\AppInfo; -use OC; use OCA\Circles\Api\v1\Circles; use OCA\Circles\Notification\Notifier; use OCA\Circles\Service\ConfigService; use OCA\Circles\Service\DavService; +use OCA\Circles\Service\GroupsBackendService; use OCA\Files\App as FilesApp; use OCP\App\ManagerEvent; use OCP\AppFramework\App; use OCP\AppFramework\IAppContainer; use OCP\AppFramework\QueryException; +use OCP\IGroup; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Util; require_once __DIR__ . '/../../appinfo/autoload.php'; @@ -56,22 +58,36 @@ class Application extends App { /** @var IAppContainer */ private $container; + /** @var \OC\ServerServer */ + private $server; + + /** @var IEventDispatcher */ + private $dispatcher; + /** * @param array $params */ public function __construct(array $params = array()) { parent::__construct(self::APP_NAME, $params); + } + public function register() + { $this->container = $this->getContainer(); + $this->server = $this->container->getServer(); + $this->dispatcher = $this->server->query(IEventDispatcher::class); - $manager = OC::$server->getNotificationManager(); + $manager = $this->server->getNotificationManager(); $manager->registerNotifierService(Notifier::class); + $this->registerNavigation(); + $this->registerFilesNavigation(); + $this->registerFilesPlugin(); $this->registerHooks(); $this->registerDavHooks(); + $this->registerGroupsBackendHooks(); } - /** * Register Hooks */ @@ -91,7 +107,7 @@ public function registerHooks() { public function registerNavigation() { /** @var ConfigService $configService */ try { - $configService = OC::$server->query(ConfigService::class); + $configService = $this->server->query(ConfigService::class); } catch (QueryException $e) { return; } @@ -104,8 +120,8 @@ public function registerNavigation() { ->getNavigationManager(); $appManager->add( function() { - $urlGen = OC::$server->getURLGenerator(); - $navName = OC::$server->getL10N(self::APP_NAME) + $urlGen = $this->server->getURLGenerator(); + $navName = $this->server->getL10N(self::APP_NAME) ->t('Circles'); return [ @@ -121,8 +137,17 @@ function() { } public function registerFilesPlugin() { - $eventDispatcher = OC::$server->getEventDispatcher(); - $eventDispatcher->addListener( + try { + /** @var ConfigService $configService */ + $configService = $this->server->query(ConfigService::class); + if (!$configService->isFilesFilteredCirclesAllowed()) { + return; + } + } catch (QueryException $e) { + return; + } + + $this->dispatcher->addListener( 'OCA\Files::loadAdditionalScripts', function() { Circles::addJavascriptAPI(); @@ -140,10 +165,20 @@ function() { * */ public function registerFilesNavigation() { + try { + /** @var ConfigService $configService */ + $configService = $this->server->query(ConfigService::class); + if (!$configService->isFilesFilteredCirclesAllowed()) { + return; + } + } catch (QueryException $e) { + return; + } + $appManager = FilesApp::getNavigationManager(); $appManager->add( function() { - $l = OC::$server->getL10N('circles'); + $l = $this->server->getL10N('circles'); return [ 'id' => 'circlesfilter', @@ -160,24 +195,63 @@ function() { public function registerDavHooks() { try { /** @var ConfigService $configService */ - $configService = OC::$server->query(ConfigService::class); + $configService = $this->server->query(ConfigService::class); if (!$configService->isContactsBackend()) { return; } /** @var DavService $davService */ - $davService = OC::$server->query(DavService::class); + $davService = $this->server->query(DavService::class); } catch (QueryException $e) { return; } - $event = OC::$server->getEventDispatcher(); + $this->dispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE, [$davService, 'onAppEnabled']); + $this->dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', [$davService, 'onCreateCard']); + $this->dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', [$davService, 'onUpdateCard']); + $this->dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', [$davService, 'onDeleteCard']); + } - $event->addListener(ManagerEvent::EVENT_APP_ENABLE, [$davService, 'onAppEnabled']); - $event->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', [$davService, 'onCreateCard']); - $event->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', [$davService, 'onUpdateCard']); - $event->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', [$davService, 'onDeleteCard']); + public function registerGroupsBackendHooks() { + try { + /** @var ConfigService $configService */ + $configService = $this->server->query(ConfigService::class); + if (!$configService->isGroupsBackend()) { + return; + } + + /** @var GroupsBackendService $groupsBackendService */ + $groupsBackendService = $this->server->query(GroupsBackendService::class); + } catch (QueryException $e) { + return; + } + + $this->dispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE, [$groupsBackendService, 'onAppEnabled']); + $this->dispatcher->addListener('\OCA\Circles::onCircleCreation', [$groupsBackendService, 'onCircleCreation']); + $this->dispatcher->addListener('\OCA\Circles::onCircleDestruction', [$groupsBackendService, 'onCircleDestruction']); + $this->dispatcher->addListener('\OCA\Circles::onMemberNew', [$groupsBackendService, 'onMemberNew']); + $this->dispatcher->addListener('\OCA\Circles::onMemberInvited', [$groupsBackendService, 'onMemberInvited']); + $this->dispatcher->addListener('\OCA\Circles::onMemberRequesting', [$groupsBackendService, 'onMemberRequesting']); + $this->dispatcher->addListener('\OCA\Circles::onMemberLeaving', [$groupsBackendService, 'onMemberLeaving']); + $this->dispatcher->addListener('\OCA\Circles::onMemberLevel', [$groupsBackendService, 'onMemberLevel']); + $this->dispatcher->addListener('\OCA\Circles::onMemberOwner', [$groupsBackendService, 'onMemberOwner']); + $this->dispatcher->addListener('\OCA\Circles::onGroupLink', [$groupsBackendService, 'onGroupLink']); + $this->dispatcher->addListener('\OCA\Circles::onGroupUnlink', [$groupsBackendService, 'onGroupUnlink']); + $this->dispatcher->addListener('\OCA\Circles::onGroupLevel', [$groupsBackendService, 'onGroupLevel']); + $this->dispatcher->addListener('\OCA\Circles::onLinkRequestSent', [$groupsBackendService, 'onLinkRequestSent']); + $this->dispatcher->addListener('\OCA\Circles::onLinkRequestReceived', [$groupsBackendService, 'onLinkRequestReceived']); + $this->dispatcher->addListener('\OCA\Circles::onLinkRequestRejected', [$groupsBackendService, 'onLinkRequestRejected']); + $this->dispatcher->addListener('\OCA\Circles::onLinkRequestCanceled', [$groupsBackendService, 'onLinkRequestCanceled']); + $this->dispatcher->addListener('\OCA\Circles::onLinkRequestAccepted', [$groupsBackendService, 'onLinkRequestAccepted']); + $this->dispatcher->addListener('\OCA\Circles::onLinkRequestAccepting', [$groupsBackendService, 'onLinkRequestAccepting']); + $this->dispatcher->addListener('\OCA\Circles::onLinkUp', [$groupsBackendService, 'onLinkUp']); + $this->dispatcher->addListener('\OCA\Circles::onLinkDown', [$groupsBackendService, 'onLinkDown']); + $this->dispatcher->addListener('\OCA\Circles::onLinkRemove', [$groupsBackendService, 'onLinkRemove']); + $this->dispatcher->addListener('\OCA\Circles::onSettingsChange', [$groupsBackendService, 'onSettingsChange']); + + $this->dispatcher->addListener(IGroup::class . '::postAddUser', [$groupsBackendService, 'onGroupPostAddUser']); + $this->dispatcher->addListener(IGroup::class . '::postRemoveUser', [$groupsBackendService, 'onGroupPostRemoveUser']); + $this->dispatcher->addListener(IGroup::class . '::postDelete', [$groupsBackendService, 'onGroupPostDelete']); } } - diff --git a/lib/Db/CirclesRequest.php b/lib/Db/CirclesRequest.php index aca9d7fec..d49304634 100644 --- a/lib/Db/CirclesRequest.php +++ b/lib/Db/CirclesRequest.php @@ -130,6 +130,36 @@ public function forceGetCircleByName($name) { } + /** + * forceGetCircleByGroupId(); + * + * returns data of a circle from its Group ID. + * + * WARNING: This function does not filters data regarding the current user/viewer. + * In case of interaction with users, do not use this method. + * + * @param $groupId + * + * @return null|Circle + */ + public function forceGetCircleByGroupId($groupId) { + $qb = $this->getCirclesSelectSql(); + + $this->limitToGroupId($qb, $groupId); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + return null; + } + + $entry = $this->parseCirclesSelectSql($data); + + return $entry; + } + /** * @param string $userId * @param int $type @@ -331,7 +361,8 @@ public function updateCircle(Circle $circle, $userId) { $qb = $this->getCirclesUpdateSql($circle->getUniqueId(true)); $qb->set('name', $qb->createNamedParameter($circle->getName())) ->set('description', $qb->createNamedParameter($circle->getDescription())) - ->set('settings', $qb->createNamedParameter($circle->getSettings(true))); + ->set('settings', $qb->createNamedParameter($circle->getSettings(true))) + ->set('group_id', $qb->createNamedParameter($circle->getGroupId())); $qb->execute(); } diff --git a/lib/Db/CirclesRequestBuilder.php b/lib/Db/CirclesRequestBuilder.php index 9f773c8fe..9226aa2c8 100644 --- a/lib/Db/CirclesRequestBuilder.php +++ b/lib/Db/CirclesRequestBuilder.php @@ -392,7 +392,7 @@ protected function getCirclesSelectSql() { $qb->selectDistinct('c.unique_id') ->addSelect( 'c.id', 'c.name', 'c.description', 'c.settings', 'c.type', 'contact_addressbook', - 'contact_groupname', 'c.creation' + 'contact_groupname', 'c.creation', 'c.group_id' ) ->from(CoreRequestBuilder::TABLE_CIRCLES, 'c'); $this->default_select_alias = 'c'; @@ -419,6 +419,7 @@ protected function parseCirclesSelectSql($data) { if ($data['contact_groupname'] !== null) { $circle->setContactGroupName($data['contact_groupname']); } + $circle->setGroupId($data['group_id']); $circle->setSettings($data['settings']); $circle->setType($data['type']); $circle->setCreation($data['creation']); diff --git a/lib/Db/MembersRequest.php b/lib/Db/MembersRequest.php index 54bd6961e..e60c8f048 100644 --- a/lib/Db/MembersRequest.php +++ b/lib/Db/MembersRequest.php @@ -637,6 +637,14 @@ public function unlinkAllFromGroup($groupId) { } + public function unlinkFromGroup($circleId, $groupId) { + $qb = $this->getGroupsDeleteSql($groupId); + $this->limitToCircleId($qb, $circleId); + + $qb->execute(); + } + + /** * @param string $contactId * @@ -731,4 +739,3 @@ public function removeMembersByContactId(string $contactId, int $type = 0) { } - diff --git a/lib/Migration/Version0017Date20200221173726.php b/lib/Migration/Version0017Date20200221173726.php new file mode 100644 index 000000000..e27d834a1 --- /dev/null +++ b/lib/Migration/Version0017Date20200221173726.php @@ -0,0 +1,81 @@ + + * @copyright 2019 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + +namespace OCA\Circles\Migration; + +use Closure; +use Doctrine\DBAL\Schema\SchemaException; +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * Auto-generated migration step: Please modify to your needs! + */ +class Version0017Date20200221173726 extends SimpleMigrationStep { + + + /** @var IDBConnection */ + private $connection; + + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * + * @return null|ISchemaWrapper + * @throws SchemaException + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('circles_circles'); + $table->addColumn( + 'group_id', 'string', [ + 'notnull' => false, + 'default' => '', + 'length' => 64, + ] + ); + + return $schema; + } + +} diff --git a/lib/Model/BaseCircle.php b/lib/Model/BaseCircle.php index 523662cff..3bd78c0a4 100644 --- a/lib/Model/BaseCircle.php +++ b/lib/Model/BaseCircle.php @@ -82,6 +82,8 @@ class BaseCircle { /** @var int */ private $contactAddressBook = 0; + /** @var string */ + private $groupId = ''; /** @var string */ private $creation; @@ -307,6 +309,23 @@ public function getContactGroupName() { return $this->contactGroupName; } + /** + * @param string $groupId + * + * @return BaseCircle + */ + public function setGroupId($groupId) { + $this->groupId = (string) $groupId; + + return $this; + } + + /** + * @return string + */ + public function getGroupId() { + return (string) $this->groupId; + } /** * @param string|array $settings diff --git a/lib/Model/Circle.php b/lib/Model/Circle.php index 506ee313c..bdad2c37c 100644 --- a/lib/Model/Circle.php +++ b/lib/Model/Circle.php @@ -75,6 +75,7 @@ public function jsonSerialize() { 'viewer' => $this->getHigherViewer(), 'description' => $this->getDescription(), 'settings' => $this->getSettings(), + 'group_id' => $this->getGroupId(), 'type' => $this->getType(), 'creation' => $this->getCreation(), 'type_string' => $this->getTypeString(), @@ -253,5 +254,3 @@ public static function typeLongString($type) { } - - diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index c1497c7ac..f05c232cd 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -26,6 +26,7 @@ namespace OCA\Circles\Service; +use OC; use OCA\Circles\Model\Circle; use OCP\IConfig; use OCP\IRequest; @@ -37,6 +38,9 @@ class ConfigService { const CIRCLES_ALLOW_CIRCLES = 'allow_circles'; const CIRCLES_CONTACT_BACKEND = 'contact_backend'; const CIRCLES_STILL_FRONTEND = 'still_frontend'; + const CIRCLES_GROUP_BACKEND = 'group_backend'; + const CIRCLES_GROUP_BACKEND_NAME_PREFIX = 'group_backend_name_prefix'; + const CIRCLES_GROUP_BACKEND_NAME_SUFFIX = 'group_backend_name_suffix'; const CIRCLES_SWAP_TO_TEAMS = 'swap_to_teams'; const CIRCLES_ALLOW_FEDERATED_CIRCLES = 'allow_federated'; const CIRCLES_MEMBERS_LIMIT = 'members_limit'; @@ -53,19 +57,25 @@ class ConfigService { const CIRCLES_TEST_ASYNC_COUNT = 'test_async_count'; private $defaults = [ - self::CIRCLES_ALLOW_CIRCLES => Circle::CIRCLES_ALL, - self::CIRCLES_CONTACT_BACKEND => '0', - self::CIRCLES_STILL_FRONTEND => '0', - self::CIRCLES_TEST_ASYNC_INIT => '0', - self::CIRCLES_SWAP_TO_TEAMS => '0', - self::CIRCLES_ACCOUNTS_ONLY => '0', - self::CIRCLES_MEMBERS_LIMIT => '50', - self::CIRCLES_ALLOW_LINKED_GROUPS => '0', - self::CIRCLES_ALLOW_FEDERATED_CIRCLES => '0', - self::CIRCLES_ALLOW_NON_SSL_LINKS => '0', - self::CIRCLES_NON_SSL_LOCAL => '0', - self::CIRCLES_ACTIVITY_ON_CREATION => '1', - self::CIRCLES_SKIP_INVITATION_STEP => '0' + self::CIRCLES_ALLOW_CIRCLES => Circle::CIRCLES_ALL, + self::CIRCLES_CONTACT_BACKEND => '0', + self::CIRCLES_STILL_FRONTEND => '0', + self::CIRCLES_GROUP_BACKEND => '0', + self::CIRCLES_GROUP_BACKEND_NAME_PREFIX => '', + self::CIRCLES_GROUP_BACKEND_NAME_SUFFIX => '', + self::CIRCLES_TEST_ASYNC_INIT => '0', + self::CIRCLES_SWAP_TO_TEAMS => '0', + self::CIRCLES_ACCOUNTS_ONLY => '0', + self::CIRCLES_MEMBERS_LIMIT => '50', + self::CIRCLES_ALLOW_FILES_CIRCLES_FILTER => '1', + self::CIRCLES_ALLOW_LISTED_CIRCLES => '1', + self::CIRCLES_ALLOW_ANY_GROUP_MEMBERS => '1', + self::CIRCLES_ALLOW_LINKED_GROUPS => '0', + self::CIRCLES_ALLOW_FEDERATED_CIRCLES => '0', + self::CIRCLES_ALLOW_NON_SSL_LINKS => '0', + self::CIRCLES_NON_SSL_LOCAL => '0', + self::CIRCLES_ACTIVITY_ON_CREATION => '1', + self::CIRCLES_SKIP_INVITATION_STEP => '0' ]; /** @var string */ @@ -98,6 +108,12 @@ class ConfigService { /** @var int */ private $localNonSSL = -1; + /** @var string */ + private $groupBackendNamePrefix = null; + + /** @var string */ + private $groupBackendNameSuffix = null; + /** * ConfigService constructor. * @@ -383,6 +399,41 @@ public function contactsBackendType(): int { return (int)$this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND); } + /** + * @return bool + */ + public function isGroupsBackend(): bool { + return ($this->getAppValue(ConfigService::CIRCLES_GROUP_BACKEND) !== '0'); + } + + /** + * returns the prefix of the group name + * + * @return string|null + */ + public function getGroupBackendNamePrefix() { + if ($this->groupBackendNamePrefix === null && $this->isGroupsBackend()) { + $this->groupBackendNamePrefix = ltrim((string) $this->getAppValue(self::CIRCLES_GROUP_BACKEND_NAME_PREFIX)); + } + + return $this->groupBackendNamePrefix; + } + + /** + * returns the suffix of the group name + * + * @return string|null + */ + public function getGroupBackendNameSuffix() { + if ($this->groupBackendNameSuffix === null && $this->isGroupsBackend()) { + $l = OC::$server->getL10N('circles'); + $defaultSuffix = ' '.$l->t('Circle'); + $customSuffix = (string) $this->getAppValue(self::CIRCLES_GROUP_BACKEND_NAME_SUFFIX); + $this->groupBackendNameSuffix = rtrim($customSuffix ?: $defaultSuffix); + } + + return $this->groupBackendNameSuffix; + } /** * @return bool diff --git a/lib/Service/GroupsBackendService.php b/lib/Service/GroupsBackendService.php new file mode 100644 index 000000000..98f91ff85 --- /dev/null +++ b/lib/Service/GroupsBackendService.php @@ -0,0 +1,442 @@ + + * @copyright 2017 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Circles\Service; + +// use Exception; +use OCP\App\ManagerEvent; +use OCA\Circles\Db\CirclesRequest; +use OCA\Circles\Db\MembersRequest; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\Member; +use OCP\EventDispatcher\GenericEvent; +use Symfony\Component\EventDispatcher\GenericEvent as SymfonyGenericEvent; +use OCP\IGroup; +use OCP\IUser; +use OCP\IGroupManager; +use OCP\IUserManager; + +/** + * Class GroupsBackendService + * + * @package OCA\Circles\Service + */ +class GroupsBackendService { + + /** @var string */ + protected $userId; + + /** @var Circle */ + protected $circle; + + /** @var Member */ + protected $member; + + /** @var IGroup */ + protected $group; + + /** @var IUser */ + protected $user; + + /** @var ConfigService */ + protected $configService; + + /** @var MiscService */ + protected $miscService; + + /** @var CirclesRequest */ + protected $circlesRequest; + + /** @var MembersRequest */ + protected $membersRequest; + + /** @var IGroupManager */ + protected $groupManager; + + /** @var IUserManager */ + protected $userManager; + + /** + * GroupsBackendService constructor. + * + * @param string $userId + * @param CirclesRequest $circlesRequest + * @param MembersRequest $membersRequest + * @param IGroupManager $groupManager + * @param IUserManager $userManager + * @param ConfigService $configService + * @param MiscService $miscService + */ + public function __construct( + $userId, + CirclesRequest $circlesRequest, + MembersRequest $membersRequest, + IGroupManager $groupManager, + IUserManager $userManager, + ConfigService $configService, + MiscService $miscService + ) { + $this->userId = $userId; + $this->circlesRequest = $circlesRequest; + $this->membersRequest = $membersRequest; + $this->groupManager = $groupManager; + $this->userManager = $userManager; + $this->configService = $configService; + $this->miscService = $miscService; + } + + /** + * @param ManagerEvent $event + */ + public function onAppEnabled(ManagerEvent $event) { + if ($event->getAppID() !== 'circles') { + return; + } + } + + /** + * @param GenericEvent $event + */ + public function onCircleCreation(GenericEvent $event) { + $this->circle = $event->getArgument('circle'); + // '\OC\Group', 'postDelete' + // '\OC\Group', 'postAddUser' + // '\OC\Group', 'postRemoveUser' + // $eventName ='\OC\Group::postCreate'; + + // $listeners = $this->eventDispatcher->getSymfonyDispatcher()->getListeners($eventName); + // $this->miscService->log('number of listeners: '. count($listeners), 1); + + // foreach ($listeners as $listener) { + // $this->miscService->log('remove listener: '. json_encode($listener), 1); + // $this->eventDispatcher->getSymfonyDispatcher()->removeListener($eventName, $listener); + // } + + $this->group = $this->groupManager->createGroup($this->getCircleGroupName()); + + // foreach ($listeners as $listener) { + // $this->miscService->log('add listener: '. json_encode($listener), 1); + // $this->eventDispatcher->getSymfonyDispatcher()->addListener($eventName, $listener); + // } + + if ($this->group) { + $this->member = $this->circle->getOwner(); + + $this->circle->setGroupId($this->group->getGID()); + $this->circlesRequest->updateCircle($this->circle, $this->member->getUserId()); + + if ($this->member->getType() === Member::TYPE_USER) { + $this->user = $this->userManager->get($this->member->getUserId()); + if ($this->user) { + $this->group->addUser($this->user); + } + } + } + + $this->miscService->log('onCircleCreation: '. json_encode($this->circle), 1); + } + + /** + * @param GenericEvent $event + */ + public function onCircleDestruction(GenericEvent $event) { + $this->circle = $event->getArgument('circle'); + $gid = $this->circle->getGroupId(); + $this->group = $this->groupManager->get($gid); + + if ($this->group) { + $this->group->delete(); + } + + $this->miscService->log('onCircleDestruction: '. json_encode($this->circle), 1); + } + + /** + * @param GenericEvent $event + */ + public function onMemberNew(GenericEvent $event) { + $this->circle = $event->getArgument('circle'); + $this->member = $event->getArgument('member'); + + if ($this->member->getType() === Member::TYPE_USER) { + $gid = $this->circle->getGroupId(); + $this->group = $this->groupManager->get($gid); + $this->user = $this->userManager->get($this->member->getUserId()); + + if ($this->group && $this->user) { + $this->group->addUser($this->user); + } + } + + $this->miscService->log('onMemberNew: '. json_encode($this->circle).json_encode($this->member), 1); + } + + /** + * @param GenericEvent $event + */ + public function onMemberInvited(GenericEvent $event) { + $this->miscService->log('onMemberInvited: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onMemberRequesting(GenericEvent $event) { + $this->miscService->log('onMemberRequesting: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onMemberLeaving(GenericEvent $event) { + $this->circle = $event->getArgument('circle'); + $this->member = $event->getArgument('member'); + + if ($this->member->getType() === Member::TYPE_USER) { + $gid = $this->circle->getGroupId(); + $this->group = $this->groupManager->get($gid); + $this->user = $this->userManager->get($this->member->getUserId()); + + if ($this->group && $this->user) { + $this->group->removeUser($this->user); + } + } + + $this->miscService->log('onMemberLeaving: '. json_encode($this->circle).json_encode($this->member), 1); + } + + /** + * @param GenericEvent $event + */ + public function onMemberLevel(GenericEvent $event) { + $this->miscService->log('onMemberLevel: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onMemberOwner(GenericEvent $event) { + $this->miscService->log('onMemberOwner: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onGroupLink(GenericEvent $event) { + $this->miscService->log('onGroupLink: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onGroupUnlink(GenericEvent $event) { + $this->miscService->log('onGroupUnlink: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onGroupLevel(GenericEvent $event) { + $this->miscService->log('onGroupLevel: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkRequestSent(GenericEvent $event) { + $this->miscService->log('onLinkRequestSent: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkRequestReceived(GenericEvent $event) { + $this->miscService->log('onLinkRequestReceived: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkRequestRejected(GenericEvent $event) { + $this->miscService->log('onLinkRequestRejected: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkRequestCanceled(GenericEvent $event) { + $this->miscService->log('onLinkRequestCanceled: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkRequestAccepted(GenericEvent $event) { + $this->miscService->log('onLinkRequestAccepted: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkRequestAccepting(GenericEvent $event) { + $this->miscService->log('onLinkRequestAccepting: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkUp(GenericEvent $event) { + $this->miscService->log('onLinkUp: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkDown(GenericEvent $event) { + $this->miscService->log('onLinkDown: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onLinkRemove(GenericEvent $event) { + $this->miscService->log('onLinkRemove: '. json_encode($event), 1); + } + + /** + * @param GenericEvent $event + */ + public function onSettingsChange(GenericEvent $event) { + $this->circle = $event->getArgument('circle'); + $oldSettings = $event->getArgument('oldSettings'); + + $this->circle = $event->getArgument('circle'); + $gid = $this->circle->getGroupId(); + $this->group = $this->groupManager->get($gid); + + $this->setCircleGroupName($this->getCircleGroupName()); + + $this->miscService->log('onSettingsChange: '. json_encode($this->circle).json_encode($oldSettings), 1); + } + + /** + * When a group add a user, add it as a member of the associate Circle + * + * @param SymfonyGenericEvent $event + */ + public function onGroupPostAddUser(SymfonyGenericEvent $event) { + $this->group = $event->getSubject(); + $this->user = $event->getArgument('user'); + + $this->miscService->log('onGroupPostAddUser: '.json_encode($event).json_encode($this->group).json_encode($this->user), 1); + if ($this->group instanceof IGroup && $this->group->getGID()) { + $this->circle = $this->circlesRequest->forceGetCircleByGroupId($this->group->getGID()); + if ($this->circle) { + $this->member = $this->membersRequest->getFreshNewMember( + $this->circle->getUniqueId(), $this->user->getUID(), Member::TYPE_USER + ); + $this->member->addMemberToCircle(); + $this->membersRequest->updateMember($this->member); + } + } + } + + /** + * When a group remove a user, remove it as a member of the associate Circle + * + * @param SymfonyGenericEvent $event + */ + public function onGroupPostRemoveUser(SymfonyGenericEvent $event) { + $this->group = $event->getSubject(); + $this->user = $event->getArgument('user'); + + $this->miscService->log('onGroupPostRemoveUser: '.json_encode($event).json_encode($this->group).json_encode($this->user), 1); + if ($this->group instanceof IGroup && $this->group->getGID()) { + $this->circle = $this->circlesRequest->forceGetCircleByGroupId($this->group->getGID()); + if ($this->circle) { + try { + $this->member = $this->membersRequest->forceGetMember( + $this->circle->getUniqueId(), $this->user->getUID(), Member::TYPE_USER + ); + $this->member->hasToBeMember(); + $this->member->cantBeOwner(); + } catch (MemberDoesNotExistException $e) { + $this->member = null; + } catch (MemberIsOwnerException $e) { + $this->member = null; + } + if ($this->member) { + $this->membersRequest->removeMember($this->member); + } + } + } + } + + /** + * When a group is removed, remove its associated Circle, if any + * + * @param SymfonyGenericEvent $event + */ + public function onGroupPostDelete(SymfonyGenericEvent $event) { + $this->group = $event->getSubject(); + $this->miscService->log('onGroupPostDelete: '.json_encode($event).json_encode($this->group), 1); + if ($this->group instanceof IGroup && $this->group->getGID()) { + $circle = $this->circlesRequest->forceGetCircleByGroupId($this->group->getGID()); + if ($circle) { + $this->circlesRequest->destroyCircle($circle->getUniqueId()); + } + } + } + + /** + * @return string|null + */ + protected function getCircleGroupName() + { + if ($this->circle instanceof Circle) { + return $this->configService->getGroupBackendNamePrefix(). + $this->circle->getName(). + $this->configService->getGroupBackendNameSuffix(); + } + + return; + } + + /** + * @param string $displayName + * @return bool + */ + protected function setCircleGroupName($displayName) + { + if ($this->group && method_exists($this->group, 'setDisplayName')) { + $this->miscService->log('setCircleGroupName: '. json_encode($displayName), 1); + return $this->group->setDisplayName($displayName); + } + + return false; + } +} diff --git a/lib/Service/GroupsService.php b/lib/Service/GroupsService.php index f0cbd7878..734651417 100644 --- a/lib/Service/GroupsService.php +++ b/lib/Service/GroupsService.php @@ -270,7 +270,7 @@ public function unlinkGroup($circleUniqueId, $groupId) { $group->cantBeOwner(); $higherViewer->hasToBeHigherLevel($group->getLevel()); - $this->membersRequest->unlinkAllFromGroup($groupId); + $this->membersRequest->unlinkFromGroup($circleUniqueId, $groupId); $this->eventsService->onGroupUnlink($circle, $group); diff --git a/lib/Service/MembersService.php b/lib/Service/MembersService.php index c025187e4..063621075 100644 --- a/lib/Service/MembersService.php +++ b/lib/Service/MembersService.php @@ -176,6 +176,7 @@ public function addMember($circleUniqueId, $ident, $type, bool $force = false) { */ private function addSingleMember(Circle $circle, $ident, $type) { $this->verifyIdentBasedOnItsType($ident, $type); + $this->verifyIdentWithGroupBackend($circle, $ident, $type); $member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type); $member->hasToBeInviteAble(); @@ -284,6 +285,33 @@ private function addContact(Member $member) { } + /** + * Verify the availability of an ident when Group Backend is enabled + * + * @param Circle $circle + * @param string $ident + * @param int $type + * + * @throws Exception + */ + private function verifyIdentWithGroupBackend(Circle $circle, $ident, $type) { + if ($this->configService->isGroupsBackend() && + in_array($type, [Member::TYPE_MAIL, Member::TYPE_CONTACT], true) && + in_array($circle->getType(), [Circle::CIRCLES_CLOSED, Circle::CIRCLES_PUBLIC], true) + ) { + if ($type === Member::TYPE_MAIL) { + $errorMessage = 'You cannot add a mail address as member of your Circle'; + } + if ($type === Member::TYPE_CONTACT) { + $errorMessage = 'You cannot add a contact as member of your Circle'; + } + throw new EmailAccountInvalidFormatException( + $this->l10n->t($errorMessage) + ); + } + } + + /** * Verify the availability of an ident, based on its type. * @@ -656,4 +684,3 @@ public function onUserRemoved($userId) { } -