diff --git a/README.md b/README.md index d3882640b..a23a57b7f 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,36 @@ Anyone can find the circle and request an invitation; but only members will see - A **Secret Circle** is an hidden group that can only be seen by its members or by people knowing the exact name of the circle. Non-members won't be able to find your secret circle using the search bar. +## Settings + +Circles settings is available in Nextcloud interface in **Settings / Additional Settings**. + +### Async Testing + +This option allows to initiate an async test in Circles. + +### Allow linking of groups + +This option allows that groups be linked to circles. + +### Allow federated circles + +This option allows that circles from different Nextclouds can be linked together. + +### Enable audit + +This options allows that actions of circles, members and sharing can be audit with records into log. Following actions are audited: + +* User X created circle Z; +* User X removed circle Z; +* User X change name of circle Z for circle W; +* User X was added to circle U by user Z; +* User X shared file/folder with circle Y; +* User X, that created circle, unshared file/folder with circle Y +* Member X accepted invitation to circle Y by user Z; +* Member X left circle Y; +* User X change role of member Y in circle Z for W. +* User X was invited to circle U by user Z *** # API (PHP & Javascript) diff --git a/appinfo/info.xml b/appinfo/info.xml index 51103c7f2..fbeeda955 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -38,7 +38,7 @@ Users won't be able to find this Circle using Nextcloud search engine. https://github.com/nextcloud/circles.git https://raw.githubusercontent.com/nextcloud/circles/master/screenshots/0.12.0.png - + diff --git a/css/navigation.css b/css/navigation.css index 0772d8992..2cbfe63c6 100644 --- a/css/navigation.css +++ b/css/navigation.css @@ -4,22 +4,22 @@ padding: 20px; } -#circle-navigation .navigation-element, #circle-navigation .navigation-element option { +#app-navigation .navigation-element, #app-navigation .navigation-element option { padding: 3px 12px; } -#circle-navigation input#circles_search, #circle-navigation select#circles_filters { +#app-navigation input#circles_search, #app-navigation select#circles_filters { margin: 10px; - width: 225px; + width: 285px; } -#circle-navigation select#circles_filters { +#app-navigation select#circles_filters { margin-top: 0; font-style: italic; color: #777777; } -#circle-navigation input, #circle-navigation select, #circle-navigation option { +#app-navigation input, #app-navigation select, #app-navigation option { width: 100%; box-sizing: border-box; background: #ffffffaa !important; @@ -29,7 +29,7 @@ overflow-x: hidden; } -#circle-navigation .header { +#app-navigation .header { padding: 0 12px; line-height: 44px; min-height: 44px; @@ -39,7 +39,7 @@ #circles_new_type_definition div { margin-top: 20px; position: absolute; - width: 190px; + width: 255px; color: #474747b0; word-wrap: break-word; } @@ -51,7 +51,7 @@ SELECT.select_none { #circles_list, #circles_list .lightenbg { position: absolute; width: 100%; - top: 350px; + top: 300px; } #circles_list div { @@ -75,28 +75,28 @@ SELECT.select_none { font-weight: bold !important; } -#circle-navigation { +#app-navigation { width: 300px; border-right: 1px solid #ddd; } -#circle-navigation.circles { +#app-navigation.circles { overflow-y: auto; width: 350px; overflow-x: hidden; z-index: 1002; } -#circle-navigation.circles, #circle-navigation.circles .circle, #circle-navigation.circles .selected { +#app-navigation.circles, #app-navigation.circles .circle, #app-navigation.circles .selected { -webkit-transition: background-color 0.3s ease-in; transition: background-color 0.3s ease-in; } -#circle-navigation.selected { +#app-navigation.selected { background: #ededed !important; } -#circle-navigation.circles .selected { +#app-navigation.circles .selected { background: #e5e5e5; } @@ -216,8 +216,8 @@ div.circle .owner, div.circle .type, div.circle .resume { #emptycontent { position: absolute; - z-index: 1001; - left: 0px; + z-index: -99; + left: 200px; } #circle-actions { @@ -354,4 +354,4 @@ DIV.result_bot { padding: 5px; height: 10px; font-style: italic; -} \ No newline at end of file +} diff --git a/js/admin.js b/js/admin.js index cbfdcb195..336b96bd9 100644 --- a/js/admin.js +++ b/js/admin.js @@ -33,7 +33,8 @@ var elements = { // test_async_wait: null, members_limit: null, allow_linked_groups: null, - allow_federated_circles: null + allow_federated_circles: null, + disable_notification_for_seen_users: null }; @@ -46,6 +47,7 @@ $(document).ready(function () { elements.members_limit = $('#members_limit'); elements.allow_linked_groups = $('#allow_linked_groups'); elements.allow_federated_circles = $('#allow_federated_circles'); + elements.disable_notification_for_seen_users = $('#disable_notification_for_seen_users'); // elements.test_async_wait.hide().on('click', function () { // self.refreshResult(); @@ -81,6 +83,11 @@ $(document).ready(function () { elements.allow_federated_circles.on('change', function () { saveChange(); }); + + elements.disable_notification_for_seen_users.on('change', function () { + saveChange(); + }); + saveChange = function () { $.ajax({ @@ -91,12 +98,15 @@ $(document).ready(function () { allow_linked_groups: (elements.allow_linked_groups.is( ':checked')) ? '1' : '0', allow_federated_circles: (elements.allow_federated_circles.is( - ':checked')) ? '1' : '0' + ':checked')) ? '1' : '0', + disable_notification_for_seen_users: (elements.disable_notification_for_seen_users.is( + ':checked')) ? '1' : '0' } }).done(function (res) { elements.members_limit.val(res.membersLimit); elements.allow_linked_groups.prop('checked', (res.allowLinkedGroups === '1')); elements.allow_federated_circles.prop('checked', (res.allowFederatedCircles === '1')); + elements.disable_notification_for_seen_users.prop('checked', (res.disableNotificationForSeenUsers === '1')); }); }; @@ -180,6 +190,7 @@ $(document).ready(function () { elements.members_limit.val(res.membersLimit); elements.allow_linked_groups.prop('checked', (res.allowLinkedGroups === '1')); elements.allow_federated_circles.prop('checked', (res.allowFederatedCircles === '1')); + elements.disable_notification_for_seen_users.prop('checked', (res.disableNotificationForSeenUsers === '1')); }); // // var timerTestAsync = setInterval(function () { diff --git a/js/circles.app.elements.js b/js/circles.app.elements.js index 31d42df87..4019503a6 100644 --- a/js/circles.app.elements.js +++ b/js/circles.app.elements.js @@ -72,12 +72,14 @@ var elements = { settingsDesc: null, settingsLimit: null, settingsEntryLimit: null, + settingsNotification: null, settingsLink: null, settingsLinkAuto: null, settingsLinkFiles: null, settingsEntryLink: null, settingsEntryLinkAuto: null, settingsEntryLinkFiles: null, + settingsEntryNotification: null, settingsSave: null, addMember: null, @@ -98,7 +100,7 @@ var elements = { elements.newType = $('#circles_new_type'); elements.newSubmit = $('#circles_new_submit'); elements.newName = $('#circles_new_name'); - elements.navigation = $('#circle-navigation.circles'); + elements.navigation = $('#app-navigation.circles'); elements.circlesList = $('#circles_list'); elements.circlesSearch = $('#circles_search'); elements.circlesFilters = $('#circles_filters'); @@ -139,9 +141,11 @@ var elements = { elements.settingsLink = $('#settings-link'); elements.settingsLinkAuto = $('#settings-link-auto'); elements.settingsLinkFiles = $('#settings-link-files'); + elements.settingsNotification = $('#settings-notification') elements.settingsEntryLink = $('#settings-entry-link'); elements.settingsEntryLinkAuto = $('#settings-entry-link-auto'); elements.settingsEntryLinkFiles = $('#settings-entry-link-files'); + elements.settingsEntryNotification = $('#settings-entry-notification'); elements.settingsSave = $('#settings-submit'); elements.addMember = $('#addmember'); diff --git a/js/circles.app.js b/js/circles.app.js index 4f2c837af..ad72bf2f5 100644 --- a/js/circles.app.js +++ b/js/circles.app.js @@ -60,6 +60,7 @@ var curr = { allowed_linked_groups: 0, allowed_federated_circles: 0, allowed_circles: 0, + disabled_notification_for_seen_users: 0, defineCircle: function (data) { curr.circle = data.circle_id; diff --git a/js/circles.app.settings.js b/js/circles.app.settings.js index 4de11d982..d4c07df07 100644 --- a/js/circles.app.settings.js +++ b/js/circles.app.settings.js @@ -61,6 +61,8 @@ var settings = { (curr.circleSettings['allow_links_auto'] === 'true')); elements.settingsLinkFiles.prop('checked', (curr.circleSettings['allow_links_files'] === 'true')); + elements.settingsNotification.prop('checked', + (curr.circleSettings['disable_notification_for_seen_users'] === 'true')); elements.settingsLink.on('change', function () { settings.interactUISettings(); @@ -86,6 +88,8 @@ var settings = { (elements.settingsLink.is(":checked"))); settings.enableSetting(elements.settingsEntryLinkFiles, elements.settingsLinkFiles, (elements.settingsLink.is(":checked"))); + settings.enableSetting(elements.settingsEntryNotification, elements.settingsNotification, + (elements.settingsNotification.is(":checked"))); }, enableSetting: function (entry, input, enable) { diff --git a/js/circles.v1.members.js b/js/circles.v1.members.js index 1127b3339..57908891e 100644 --- a/js/circles.v1.members.js +++ b/js/circles.v1.members.js @@ -30,7 +30,7 @@ var members = { searchUsers: function (search, callback) { - + $('#addmember').val(t('Searching for users like...') + search); var result = {status: -1}; $.ajax({ method: 'GET', diff --git a/l10n/pt_BR.js b/l10n/pt_BR.js index f6f8970d6..4d964a03c 100644 --- a/l10n/pt_BR.js +++ b/l10n/pt_BR.js @@ -279,6 +279,14 @@ OC.L10N.register( "Members limit:" : "Limite de membros:", "Default limit to the number of members in a circle." : "Limite de membros em um círculo.", "Allow linking of groups:" : "Permitir links dos grupos:", - "Allow federated circles:" : "Permitir círculos federados:" + "Allow federated circles:" : "Permitir círculos federados:", + "You shared {file} with circle {circle}" : "Você compartilhou {file} com o círculo {circle}", + "{author} shared {file} with the circle {circle}" : "{author} compartilhou {file} com o círculo {circle}", + "You unshared {file} with the circle {circle}" : "Você descompartilhou {file} com o círculo {circle}", + "{author} unshared {file} with the circle {circle}" : "{author} descompartilhou {file} com o círculo {circle}", + "Searching for users like..." : "Buscando por usuários como...", + "Disable notification for seen users." : "Desabilita notificação para usuários vistos.", + "You changed circle {circle}" : "Você alterou {circle}", + "{author} changed circle {circle}" : "{author} alterou o círculo {circle}" }, "nplurals=2; plural=(n > 1);"); diff --git a/l10n/pt_BR.json b/l10n/pt_BR.json index 1c0891aa2..5f913ba82 100644 --- a/l10n/pt_BR.json +++ b/l10n/pt_BR.json @@ -277,6 +277,14 @@ "Members limit:" : "Limite de membros:", "Default limit to the number of members in a circle." : "Limite de membros em um círculo.", "Allow linking of groups:" : "Permitir links dos grupos:", - "Allow federated circles:" : "Permitir círculos federados:" + "Allow federated circles:" : "Permitir círculos federados:", + "You shared {file} with circle {circle}" : "Você compartilhou {file} com o círculo {circle}", + "{author} shared {file} with the circle {circle}" : "{author} compartilhou {file} com o círculo {circle}", + "You unshared {file} with the circle {circle}" : "Você descompartilhou {file} com o círculo {circle}", + "{author} unshared {file} with the circle {circle}" : "{author} descompartilhou {file} com o círculo {circle}", + "Searching for users like..." : "Buscando por usuários como...", + "Disable notification for seen users." : "Desabilita notificação para usuários vistos.", + "You changed circle {circle}" : "Você alterou o círculo {circle}", + "{author} changed circle {circle}" : "{author} alterou o círculo {circle}" },"pluralForm" :"nplurals=2; plural=(n > 1);" } \ No newline at end of file diff --git a/lib/Activity/Provider.php b/lib/Activity/Provider.php index 109e3559a..1f218cbb1 100644 --- a/lib/Activity/Provider.php +++ b/lib/Activity/Provider.php @@ -55,6 +55,9 @@ class Provider implements IProvider { /** @var ProviderSubjectLink */ private $parserLink; + + /** @var ProviderSubjectShared */ + private $parserShared; /** @var MiscService */ protected $miscService; @@ -66,7 +69,7 @@ class Provider implements IProvider { public function __construct( IManager $activityManager, MiscService $miscService, ProviderSubjectCircle $parserCircle, ProviderSubjectMember $parserMember, ProviderSubjectGroup $parserGroup, - ProviderSubjectLink $parserLink + ProviderSubjectLink $parserLink, ProviderSubjectShared $parserShared ) { $this->activityManager = $activityManager; $this->miscService = $miscService; @@ -75,6 +78,7 @@ public function __construct( $this->parserMember = $parserMember; $this->parserGroup = $parserGroup; $this->parserLink = $parserLink; + $this->parserShared = $parserShared; } @@ -92,9 +96,11 @@ public function parse($lang, IEvent $event, IEvent $previousEvent = null) { $this->setIcon($event, $circle); $this->parseAsMember($event, $circle, $params); $this->parseAsModerator($event, $circle, $params); - } catch (FakeException $e) { /** clean exit */ + } catch (InvalidArgumentException $e) { + /** sharing is a special case because app is files **/ + $this->parseShared($event, $params); } return $event; @@ -110,7 +116,7 @@ private function initActivityParser(IEvent $event, $params) { throw new InvalidArgumentException(); } - if (!key_exists('circle', $params)) { + if ($event->getApp() === Application::APP_NAME && !key_exists('circle', $params)) { throw new InvalidArgumentException(); } } @@ -161,6 +167,7 @@ private function parseAsModerator(IEvent &$event, Circle $circle, $params) { return; } + $this->parserCircle->parseSubjectCircleChange($event, $circle, $params); $this->parseMemberAsModerator($event, $circle, $params); $this->parseGroupAsModerator($event, $circle, $params); $this->parseLinkAsModerator($event, $circle, $params); @@ -252,5 +259,21 @@ private function parseLinkAsModerator(IEvent &$event, Circle $circle, $params) { $this->parserLink->parseLinkRemove($event, $circle, $remote); } - + /** + * @param IEvent $event + * @param array $params + * + * @throws FakeException + */ + private function parseShared(IEvent &$event, $params) { + if ($event->getSubject() === 'shared_circle_self') { + $this->parserShared->parseSubjectSharedWithCircle($event, $params); + return; + } + if ($event->getSubject() === 'unshared_circle_self') { + $this->parserShared->parseSubjectUnsharedWithCircle($event, $params); + return; + } + throw new InvalidArgumentException(); + } } \ No newline at end of file diff --git a/lib/Activity/ProviderParser.php b/lib/Activity/ProviderParser.php index a01965dc7..fb2c07d39 100644 --- a/lib/Activity/ProviderParser.php +++ b/lib/Activity/ProviderParser.php @@ -99,7 +99,7 @@ protected function parseCircleEvent(IEvent &$event, Circle $circle, $remote, $ow * @param string $line * @param array $data */ - private function setSubject(IEvent $event, $line, $data) { + protected function setSubject(IEvent $event, $line, $data) { $this->setParsedSubject($event, $line, $data); $this->setRichSubject($event, $line, $data); } @@ -110,7 +110,7 @@ private function setSubject(IEvent $event, $line, $data) { * @param string $line * @param array $data */ - private function setRichSubject(IEvent $event, $line, $data) { + protected function setRichSubject(IEvent $event, $line, $data) { $ak = array_keys($data); foreach ($ak as $k) { $subAk = array_keys($data[$k]); @@ -130,7 +130,7 @@ private function setRichSubject(IEvent $event, $line, $data) { * @param string $line * @param array $data */ - private function setParsedSubject(IEvent $event, $line, $data) { + protected function setParsedSubject(IEvent $event, $line, $data) { $ak = array_keys($data); $replace = []; foreach ($ak as $k) { diff --git a/lib/Activity/ProviderSubjectCircle.php b/lib/Activity/ProviderSubjectCircle.php index d4150266e..b48d80f62 100644 --- a/lib/Activity/ProviderSubjectCircle.php +++ b/lib/Activity/ProviderSubjectCircle.php @@ -78,5 +78,23 @@ public function parseSubjectCircleDelete(IEvent &$event, Circle $circle) { throw new FakeException(); } - + /** + * @param IEvent $event + * @param Circle $circle + * + * @throws FakeException + */ + public function parseSubjectCircleChange(IEvent &$event, Circle $circle) { + if ($event->getSubject() !== 'circle_change') { + return; + } + + $this->parseCircleEvent( + $event, $circle, null, + $this->l10n->t('You changed circle {circle}'), + $this->l10n->t('{author} changed circle {circle}') + ); + + throw new FakeException(); + } } \ No newline at end of file diff --git a/lib/Activity/ProviderSubjectShared.php b/lib/Activity/ProviderSubjectShared.php new file mode 100644 index 000000000..ef53bfddf --- /dev/null +++ b/lib/Activity/ProviderSubjectShared.php @@ -0,0 +1,119 @@ + + * @copyright 2018 + * @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\Activity; + +use OCA\Circles\Exceptions\FakeException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\FederatedLink; +use OCA\Circles\Model\Member; +use OCP\Activity\IEvent; +use OCA\Circles\Api\v1\Circles; +use InvalidArgumentException; + +class ProviderSubjectShared extends ProviderParser { + /** + * @param IEvent $event + * @param array $params + * + * @throws FakeException + */ + public function parseSubjectSharedWithCircle(IEvent &$event, array $params) { + $this->parseSharedEvent( + $event, $params, null, + $this->l10n->t('You shared {file} with circle {circle}'), + $this->l10n->t('{author} shared {file} with the circle {circle}') + ); + } + + /** + * @param IEvent $event + * @param array $params + * + * @throws FakeException + */ + public function parseSubjectUnsharedWithCircle(IEvent &$event, array $params) { + $this->parseSharedEvent( + $event, $params, null, + $this->l10n->t('You unshared {file} with the circle {circle}'), + $this->l10n->t('{author} unshared {file} with the circle {circle}') + ); + } + + /** + * general function to generate Circle event. + * + * @param IEvent $event + * @param array $params + * @param FederatedLink|null $remote + * @param string $ownEvent + * @param string $othersEvent + */ + protected function parseSharedEvent(IEvent &$event, array $params, $remote, $ownEvent, $othersEvent + ) { + try { + $circle = Circles::infoCircleByName($params['circle']['name']); + } catch (\Exception $e) { + throw new InvalidArgumentException($e->getMessage()); + } + + try { + $path = Circles::getViewPath($params['file']['id']); + } catch (\Exception $e) { + $path = \OC::$server->getURLGenerator ()->getBaseUrl() . '/apps/files'; + } + + $data = [ + 'author' => [ + 'type' => 'user', + 'id' => $params['author']['id'], + 'name' => $params['author']['name'], + '_parsed' => $params['author']['name'] + ], + 'circle' => [ + 'type' => 'circle', + 'id' => $circle->getId(), + 'name' => $circle->getName(), + '_parsed' => $circle->getName(), + 'link' => Circles::generateLink($circle->getUniqueId()) + ], + 'file' => [ + 'type' => 'file', + 'id' => $params['file']['id'], + 'name' => $params['file']['name'], + 'path' => $path, + 'link' => \OC::$server->getURLGenerator ()->linkToRouteAbsolute('files.view.index', array ( + 'dir' => ($params['file']['type'] !== 'file') ? dirname($path) : $path) ) + ] + ]; + + if ($this->isViewerTheAuthor($circle, $this->activityManager->getCurrentUserId())) { + $this->setSubject($event, $ownEvent, $data); + return; + } + + $this->setSubject($event, $othersEvent, $data); + } +} \ No newline at end of file diff --git a/lib/Api/v1/Circles.php b/lib/Api/v1/Circles.php index 64f4c5970..c4ddd4e84 100644 --- a/lib/Api/v1/Circles.php +++ b/lib/Api/v1/Circles.php @@ -43,6 +43,7 @@ use OCA\Circles\Service\SharingFrameService; use OCP\AppFramework\QueryException; use OCP\Util; +use OC\Files\View; class Circles { @@ -529,4 +530,35 @@ public static function getFilesForCircles($circleUniqueIds) { return $c->query(CirclesService::class) ->getFilesForCircles($circleUniqueIds); } + + /** + * Get a list of objects which are shred with $circleUniqueId. + * + * @since 0.15.0 + * + * @param string $circleName + * + * @return Circle|null + * @throws CircleDoesNotExistException + */ + public static function infoCircleByName($circleName) { + $c = self::getContainer(); + return $c->query(CirclesService::class) + ->infoCircleByName($circleName); + } + + /** + * Get path of a file from id. + * + * @since 0.15.0 + * + * @param string $id + * + * @return string|null + */ + public static function getViewPath($id) { + $c = self::getContainer(); + return $c->query(View::class) + ->getPath($id); + } } \ No newline at end of file diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index bb1d1bb6a..cbff476e9 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -35,7 +35,6 @@ use OCP\AppFramework\IAppContainer; use OCP\Util; - class Application extends App { const APP_NAME = 'circles'; @@ -70,6 +69,12 @@ public function registerHooks() { Util::connectHook( 'OC_User', 'post_deleteGroup', '\OCA\Circles\Hooks\UserHooks', 'onGroupDeleted' ); + Util::connectHook( + 'OCP\Share', 'post_shared', '\OCA\Circles\Hooks\UserHooks', 'onItemShared' + ); + Util::connectHook( + 'OCP\Share', 'post_unshared', '\OCA\Circles\Hooks\UserHooks', 'onItemUnshared' + ); } @@ -134,6 +139,5 @@ function() { } ); } - } diff --git a/lib/Controller/NavigationController.php b/lib/Controller/NavigationController.php index fb14afb60..d7f5cb67b 100644 --- a/lib/Controller/NavigationController.php +++ b/lib/Controller/NavigationController.php @@ -84,6 +84,7 @@ public function settings() { 'members_list' => $this->configService->getAppValue(ConfigService::CIRCLES_MEMBERS_LIMIT), 'allowed_linked_groups' => $this->configService->getAppValue(ConfigService::CIRCLES_ALLOW_LINKED_GROUPS), 'allowed_federated_circles' => $this->configService->getAppValue(ConfigService::CIRCLES_ALLOW_FEDERATED_CIRCLES), + 'disabled_notification_for_seen_users' => $this->configService->getAppValue(ConfigService::CIRCLES_DISABLE_NOTIFICATION_FOR_SEEN_USERS), 'status' => 1 ]; diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 809bd52e7..f767ff839 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -48,6 +48,9 @@ public function getSettings() { ), 'allowFederatedCircles' => $this->configService->getAppValue( ConfigService::CIRCLES_ALLOW_FEDERATED_CIRCLES + ), + 'disableNotificationForSeenUsers' => $this->configService->getAppValue( + ConfigService::CIRCLES_DISABLE_NOTIFICATION_FOR_SEEN_USERS ) ]; @@ -55,7 +58,7 @@ public function getSettings() { } - public function setSettings($members_limit, $allow_linked_groups, $allow_federated_circles) { + public function setSettings($members_limit, $allow_linked_groups, $allow_federated_circles, $disable_notification_for_seen_users) { $this->configService->setAppValue( ConfigService::CIRCLES_MEMBERS_LIMIT, $members_limit ); @@ -65,6 +68,9 @@ public function setSettings($members_limit, $allow_linked_groups, $allow_federat $this->configService->setAppValue( ConfigService::CIRCLES_ALLOW_FEDERATED_CIRCLES, $allow_federated_circles ); + $this->configService->setAppValue( + ConfigService::CIRCLES_DISABLE_NOTIFICATION_FOR_SEEN_USERS, $disable_notification_for_seen_users + ); return $this->getSettings(); } diff --git a/lib/Db/CirclesRequest.php b/lib/Db/CirclesRequest.php index f56d2d984..969ceb480 100644 --- a/lib/Db/CirclesRequest.php +++ b/lib/Db/CirclesRequest.php @@ -102,14 +102,18 @@ public function forceGetCircles() { * WARNING: This function does not filters data regarding the current user/viewer. * In case of interaction with users, do not use this method. * - * @param $name + * @param string $name + * @param boolean $getOwner * * @return null|Circle * @throws CircleDoesNotExistException */ - public function forceGetCircleByName($name) { + public function forceGetCircleByName($name, $getOwner = false) { $qb = $this->getCirclesSelectSql(); + if ($getOwner) { + $this->leftJoinOwner($qb); + } $this->limitToName($qb, $name); @@ -350,6 +354,28 @@ public function getCircleFromUniqueId($uniqueId) { return $entry; } - - + + /** + * @param string $uniqueId + * + * @return Circle + * @throws CircleDoesNotExistException + */ + public function getCircleFromShortenUniqueId($circleUniqueId) { + $qb = $this->getCirclesSelectSql(); + $this->leftJoinOwner($qb); + $this->limitToShortenUniqueId($qb, $circleUniqueId, Circle::SHORT_UNIQUE_ID_LENGTH); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new CircleDoesNotExistException($this->l10n->t('Circle not found')); + } + + $entry = $this->parseCirclesSelectSql($data); + + return $entry; + } } \ No newline at end of file diff --git a/lib/Events/UserEvents.php b/lib/Events/UserEvents.php index 8b8986a94..2dc59d3c6 100644 --- a/lib/Events/UserEvents.php +++ b/lib/Events/UserEvents.php @@ -8,6 +8,10 @@ use OCA\Circles\Service\GroupsService; use OCA\Circles\Service\MembersService; use OCA\Circles\Service\MiscService; +use OCP\IUser; +use OC\Files\View; +use OCA\Circles\Api\v1\Circles; +use OCP\Activity\IEvent; class UserEvents { @@ -22,6 +26,9 @@ class UserEvents { /** @var MiscService */ private $miscService; + + /** @var IUser */ + private static $user; /** * UserEvents constructor. @@ -59,6 +66,185 @@ public function onGroupDeleted(array $params) { $groupId = $params['gid']; $this->groupsService->onGroupRemoved($groupId); } - -} - + + /** + * + * @param array $params + */ + public function onItemShared(array $params) { + $shareWith = $params['shareWith']; + $fileTarget = $params['fileTarget']; + $user = $this->getUser()->getDisplayName(); + $affectedUsers = []; + $owner = null; + try { + $path = Circles::getViewPath($params['nodeId']); + } catch (NotFoundException $e) { + $path = \OC::$server->getURLGenerator()->getBaseUrl() . '/apps/files'; + } + try { + $circle = $this->circlesService->infoCircleByName($shareWith, true, true); + $shareWith = $circle->getName(); + $affectedUsers = function ($circle) { + foreach ($circle->getMembers() as $member){ + yield $member->getDisplayName(); + } + }; + $owner = $circle->getOwner(); + } catch ( \Exception $e ) { + $circle = $this->circlesService->detailsCircle($shareWith, true); + $shareWith = $circle->getName(); + $affectedUsers = function ($circle) { + foreach ($circle->getMembers() as $member){ + yield $member->getDisplayName(); + } + }; + $owner = $circle->getOwner(); + } + $this->miscService->log("user $user shared $fileTarget with $shareWith"); + + $subjectParams = [ + 'author' => [ + 'id' => $this->getUser()->getUID(), + 'name' => $user + ], + 'circle' => [ + 'name' => $shareWith + ], + 'file' => [ + 'id' => $params['nodeId'], + 'name' => $fileTarget, + 'type' => $params['itemType'] + ] + ]; + $objectType = ($params ['id']) ? 'files' : ''; + $link = \OC::$server->getURLGenerator ()->linkToRouteAbsolute('files.view.index', array ( + 'dir' => ($params['itemType'] !== 'file') ? dirname($path) : $path + ) ); + $event = \OC::$server->getActivityManager()->generateEvent('shared'); + $event->setApp('files_sharing') + ->setType('shared') + ->setAffectedUser($this->getUser()->getDisplayName()) + ->setTimestamp(time()) + ->setSubject('shared_circle_self', $subjectParams) + ->setParsedSubject("$user shared $fileTarget with the circle $shareWith") + ->setObject($objectType,(int) $params ['id'], $fileTarget ) + ->setLink($link); + if (!is_array($affectedUsers)){ + $affectedUsers = [$this->getUser()->getDisplayName()]; + } + if ($owner !== null && !in_array($owner->getDisplayName(), $affectedUsers)) { + $affectedUsers[] = $owner->getDisplayName(); + } + if (!in_array('admin', $affectedUsers)) { + $affectedUsers[] = 'admin'; + } + $this->publishEvent($event, $affectedUsers); + } + + /** + * + * @param array $params + */ + public function onItemUnshared(array $params) { + $shareWith = $params ['shareWith']; + $fileTarget = $params ['fileTarget']; + $user = $this->getUser()->getDisplayName (); + $affectedUsers = []; + $owner = null; + try { + $circle = $this->circlesService->infoCircleByName($shareWith, true, true); + $shareWith = $circle->getName(); + $affectedUsers = function ($circle) { + foreach ($circle->getMembers() as $member){ + yield $member->getDisplayName(); + } + }; + $owner = $circle->getOwner(); + } catch ( \Exception $e ) { + try { + $circle = $this->circlesService->detailsCircle($shareWith, true); + $shareWith = $circle->getName(); + $affectedUsers = function ($circle) { + foreach ($circle->getMembers() as $member){ + yield $member->getDisplayName(); + } + }; + $owner = $circle->getOwner(); + } catch (\Exception $e) { + $circle = $this->circlesService->infoCircleByUniqueId($shareWith, true); + $shareWith = $circle->getName(); + $affectedUsers = function ($circle) { + foreach ($circle->getMembers() as $member){ + yield $member->getDisplayName(); + } + }; + $owner = $circle->getOwner(); + } + } + $this->miscService->log ( "user $user unshared $fileTarget with $shareWith"); + try { + $path = Circles::getViewPath($params['nodeId']); + } catch ( NotFoundException $e ) { + $path = \OC::$server->getURLGenerator()->getBaseUrl() . '/apps/files'; + } + $subjectParams = [ + 'author' => [ + 'id' => $this->getUser()->getUID(), + 'name' => $user + ], + 'circle' => [ + 'name' => $shareWith + ], + 'file' => [ + 'id' => $params['nodeId'], + 'name' => $fileTarget, + 'type' => $params['itemType'] + ] + ]; + $objectType = ($params ['id']) ? 'files' : ''; + $link = \OC::$server->getURLGenerator ()->linkToRouteAbsolute ( 'files.view.index', array ( + 'dir' => ($params ['itemType'] !== 'file') ? dirname ( $path ) : $path + ) ); + $event = \OC::$server->getActivityManager ()->generateEvent ('shared'); + $event->setApp('files_sharing') + ->setType('shared') + ->setTimestamp(time()) + ->setSubject('unshared_circle_self', $subjectParams ) + ->setParsedSubject("$user unshared $fileTarget with the circle $shareWith") + ->setObject($objectType,(int)$params ['id'], $params ['fileTarget']) + ->setLink ( $link ); + if (!is_array($affectedUsers)){ + $affectedUsers = [$this->getUser()->getDisplayName()]; + } + if ($owner !== null && !in_array($owner->getDisplayName(), $affectedUsers)) { + $affectedUsers[] = $owner->getDisplayName(); + } + if (!in_array('admin', $affectedUsers)) { + $affectedUsers[] = 'admin'; + } + $this->publishEvent($event, $affectedUsers); + } + + /** + * + * @return User + */ + private function getUser() { + if (self::$user == null) { + self::$user = \OC::$server->getUserSession()->getUser(); + } + return self::$user; + } + + /** + * @param IEvent $event + * @param array $affectedUsers + */ + private function publishEvent(IEvent $event, array $affectedUsers) { + foreach($affectedUsers as $affectedUser) { + $event->setAffectedUser($affectedUser); + \OC::$server->getActivityManager()->publish($event); + } + } +} \ No newline at end of file diff --git a/lib/Hooks/UserHooks.php b/lib/Hooks/UserHooks.php index eb8e7e6fe..8d2fa5dad 100644 --- a/lib/Hooks/UserHooks.php +++ b/lib/Hooks/UserHooks.php @@ -22,11 +22,18 @@ public static function onUserDeleted($params) { ->onUserDeleted($params); } - public static function onGroupDeleted($params) { self::getController() ->onGroupDeleted($params); } -} + public static function onItemShared($params) { + self::getController() + ->onItemShared($params); + } + public static function onItemUnshared($params) { + self::getController() + ->onItemUnshared($params); + } +} \ No newline at end of file diff --git a/lib/Model/BaseMember.php b/lib/Model/BaseMember.php index a24ecfba1..a30c00484 100644 --- a/lib/Model/BaseMember.php +++ b/lib/Model/BaseMember.php @@ -277,20 +277,7 @@ public function jsonSerialize() { } public function getLevelString() { - switch ($this->getLevel()) { - case self::LEVEL_NONE: - return 'Not a member'; - case self::LEVEL_MEMBER: - return 'Member'; - case self::LEVEL_MODERATOR: - return 'Moderator'; - case self::LEVEL_ADMIN: - return 'Admin'; - case self::LEVEL_OWNER: - return 'Owner'; - } - - return 'none'; + return self::getLevelStringFromCode($this->getLevel()); } @@ -308,4 +295,25 @@ public function getTypeString() { return 'none'; } + + /** + * @param integer $code + * @return string + */ + public static function getLevelStringFromCode($code) { + switch ($code) { + case self::LEVEL_NONE: + return 'Not a member'; + case self::LEVEL_MEMBER: + return 'Member'; + case self::LEVEL_MODERATOR: + return 'Moderator'; + case self::LEVEL_ADMIN: + return 'Admin'; + case self::LEVEL_OWNER: + return 'Owner'; + } + + return 'none'; + } } diff --git a/lib/Service/BaseService.php b/lib/Service/BaseService.php new file mode 100644 index 000000000..50ffe7e0a --- /dev/null +++ b/lib/Service/BaseService.php @@ -0,0 +1,45 @@ + + * + * @copyright 2018 + * @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 OC\User\User; + +abstract class BaseService { + protected static $user = null; + + /** + * @return User + */ + protected function getUser() + { + if (self::$user == null){ + self::$user = \OC::$server->getUserSession()->getUser(); + } + return self::$user; + } +} \ No newline at end of file diff --git a/lib/Service/CirclesService.php b/lib/Service/CirclesService.php index 2ecf9b098..ef033e6aa 100644 --- a/lib/Service/CirclesService.php +++ b/lib/Service/CirclesService.php @@ -49,8 +49,9 @@ use OCA\Circles\Model\Member; use OCP\IGroupManager; use OCP\IL10N; +use OCP\Util; -class CirclesService { +class CirclesService extends BaseService { /** @var string */ private $userId; @@ -159,6 +160,9 @@ public function createCircle($type, $name) { try { $this->circlesRequest->createCircle($circle, $this->userId); $this->membersRequest->createMember($circle->getOwner()); + + $owner = $circle->getOwner()->getDisplayName(); + $this->miscService->log("user $owner created circle $name"); } catch (CircleAlreadyExistsException $e) { throw $e; } @@ -208,17 +212,18 @@ public function listCircles($userId, $type, $name = '', $level = 0) { * returns details on circle and its members if this->userId is a member itself. * * @param string $circleUniqueId - * + * @param boolean $getMembers * @return Circle * @throws \Exception */ - public function detailsCircle($circleUniqueId) { + public function detailsCircle($circleUniqueId, $getMembers = false) { try { $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); if ($this->viewerIsAdmin() || $circle->getHigherViewer() - ->isLevel(Member::LEVEL_MEMBER) + ->isLevel(Member::LEVEL_MEMBER + || $getMembers) ) { $this->detailsCircleMembers($circle); $this->detailsCircleLinkedGroups($circle); @@ -243,6 +248,7 @@ private function detailsCircleMembers(Circle &$circle) { if ($this->viewerIsAdmin()) { $members = $this->membersRequest->forceGetMembers($circle->getUniqueId()); } else { + $this->higherViewerCircle($circle); $members = $this->membersRequest->getMembers( $circle->getUniqueId(), $circle->getHigherViewer() ); @@ -310,15 +316,22 @@ public function settingsCircle($circleUniqueId, $settings) { if (!$this->viewerIsAdmin()) { $settings['members_limit'] = $circle->getSetting('members_limit'); } - + $formerCircleName = $circle->getName(); $ak = array_keys($settings); + $changes = ''; foreach ($ak AS $k) { + if (trim($circle->getSetting($k)) !== trim($settings[$k])){ + $changes .= ((empty($changes) ? '' : ' and ') . "$k to {$settings[$k]}"); + } $circle->setSetting($k, $settings[$k]); } $this->circlesRequest->updateCircle($circle, $this->userId); $this->eventsService->onSettingsChange($circle); + + $user = $this->getUser()->getDisplayName(); + $this->miscService->log("user $user updated circle $formerCircleName changing $changes"); } catch (\Exception $e) { throw $e; } @@ -343,6 +356,7 @@ public function joinCircle($circleUniqueId) { $member = $this->membersRequest->getFreshNewMember( $circleUniqueId, $this->userId, Member::TYPE_USER ); + $formerStatus = $member->getStatus(); $member->hasToBeAbleToJoinTheCircle(); $this->checkThatCircleIsNotFull($circle); @@ -350,6 +364,17 @@ public function joinCircle($circleUniqueId) { $this->membersRequest->updateMember($member); $this->eventsService->onMemberNew($circle, $member); + + $circleName = $circle->getName(); + $circleType = $circle->getType(); + $memberName = $member->getDisplayName(); + if ($formerStatus == Member::STATUS_INVITED){ + $this->miscService->log("member $memberName accepted invitation to circle $circleName"); + } else if ($circle->getType() == Circle::CIRCLES_CLOSED) { + $this->miscService->log("member $memberName requested to join circle $circleName"); + } else { + $this->miscService->log("member $memberName joined circle $circleName"); + } } catch (\Exception $e) { throw $e; } @@ -374,11 +399,23 @@ public function leaveCircle($circleUniqueId) { $member->hasToBeMemberOrAlmost(); $member->cantBeOwner(); + $circleName = $circle->getName(); + $formerStatus = $member->getStatus(); + $memberName = $member->getDisplayName(); + $this->eventsService->onMemberLeaving($circle, $member); $this->membersRequest->removeMember($member); $this->sharesRequest->removeSharesFromMember($member); + if ($formerStatus == Member::STATUS_INVITED){ + $this->miscService->log("member $memberName refused invitation to circle $circleName"); + } else if ($circle->getType() == Circle::CIRCLES_CLOSED) { + $this->miscService->log("member $memberName cancelled invitation from circle $circleName"); + } else { + $this->miscService->log("member $memberName left circle $circleName"); + } + return $member; } @@ -402,17 +439,27 @@ public function removeCircle($circleUniqueId) { $this->membersRequest->removeAllFromCircle($circleUniqueId); $this->circlesRequest->destroyCircle($circleUniqueId); + + $circleName = $circle->getName(); + $user = $this->getUser()->getDisplayName(); + $this->miscService->log("user $user destroyed circle $circleName"); } /** - * @param $circleName + * @param string $circleName + * @param boolean $getOwner + * @param boolean $getMembers * * @return Circle|null * @throws CircleDoesNotExistException */ - public function infoCircleByName($circleName) { - return $this->circlesRequest->forceGetCircleByName($circleName); + public function infoCircleByName($circleName, $getOwner = false, $getMembers = false) { + $circle = $this->circlesRequest->forceGetCircleByName($circleName, $getOwner); + if ($getMembers){ + $this->detailsCircleMembers($circle); + } + return $circle; } @@ -557,7 +604,7 @@ public function getFilesForCircles($circleUniqueIds, $limit = -1, $offset = 0) { public function checkThatCircleIsNotFull(Circle $circle) { $members = $this->membersRequest->forceGetMembers( - $circle->getUniqueId(), Member::STATUS_MEMBER, true + $circle->getUniqueId(), Member::LEVEL_MEMBER, true ); $limit = $circle->getSetting('members_limit'); @@ -620,4 +667,33 @@ public function hasToBeAdmin(Member $member) { ); } } -} \ No newline at end of file + + /** + * @param string $uniqueId + * @param boolean $getMembers + * + * @return Circle|null + * @throws CircleDoesNotExistException + */ + public function infoCircleByUniqueId($circleUniqueId, $getMembers = false) { + $circle = $this->circlesRequest->getCircleFromShortenUniqueId($circleUniqueId); + if ($this->viewerIsAdmin() || $getMembers) { + $this->higherViewerCircle($circle); + $this->detailsCircleMembers($circle); + } + return $circle; + } + + /** + * @param Circle $circle + * @return Circle + */ + private function higherViewerCircle(Circle &$circle) { + if ($circle->getHigherViewer() === null) { + $member = new Member(); + $member->setLevel(Member::LEVEL_MEMBER); + $circle->setViewer($member); + } + return $circle; + } +} diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index 93eb703ec..c9e365631 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -41,6 +41,7 @@ class ConfigService { const CIRCLES_ALLOW_LINKED_GROUPS = 'allow_linked_groups'; const CIRCLES_ALLOW_NON_SSL_LINKS = 'allow_non_ssl_links'; const CIRCLES_NON_SSL_LOCAL = 'local_is_non_ssl'; + const CIRCLES_DISABLE_NOTIFICATION_FOR_SEEN_USERS = 'disable_notification_for_seen_users'; const CIRCLES_TEST_ASYNC_LOCK = 'test_async_lock'; const CIRCLES_TEST_ASYNC_INIT = 'test_async_init'; @@ -55,7 +56,8 @@ class ConfigService { 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_NON_SSL_LOCAL => '0', + self::CIRCLES_DISABLE_NOTIFICATION_FOR_SEEN_USERS => '0', ]; /** @var string */ @@ -87,6 +89,9 @@ class ConfigService { /** @var int */ private $localNonSSL = -1; + + /** @var int */ + private $disabledNotificationForSeenUsers = -1; /** * ConfigService constructor. @@ -181,6 +186,17 @@ public function isNonSSLLinksAllowed() { return ($this->allowedNonSSLLinks === 1); } + /** + * @return bool + */ + public function isDisabledNotificationForSeenUsers() { + if ($this->disabledNotificationForSeenUsers === -1) { + $this->disabledNotificationForSeenUsers = + (int)$this->getAppValue(self::CIRCLES_DISABLE_NOTIFICATION_FOR_SEEN_USERS); + } + + return ($this->disabledNotificationForSeenUsers === 1); + } /** * @param string $remote diff --git a/lib/Service/EventsService.php b/lib/Service/EventsService.php index 4e4917696..ace02ed5c 100644 --- a/lib/Service/EventsService.php +++ b/lib/Service/EventsService.php @@ -100,21 +100,34 @@ public function __construct( * @param Circle $circle */ public function onCircleCreation(Circle $circle) { + $event = $this->generateEvent('circles_as_member'); + $event->setSubject('circle_create', ['circle' => json_encode($circle)]); + if ($circle->getType() !== Circle::CIRCLES_PUBLIC && $circle->getType() !== Circle::CIRCLES_CLOSED ) { + $this->publishEvent($event, [ + \OC::$server->getUserSession()->getUser(), + \OC::$server->getUserManager()->get('admin') + ]); + $this->dispatch('\OCA\Circles::onCircleCreation', ['circle' => $circle]); return; } - $event = $this->generateEvent('circles_as_member'); - $event->setSubject('circle_create', ['circle' => json_encode($circle)]); - - $this->userManager->callForSeenUsers( - function($user) use ($event) { - /** @var IUser $user */ - $this->publishEvent($event, [$user]); - } - ); + $disableNotificationForSeenUsers = \OC::$server->getAppConfig()->getValue('circles', 'disable_notification_for_seen_users', false); + if ($disableNotificationForSeenUsers) { + $this->publishEvent($event, [ + \OC::$server->getUserSession()->getUser(), + \OC::$server->getUserManager()->get('admin') + ]); + } else { + $this->userManager->callForSeenUsers( + function($user) use ($event) { + /** @var IUser $user */ + $this->publishEvent($event, [$user]); + } + ); + } $this->dispatch('\OCA\Circles::onCircleCreation', ['circle' => $circle]); } @@ -130,12 +143,16 @@ function($user) use ($event) { * @param Circle $circle */ public function onCircleDestruction(Circle $circle) { + $event = $this->generateEvent('circles_as_member'); + $event->setSubject('circle_delete', ['circle' => json_encode($circle)]); + + $this->publishEvent($event, [\OC::$server->getUserManager()->get('admin')]); if ($circle->getType() === Circle::CIRCLES_PERSONAL) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onCircleDestruction', ['circle' => $circle]); return; } - $event = $this->generateEvent('circles_as_member'); - $event->setSubject('circle_delete', ['circle' => json_encode($circle)]); $this->publishEvent( $event, $this->membersRequest->forceGetMembers( @@ -160,31 +177,32 @@ public function onCircleDestruction(Circle $circle) { * @param Member $member */ public function onMemberNew(Circle $circle, Member $member) { + $event = $this->generateEvent('circles_as_member'); + $event->setSubject( + ($this->userId === $member->getUserId()) ? 'member_join' : 'member_add', + ['circle' => json_encode($circle), 'member' => json_encode($member)] + ); + + $this->publishEvent($event, [\OC::$server->getUserManager()->get('admin')]); if ($member->getLevel() === Member::LEVEL_OWNER || $circle->getType() === Circle::CIRCLES_PERSONAL ) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onMemberNew', ['circle' => $circle, 'member' => $member]); return; } if ($member->getLevel() === Member::LEVEL_NONE) { $this->onMemberAlmost($circle, $member); - + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onMemberNew', ['circle' => $circle, 'member' => $member]); return; } - $event = $this->generateEvent('circles_as_member'); - $event->setSubject( - ($this->userId === $member->getUserId()) ? 'member_join' : 'member_add', - ['circle' => json_encode($circle), 'member' => json_encode($member)] - ); - $this->publishEvent( - $event, array_merge( - [$member], - $this->membersRequest->forceGetMembers( - $circle->getUniqueId(), Member::LEVEL_MODERATOR, true - ) - ) + $event, $this->membersRequest->forceGetMembers( + $circle->getUniqueId(), Member::LEVEL_MEMBER, true + ) ); $this->dispatch('\OCA\Circles::onMemberNew', ['circle' => $circle, 'member' => $member]); } @@ -225,20 +243,22 @@ private function onMemberAlmost(Circle $circle, Member $member) { * @param Member $member */ private function onMemberInvited(Circle $circle, Member $member) { - if ($circle->getType() !== Circle::CIRCLES_CLOSED) { - return; - } - $event = $this->generateEvent('circles_as_moderator'); $event->setSubject( 'member_invited', ['circle' => json_encode($circle), 'member' => json_encode($member)] ); + if ($circle->getType() !== Circle::CIRCLES_CLOSED) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onMemberInvited', ['circle' => $circle, 'member' => $member]); + return; + } + $this->publishEvent( $event, array_merge( [$member], $this->membersRequest->forceGetMembers( - $circle->getUniqueId(), Member::LEVEL_MODERATOR, true + $circle->getUniqueId(), Member::LEVEL_MEMBER, true ) ) ); @@ -256,16 +276,18 @@ private function onMemberInvited(Circle $circle, Member $member) { * @param Member $member */ private function onMemberRequesting(Circle $circle, Member $member) { - if ($circle->getType() !== Circle::CIRCLES_CLOSED) { - return; - } - $event = $this->generateEvent('circles_as_moderator'); $event->setSubject( - 'member_request_invitation', - ['circle' => json_encode($circle), 'member' => json_encode($member)] + 'member_request_invitation', + ['circle' => json_encode($circle), 'member' => json_encode($member)] ); + if ($circle->getType() !== Circle::CIRCLES_CLOSED) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onMemberRequesting', ['circle' => $circle, 'member' => $member]); + return; + } + $this->publishEvent( $event, array_merge( [$member], @@ -289,16 +311,19 @@ private function onMemberRequesting(Circle $circle, Member $member) { * @param Member $member */ public function onMemberLeaving(Circle $circle, Member $member) { - if ($circle->getType() === Circle::CIRCLES_PERSONAL) { - return; - } - $event = $this->generateEvent('circles_as_member'); $event->setSubject( - ($this->userId === $member->getUserId()) ? 'member_left' : 'member_remove', - ['circle' => json_encode($circle), 'member' => json_encode($member)] + ($this->userId === $member->getUserId()) ? 'member_left' : 'member_remove', + ['circle' => json_encode($circle), 'member' => json_encode($member)] ); + $this->publishEvent($event, [\OC::$server->getUserManager()->get('admin')]); + if ($circle->getType() === Circle::CIRCLES_PERSONAL) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onMemberLeaving', ['circle' => $circle, 'member' => $member]); + return; + } + $this->publishEvent( $event, array_merge( [$member], @@ -322,18 +347,19 @@ public function onMemberLeaving(Circle $circle, Member $member) { * @param Member $member */ public function onMemberLevel(Circle $circle, Member $member) { + $event = $this->generateEvent('circles_as_moderator'); + $event->setSubject( + 'member_level', + ['circle' => json_encode($circle), 'member' => json_encode($member)] + ); + if ($member->getLevel() === Member::LEVEL_OWNER) { $this->onMemberOwner($circle, $member); - + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onMemberLevel', ['circle' => $circle, 'member' => $member]); return; } - $event = $this->generateEvent('circles_as_moderator'); - $event->setSubject( - 'member_level', - ['circle' => json_encode($circle), 'member' => json_encode($member)] - ); - $mods = $this->membersRequest->forceGetMembers( $circle->getUniqueId(), Member::LEVEL_MODERATOR, true ); @@ -380,16 +406,18 @@ public function onMemberOwner(Circle $circle, Member $member) { * @param Member $group */ public function onGroupLink(Circle $circle, Member $group) { - if ($circle->getType() === Circle::CIRCLES_PERSONAL) { - return; - } - $event = $this->generateEvent('circles_as_moderator'); $event->setSubject( - 'group_link', - ['circle' => json_encode($circle), 'group' => json_encode($group)] + 'group_link', + ['circle' => json_encode($circle), 'group' => json_encode($group)] ); + if ($circle->getType() === Circle::CIRCLES_PERSONAL) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onGroupLink', ['circle' => $circle, 'group' => $group]); + return; + } + $mods = $this->membersRequest->forceGetMembers( $circle->getUniqueId(), Member::LEVEL_MODERATOR, true ); @@ -413,16 +441,18 @@ public function onGroupLink(Circle $circle, Member $group) { * @param Member $group */ public function onGroupUnlink(Circle $circle, Member $group) { - if ($circle->getType() === Circle::CIRCLES_PERSONAL) { - return; - } - $event = $this->generateEvent('circles_as_moderator'); $event->setSubject( 'group_unlink', ['circle' => json_encode($circle), 'group' => json_encode($group)] ); + if ($circle->getType() === Circle::CIRCLES_PERSONAL) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onGroupUnlink', ['circle' => $circle, 'group' => $group]); + return; + } + $mods = $this->membersRequest->forceGetMembers( $circle->getUniqueId(), Member::LEVEL_MODERATOR, true ); @@ -446,15 +476,17 @@ public function onGroupUnlink(Circle $circle, Member $group) { * @param Member $group */ public function onGroupLevel(Circle $circle, Member $group) { - if ($circle->getType() === Circle::CIRCLES_PERSONAL) { - return; - } - $event = $this->generateEvent('circles_as_moderator'); $event->setSubject( 'group_level', ['circle' => json_encode($circle), 'group' => json_encode($group)] ); + + if ($circle->getType() === Circle::CIRCLES_PERSONAL) { + $this->publishEvent($event, [\OC::$server->getUserSession()->getUser()]); + $this->dispatch('\OCA\Circles::onGroupLevel', ['circle' => $circle, 'group' => $group]); + return; + } $mods = $this->membersRequest->forceGetMembers( $circle->getUniqueId(), Member::LEVEL_MODERATOR, true @@ -698,8 +730,8 @@ public function onLinkRemove(Circle $circle, FederatedLink $link) { $this->publishEvent( $event, $this->membersRequest->forceGetMembers( - $link->getCircleId(), Member::LEVEL_MODERATOR, true - ) + $link->getCircleId(), Member::LEVEL_MODERATOR, true + ) ); $this->dispatch('\OCA\Circles::onLinkRemove', ['circle' => $circle, 'link' => $link]); } @@ -712,6 +744,19 @@ public function onLinkRemove(Circle $circle, FederatedLink $link) { * @param Circle $circle */ public function onSettingsChange(Circle $circle) { + $event = $this->generateEvent('circles_as_moderator'); + $event->setSubject('circle_change',['circle' => json_encode($circle)]); + $users = $circle->getMembers(); + $owner = $circle->getOwner()->getDisplayName(); + if ($users === null) { + $users = [$circle->getOwner()]; + } + if ($owner !== 'admin' && $circle->getType() !== Circle::CIRCLES_SECRET){ + $admin = new Member(); + $admin->setUserId('admin'); + $users[] = $admin; + } + $this->publishEvent($event, $users); $this->dispatch('\OCA\Circles::onSettingsChange', ['circle' => $circle]); } diff --git a/lib/Service/MembersService.php b/lib/Service/MembersService.php index 836eafcfd..4619a9027 100644 --- a/lib/Service/MembersService.php +++ b/lib/Service/MembersService.php @@ -41,8 +41,9 @@ use OCA\Circles\Model\Member; use OCP\IL10N; use OCP\IUserManager; +use OCP\Util; -class MembersService { +class MembersService extends BaseService { /** @var string */ private $userId; @@ -125,10 +126,15 @@ public function addMember($circleUniqueId, $ident, $type) { $circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId); $circle->getHigherViewer() ->hasToBeModerator(); - + if (!$this->addMassiveMembers($circle, $ident, $type)) { $this->addSingleMember($circle, $ident, $type); } + + $action = ($type == Circle::CIRCLES_CLOSED ? 'invited' : 'added'); + $circleName = $circle->getName(); + $user = $this->getUser()->getDisplayName(); + $this->miscService->log("user $user $action member $ident to circle $circleName"); } catch (\Exception $e) { throw $e; } @@ -411,7 +417,12 @@ public function levelMember($circleUniqueId, $name, $type, $level) { $member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type); $member->levelHasToBeEditable(); $this->updateMemberLevel($circle, $member, $level); - + + $circleName = $circle->getName(); + $levelString = Member::getLevelStringFromCode($level); + $memberName = $member->getDisplayName(); + $user = $this->getUser()->getDisplayName(); + $this->miscService->log("$user changed level of $memberName from circle $circleName to $levelString"); return $this->membersRequest->getMembers( $circle->getUniqueId(), $circle->getHigherViewer() ); @@ -518,6 +529,11 @@ public function removeMember($circleUniqueId, $name, $type) { $circle->getHigherViewer() ->hasToBeHigherLevel($member->getLevel()); + + $user = $this->getUser()->getDisplayName(); + $memberName = $member->getDisplayName(); + $circleName = $circle->getName(); + $this->miscService->log("user $user removed member $memberName from circle $circleName"); } catch (\Exception $e) { throw $e; } diff --git a/lib/Settings/CirclesSettings.php b/lib/Settings/CirclesSettings.php index 7664f16f0..9f0c3ac5d 100644 --- a/lib/Settings/CirclesSettings.php +++ b/lib/Settings/CirclesSettings.php @@ -52,7 +52,7 @@ public function getForm() { * @return string */ public function getSection() { - return 'groupware'; + return 'additional'; } /** diff --git a/lib/ShareByCircleProvider.php b/lib/ShareByCircleProvider.php index 57ee8a5b8..b115ee7c9 100644 --- a/lib/ShareByCircleProvider.php +++ b/lib/ShareByCircleProvider.php @@ -59,7 +59,8 @@ use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IShare; use OCP\Share\IShareProvider; - +use OCP\Util; +use OCP\IUser; class ShareByCircleProvider extends CircleProviderRequest implements IShareProvider { @@ -84,7 +85,9 @@ class ShareByCircleProvider extends CircleProviderRequest implements IShareProvi /** @var MembersRequest */ private $membersRequest; - + /** $var IUser */ + private static $user = null; + /** * DefaultShareProvider constructor. * @@ -139,6 +142,8 @@ public function identifier() { * @throws \Exception */ public function create(IShare $share) { + $circle = null; + $shareId = null; try { $nodeId = $share->getNode() ->getId(); @@ -166,8 +171,34 @@ public function create(IShare $share) { '\OCA\Circles\Circles\FileSharingBroadcaster' ); + $user = $this->getUser()->getDisplayName(); + $target = $share->getTarget(); + $shareWith = $share->getSharedWith(); + $this->miscService->log("user $user shared $target with $shareWith"); + if ($this->getShareById($shareId)){ + Util::emitHook('OCP\Share', 'post_shared',[ + 'circle' => $circle->getName(), + 'fileTarget'=> $share->getTarget(), + 'id' => $share->getId(), + 'itemType' => $share->getNodeType(), + 'nodeId' => $share->getNodeId(), + 'shareType' => $share->getShareType(), + 'shareWith' => $circle->getName() + ]); + } return $this->getShareById($shareId); } catch (\Exception $e) { + if ($this->getShareById($shareId)){ + Util::emitHook('OCP\Share', 'post_shared',[ + 'circle' => $circle->getName(), + 'fileTarget'=> $share->getTarget(), + 'id' => $share->getId(), + 'itemType' => $share->getNodeType(), + 'nodeId' => $share->getNodeId(), + 'shareType' => $share->getShareType(), + 'shareWith' => $circle->getName() + ]); + } throw $e; } } @@ -189,7 +220,10 @@ public function update(IShare $share) { ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())); $qb->execute(); - + + $user = $this->getUser()->getDisplayName(); + $target = $share->getTarget(); + $this->miscService->log("user $user updated shared $target"); return $share; } @@ -203,6 +237,18 @@ public function delete(IShare $share) { $qb = $this->getBaseDeleteSql(); $this->limitToShareAndChildren($qb, $share->getId()); + + $user = $this->getUser()->getDisplayName(); + $target = $share->getTarget(); + $shareWith = $share->getSharedWith(); + Util::emitHook('OCP\Share', 'post_unshared',[ + 'fileTarget'=> $target, + 'id' => $share->getId(), + 'itemType' => $share->getNodeType(), + 'nodeId' => $share->getNodeId(), + 'shareType' => $share->getShareType(), + 'shareWith' => $shareWith + ]); $qb->execute(); } @@ -223,8 +269,24 @@ public function deleteFromSelf(IShare $share, $userId) { $this->limitToShare($qb, $childId); $qb->execute(); - } + try { + $shareWith = $this->circlesRequest->getCircle($share->getSharedWith(),$userId)->getName(); + } catch (\Exception $e) { + $shareWith = $share->getSharedWith(); + } + + $target = $share->getTarget(); + $user = $this->getUser()->getDisplayName(); + Util::emitHook('OCP\Share', 'post_unshared',[ + 'fileTarget'=> $target, + 'id' => $share->getId(), + 'itemType' => $share->getNodeType(), + 'nodeId' => $share->getNodeId(), + 'shareType' => $share->getShareType(), + 'shareWith' => $shareWith + ]); + } /** * Move a share as a recipient. @@ -765,4 +827,15 @@ private function shareObjectToArray(IShare $share) { 'password' => $share->getPassword() ]; } + + /** + * @return User + */ + private function getUser() { + if (self::$user == null){ + $app = new Application(); + self::$user = $app->getContainer()->query('UserSession')->getUser(); + } + return self::$user; + } } diff --git a/templates/navigate.php b/templates/navigate.php index 8eea34782..e39e875d5 100644 --- a/templates/navigate.php +++ b/templates/navigate.php @@ -44,7 +44,7 @@ ?> - + @@ -193,7 +193,7 @@ - + @@ -407,6 +407,11 @@ + + t('Disable notification for seen users.')); ?> + + + diff --git a/templates/settings.admin.php b/templates/settings.admin.php index c97834b2b..7aee96857 100644 --- a/templates/settings.admin.php +++ b/templates/settings.admin.php @@ -25,4 +25,9 @@ t('Allow federated circles')); ?> t('Circles from different Nextclouds can be linked together.')); ?> - + + + t('Disable notification for seen users.')); ?> + t('Disable notification for seen users.')); ?> + + \ No newline at end of file
+ + t('Disable notification for seen users.')); ?> + t('Disable notification for seen users.')); ?> +