diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php index ba49f2961f42e..9f12a92f4cdba 100644 --- a/apps/dav/appinfo/v1/caldav.php +++ b/apps/dav/appinfo/v1/caldav.php @@ -46,10 +46,10 @@ 'principals/' ); $db = \OC::$server->getDatabaseConnection(); -$config = \OC::$server->getConfig(); $userManager = \OC::$server->getUserManager(); $random = \OC::$server->getSecureRandom(); -$calDavBackend = new CalDavBackend($db, $principalBackend, $userManager, $config, $random); +$dispatcher = \OC::$server->getEventDispatcher(); +$calDavBackend = new CalDavBackend($db, $principalBackend, $userManager, $random, $dispatcher); $debugging = \OC::$server->getConfig()->getSystemValue('debug', false); diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 2bdcff9cf84a5..c777f5e5a352e 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -24,6 +24,8 @@ */ namespace OCA\DAV\AppInfo; +use OCA\DAV\CalDAV\Activity\Backend; +use OCA\DAV\CalDAV\Activity\Extension; use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\Capabilities; use OCA\DAV\CardDAV\ContactsManager; @@ -88,6 +90,67 @@ public function registerHooks() { ); } }); + + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', function(GenericEvent $event) { + /** @var Backend $backend */ + $backend = $this->getContainer()->query(Backend::class); + $backend->onCalendarAdd( + $event->getArgument('calendarData') + ); + }); + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', function(GenericEvent $event) { + /** @var Backend $backend */ + $backend = $this->getContainer()->query(Backend::class); + $backend->onCalendarUpdate( + $event->getArgument('calendarData'), + $event->getArgument('shares'), + $event->getArgument('propertyMutations') + ); + }); + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', function(GenericEvent $event) { + /** @var Backend $backend */ + $backend = $this->getContainer()->query(Backend::class); + $backend->onCalendarDelete( + $event->getArgument('calendarData'), + $event->getArgument('shares') + ); + }); + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function(GenericEvent $event) { + /** @var Backend $backend */ + $backend = $this->getContainer()->query(Backend::class); + $backend->onCalendarUpdateShares( + $event->getArgument('calendarData'), + $event->getArgument('shares'), + $event->getArgument('add'), + $event->getArgument('remove') + ); + }); + + $listener = function(GenericEvent $event, $eventName) { + /** @var Backend $backend */ + $backend = $this->getContainer()->query(Backend::class); + + $subject = Extension::SUBJECT_OBJECT_ADD; + if ($eventName === '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject') { + $subject = Extension::SUBJECT_OBJECT_UPDATE; + } else if ($eventName === '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') { + $subject = Extension::SUBJECT_OBJECT_DELETE; + } + $backend->onTouchCalendarObject( + $subject, + $event->getArgument('calendarData'), + $event->getArgument('shares'), + $event->getArgument('objectData') + ); + }; + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', $listener); + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', $listener); + $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', $listener); + + $aM = $this->getContainer()->getServer()->getActivityManager(); + $aM->registerExtension(function() { + return $this->getContainer()->query(Extension::class); + }); } public function getSyncService() { diff --git a/apps/dav/lib/CalDAV/Activity/Backend.php b/apps/dav/lib/CalDAV/Activity/Backend.php new file mode 100644 index 0000000000000..6cf09f6de468a --- /dev/null +++ b/apps/dav/lib/CalDAV/Activity/Backend.php @@ -0,0 +1,466 @@ + + * + * @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\DAV\CalDAV\Activity; + + +use OCP\Activity\IEvent; +use OCP\Activity\IManager as IActivityManager; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserSession; +use Sabre\VObject\Reader; + +/** + * Class Backend + * + * @package OCA\DAV\CalDAV\Activity + */ +class Backend { + + /** @var IActivityManager */ + protected $activityManager; + + /** @var IGroupManager */ + protected $groupManager; + + /** @var IUserSession */ + protected $userSession; + + /** + * @param IActivityManager $activityManager + * @param IGroupManager $groupManager + * @param IUserSession $userSession + */ + public function __construct(IActivityManager $activityManager, IGroupManager $groupManager, IUserSession $userSession) { + $this->activityManager = $activityManager; + $this->groupManager = $groupManager; + $this->userSession = $userSession; + } + + /** + * Creates activities when a calendar was creates + * + * @param array $calendarData + */ + public function onCalendarAdd(array $calendarData) { + $this->triggerCalendarActivity(Extension::SUBJECT_ADD, $calendarData); + } + + /** + * Creates activities when a calendar was updated + * + * @param array $calendarData + * @param array $shares + * @param array $properties + */ + public function onCalendarUpdate(array $calendarData, array $shares, array $properties) { + $this->triggerCalendarActivity(Extension::SUBJECT_UPDATE, $calendarData, $shares, $properties); + } + + /** + * Creates activities when a calendar was deleted + * + * @param array $calendarData + * @param array $shares + */ + public function onCalendarDelete(array $calendarData, array $shares) { + $this->triggerCalendarActivity(Extension::SUBJECT_DELETE, $calendarData, $shares); + } + + /** + * Creates activities for all related users when a calendar was touched + * + * @param string $action + * @param array $calendarData + * @param array $shares + * @param array $changedProperties + */ + protected function triggerCalendarActivity($action, array $calendarData, array $shares = [], array $changedProperties = []) { + if (!isset($calendarData['principaluri'])) { + return; + } + + $principal = explode('/', $calendarData['principaluri']); + $owner = $principal[2]; + + $currentUser = $this->userSession->getUser(); + if ($currentUser instanceof IUser) { + $currentUser = $currentUser->getUID(); + } else { + $currentUser = $owner; + } + + $event = $this->activityManager->generateEvent(); + $event->setApp('dav') + ->setObject(Extension::CALENDAR, $calendarData['id']) + ->setType(Extension::CALENDAR) + ->setAuthor($currentUser); + + $changedVisibleInformation = array_intersect([ + '{DAV:}displayname', + '{http://apple.com/ns/ical/}calendar-color' + ], array_keys($changedProperties)); + + if (empty($shares) || ($action === Extension::SUBJECT_UPDATE && empty($changedVisibleInformation))) { + $users = [$owner]; + } else { + $users = $this->getUsersForShares($shares); + $users[] = $owner; + } + + foreach ($users as $user) { + $event->setAffectedUser($user) + ->setSubject( + $user === $currentUser ? $action . '_self' : $action, + [ + $currentUser, + $calendarData['{DAV:}displayname'], + ] + ); + $this->activityManager->publish($event); + } + } + + /** + * Creates activities for all related users when a calendar was (un-)shared + * + * @param array $calendarData + * @param array $shares + * @param array $add + * @param array $remove + */ + public function onCalendarUpdateShares(array $calendarData, array $shares, array $add, array $remove) { + $principal = explode('/', $calendarData['principaluri']); + $owner = $principal[2]; + + $currentUser = $this->userSession->getUser(); + if ($currentUser instanceof IUser) { + $currentUser = $currentUser->getUID(); + } else { + $currentUser = $owner; + } + + $event = $this->activityManager->generateEvent(); + $event->setApp('dav') + ->setObject(Extension::CALENDAR, $calendarData['id']) + ->setType(Extension::CALENDAR) + ->setAuthor($currentUser); + + foreach ($remove as $principal) { + // principal:principals/users/test + $parts = explode(':', $principal, 2); + if ($parts[0] !== 'principal') { + continue; + } + $principal = explode('/', $parts[1]); + + if ($principal[1] === 'users') { + $this->triggerActivityUser( + $principal[2], + $event, + $calendarData, + Extension::SUBJECT_UNSHARE_USER, + Extension::SUBJECT_DELETE . '_self' + ); + + if ($owner !== $principal[2]) { + $parameters = [ + $principal[2], + $calendarData['{DAV:}displayname'], + ]; + + if ($owner === $event->getAuthor()) { + $subject = Extension::SUBJECT_UNSHARE_USER . '_you'; + } else if ($principal[2] === $event->getAuthor()) { + $subject = Extension::SUBJECT_UNSHARE_USER . '_self'; + } else { + $event->setAffectedUser($event->getAuthor()) + ->setSubject(Extension::SUBJECT_UNSHARE_USER . '_you', $parameters); + $this->activityManager->publish($event); + + $subject = Extension::SUBJECT_UNSHARE_USER . '_by'; + $parameters[] = $event->getAuthor(); + } + + $event->setAffectedUser($owner) + ->setSubject($subject, $parameters); + $this->activityManager->publish($event); + } + } else if ($principal[1] === 'groups') { + $this->triggerActivityGroup($principal[2], $event, $calendarData, Extension::SUBJECT_UNSHARE_USER); + + $parameters = [ + $principal[2], + $calendarData['{DAV:}displayname'], + ]; + + if ($owner === $event->getAuthor()) { + $subject = Extension::SUBJECT_UNSHARE_GROUP . '_you'; + } else { + $event->setAffectedUser($event->getAuthor()) + ->setSubject(Extension::SUBJECT_UNSHARE_GROUP . '_you', $parameters); + $this->activityManager->publish($event); + + $subject = Extension::SUBJECT_UNSHARE_GROUP . '_by'; + $parameters[] = $event->getAuthor(); + } + + $event->setAffectedUser($owner) + ->setSubject($subject, $parameters); + $this->activityManager->publish($event); + } + } + + foreach ($add as $share) { + if ($this->isAlreadyShared($share['href'], $shares)) { + continue; + } + + // principal:principals/users/test + $parts = explode(':', $share['href'], 2); + if ($parts[0] !== 'principal') { + continue; + } + $principal = explode('/', $parts[1]); + + if ($principal[1] === 'users') { + $this->triggerActivityUser($principal[2], $event, $calendarData, Extension::SUBJECT_SHARE_USER); + + if ($owner !== $principal[2]) { + $parameters = [ + $principal[2], + $calendarData['{DAV:}displayname'], + ]; + + if ($owner === $event->getAuthor()) { + $subject = Extension::SUBJECT_SHARE_USER . '_you'; + } else { + $event->setAffectedUser($event->getAuthor()) + ->setSubject(Extension::SUBJECT_SHARE_USER . '_you', $parameters); + $this->activityManager->publish($event); + + $subject = Extension::SUBJECT_SHARE_USER . '_by'; + $parameters[] = $event->getAuthor(); + } + + $event->setAffectedUser($owner) + ->setSubject($subject, $parameters); + $this->activityManager->publish($event); + } + } else if ($principal[1] === 'groups') { + $this->triggerActivityGroup($principal[2], $event, $calendarData, Extension::SUBJECT_SHARE_USER); + + $parameters = [ + $principal[2], + $calendarData['{DAV:}displayname'], + ]; + + if ($owner === $event->getAuthor()) { + $subject = Extension::SUBJECT_SHARE_GROUP . '_you'; + } else { + $event->setAffectedUser($event->getAuthor()) + ->setSubject(Extension::SUBJECT_SHARE_GROUP . '_you', $parameters); + $this->activityManager->publish($event); + + $subject = Extension::SUBJECT_SHARE_GROUP . '_by'; + $parameters[] = $event->getAuthor(); + } + + $event->setAffectedUser($owner) + ->setSubject($subject, $parameters); + $this->activityManager->publish($event); + } + } + } + + /** + * Checks if a calendar is already shared with a principal + * + * @param string $principal + * @param array[] $shares + * @return bool + */ + protected function isAlreadyShared($principal, $shares) { + foreach ($shares as $share) { + if ($principal === $share['href']) { + return true; + } + } + + return false; + } + + /** + * Creates the given activity for all members of the given group + * + * @param string $gid + * @param IEvent $event + * @param array $properties + * @param string $subject + */ + protected function triggerActivityGroup($gid, IEvent $event, array $properties, $subject) { + $group = $this->groupManager->get($gid); + + if ($group instanceof IGroup) { + foreach ($group->getUsers() as $user) { + // Exclude current user + if ($user->getUID() !== $event->getAuthor()) { + $this->triggerActivityUser($user->getUID(), $event, $properties, $subject); + } + } + } + } + + /** + * Creates the given activity for the given user + * + * @param string $user + * @param IEvent $event + * @param array $properties + * @param string $subject + * @param string $subjectSelf + */ + protected function triggerActivityUser($user, IEvent $event, array $properties, $subject, $subjectSelf = '') { + $event->setAffectedUser($user) + ->setSubject( + $user === $event->getAuthor() && $subjectSelf ? $subjectSelf : $subject, + [ + $event->getAuthor(), + $properties['{DAV:}displayname'], + ] + ); + + $this->activityManager->publish($event); + } + + /** + * Creates activities when a calendar object was created/updated/deleted + * + * @param string $action + * @param array $calendarData + * @param array $shares + * @param array $objectData + */ + public function onTouchCalendarObject($action, array $calendarData, array $shares, array $objectData) { + if (!isset($calendarData['principaluri'])) { + return; + } + + $principal = explode('/', $calendarData['principaluri']); + $owner = $principal[2]; + + $currentUser = $this->userSession->getUser(); + if ($currentUser instanceof IUser) { + $currentUser = $currentUser->getUID(); + } else { + $currentUser = $owner; + } + + $object = $this->getObjectNameAndType($objectData); + $action = $action . '_' . $object['type']; + + if ($object['type'] === 'todo' && strpos($action, Extension::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'COMPLETED') { + $action .= '_completed'; + } else if ($object['type'] === 'todo' && strpos($action, Extension::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'NEEDS-ACTION') { + $action .= '_needs_action'; + } + + $event = $this->activityManager->generateEvent(); + $event->setApp('dav') + ->setObject(Extension::CALENDAR, $calendarData['id']) + ->setType($object['type'] === 'event' ? Extension::CALENDAR_EVENT : Extension::CALENDAR_TODO) + ->setAuthor($currentUser); + + $users = $this->getUsersForShares($shares); + $users[] = $owner; + + foreach ($users as $user) { + $event->setAffectedUser($user) + ->setSubject( + $user === $currentUser ? $action . '_self' : $action, + [ + $currentUser, + $calendarData['{DAV:}displayname'], + $object['name'], + ] + ); + $this->activityManager->publish($event); + } + } + + /** + * @param array $objectData + * @return string[]|bool + */ + protected function getObjectNameAndType(array $objectData) { + $vObject = Reader::read($objectData['calendardata']); + $component = $componentType = null; + foreach($vObject->getComponents() as $component) { + if (in_array($component->name, ['VEVENT', 'VTODO'])) { + $componentType = $component->name; + break; + } + } + + if (!$componentType) { + // Calendar objects must have a VEVENT or VTODO component + return false; + } + + if ($componentType === 'VEVENT') { + return ['name' => (string) $component->SUMMARY, 'type' => 'event']; + } + return ['name' => (string) $component->SUMMARY, 'type' => 'todo', 'status' => (string) $component->STATUS]; + } + + /** + * Get all users that have access to a given calendar + * + * @param array $shares + * @return string[] + */ + protected function getUsersForShares(array $shares) { + $users = $groups = []; + foreach ($shares as $share) { + $prinical = explode('/', $share['{http://owncloud.org/ns}principal']); + if ($prinical[1] === 'users') { + $users[] = $prinical[2]; + } else if ($prinical[1] === 'groups') { + $groups[] = $prinical[2]; + } + } + + if (!empty($groups)) { + foreach ($groups as $gid) { + $group = $this->groupManager->get($gid); + if ($group instanceof IGroup) { + foreach ($group->getUsers() as $user) { + $users[] = $user->getUID(); + } + } + } + } + + return array_unique($users); + } +} diff --git a/apps/dav/lib/CalDAV/Activity/Extension.php b/apps/dav/lib/CalDAV/Activity/Extension.php new file mode 100644 index 0000000000000..29222b90ce8d0 --- /dev/null +++ b/apps/dav/lib/CalDAV/Activity/Extension.php @@ -0,0 +1,378 @@ + + * + * @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\DAV\CalDAV\Activity; + +use OCP\Activity\IExtension; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; + +class Extension implements IExtension { + const APP = 'dav'; + /** + * Filter with all sharing related activities + */ + const CALENDAR = 'calendar'; + const CALENDAR_EVENT = 'calendar_event'; + const CALENDAR_TODO = 'calendar_todo'; + + const SUBJECT_ADD = 'calendar_add'; + const SUBJECT_UPDATE = 'calendar_update'; + const SUBJECT_DELETE = 'calendar_delete'; + const SUBJECT_SHARE_USER = 'calendar_user_share'; + const SUBJECT_SHARE_GROUP = 'calendar_group_share'; + const SUBJECT_UNSHARE_USER = 'calendar_user_unshare'; + const SUBJECT_UNSHARE_GROUP = 'calendar_group_unshare'; + + const SUBJECT_OBJECT_ADD = 'object_add'; + const SUBJECT_OBJECT_UPDATE = 'object_update'; + const SUBJECT_OBJECT_DELETE = 'object_delete'; + + /** @var IFactory */ + protected $languageFactory; + + /** @var IURLGenerator */ + protected $URLGenerator; + + /** + * @param IFactory $languageFactory + * @param IURLGenerator $URLGenerator + */ + public function __construct(IFactory $languageFactory, IURLGenerator $URLGenerator) { + $this->languageFactory = $languageFactory; + $this->URLGenerator = $URLGenerator; + } + + protected function getL10N($languageCode = null) { + return $this->languageFactory->get(self::APP, $languageCode); + } + + /** + * The extension can return an array of additional notification types. + * If no additional types are to be added false is to be returned + * + * @param string $languageCode + * @return array|false + */ + public function getNotificationTypes($languageCode) { + $l = $this->getL10N($languageCode); + + return array( + self::CALENDAR => (string) $l->t('A calendar was modified'), + self::CALENDAR_EVENT => (string) $l->t('A calendar event was modified'), + self::CALENDAR_TODO => (string) $l->t('A calendar todo was modified'), + ); + } + + /** + * For a given method additional types to be displayed in the settings can be returned. + * In case no additional types are to be added false is to be returned. + * + * @param string $method + * @return array|false + */ + public function getDefaultTypes($method) { + $defaultTypes = []; + if ($method === self::METHOD_STREAM) { + $defaultTypes[] = self::CALENDAR; + $defaultTypes[] = self::CALENDAR_EVENT; + $defaultTypes[] = self::CALENDAR_TODO; + } + + return $defaultTypes; + } + + /** + * A string naming the css class for the icon to be used can be returned. + * If no icon is known for the given type false is to be returned. + * + * @param string $type + * @return string|false + */ + public function getTypeIcon($type) { + switch ($type) { + case self::CALENDAR: + case self::CALENDAR_EVENT: + return 'icon-calendar-dark'; + case self::CALENDAR_TODO: + return 'icon-checkmark'; + } + + return false; + } + + /** + * The extension can translate a given message to the requested languages. + * If no translation is available false is to be returned. + * + * @param string $app + * @param string $text + * @param array $params + * @param boolean $stripPath + * @param boolean $highlightParams + * @param string $languageCode + * @return string|false + */ + public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) { + if ($app !== self::APP) { + return false; + } + + $l = $this->getL10N($languageCode); + + switch ($text) { + case self::SUBJECT_ADD: + return (string) $l->t('%1$s created calendar %2$s', $params); + case self::SUBJECT_ADD . '_self': + return (string) $l->t('You created calendar %2$s', $params); + case self::SUBJECT_DELETE: + return (string) $l->t('%1$s deleted calendar %2$s', $params); + case self::SUBJECT_DELETE . '_self': + return (string) $l->t('You deleted calendar %2$s', $params); + case self::SUBJECT_UPDATE: + return (string) $l->t('%1$s updated calendar %2$s', $params); + case self::SUBJECT_UPDATE . '_self': + return (string) $l->t('You updated calendar %2$s', $params); + + case self::SUBJECT_SHARE_USER: + return (string) $l->t('%1$s shared calendar %2$s with you', $params); + case self::SUBJECT_SHARE_USER . '_you': + return (string) $l->t('You shared calendar %2$s with %1$s', $params); + case self::SUBJECT_SHARE_USER . '_by': + return (string) $l->t('%3$s shared calendar %2$s with %1$s', $params); + case self::SUBJECT_UNSHARE_USER: + return (string) $l->t('%1$s unshared calendar %2$s from you', $params); + case self::SUBJECT_UNSHARE_USER . '_you': + return (string) $l->t('You unshared calendar %2$s from %1$s', $params); + case self::SUBJECT_UNSHARE_USER . '_by': + return (string) $l->t('%3$s unshared calendar %2$s from %1$s', $params); + case self::SUBJECT_UNSHARE_USER . '_self': + return (string) $l->t('%1$s unshared calendar %2$s from themselves', $params); + + case self::SUBJECT_SHARE_GROUP . '_you': + return (string) $l->t('You shared calendar %2$s with group %1$s', $params); + case self::SUBJECT_SHARE_GROUP . '_by': + return (string) $l->t('%3$s shared calendar %2$s with group %1$s', $params); + case self::SUBJECT_UNSHARE_GROUP . '_you': + return (string) $l->t('You unshared calendar %2$s from group %1$s', $params); + case self::SUBJECT_UNSHARE_GROUP . '_by': + return (string) $l->t('%3$s unshared calendar %2$s from group %1$s', $params); + + case self::SUBJECT_OBJECT_ADD . '_event': + return (string) $l->t('%1$s created event %3$s in calendar %2$s', $params); + case self::SUBJECT_OBJECT_ADD . '_event_self': + return (string) $l->t('You created event %3$s in calendar %2$s', $params); + case self::SUBJECT_OBJECT_DELETE . '_event': + return (string) $l->t('%1$s deleted event %3$s from calendar %2$s', $params); + case self::SUBJECT_OBJECT_DELETE . '_event_self': + return (string) $l->t('You deleted event %3$s from calendar %2$s', $params); + case self::SUBJECT_OBJECT_UPDATE . '_event': + return (string) $l->t('%1$s updated event %3$s in calendar %2$s', $params); + case self::SUBJECT_OBJECT_UPDATE . '_event_self': + return (string) $l->t('You updated event %3$s in calendar %2$s', $params); + + case self::SUBJECT_OBJECT_ADD . '_todo': + return (string) $l->t('%1$s created todo %3$s in list %2$s', $params); + case self::SUBJECT_OBJECT_ADD . '_todo_self': + return (string) $l->t('You created todo %3$s in list %2$s', $params); + case self::SUBJECT_OBJECT_DELETE . '_todo': + return (string) $l->t('%1$s deleted todo %3$s from list %2$s', $params); + case self::SUBJECT_OBJECT_DELETE . '_todo_self': + return (string) $l->t('You deleted todo %3$s from list %2$s', $params); + case self::SUBJECT_OBJECT_UPDATE . '_todo': + return (string) $l->t('%1$s updated todo %3$s in list %2$s', $params); + case self::SUBJECT_OBJECT_UPDATE . '_todo_self': + return (string) $l->t('You updated todo %3$s in list %2$s', $params); + + case self::SUBJECT_OBJECT_UPDATE . '_todo_completed': + return (string) $l->t('%1$s solved todo %3$s in list %2$s', $params); + case self::SUBJECT_OBJECT_UPDATE . '_todo_completed_self': + return (string) $l->t('You solved todo %3$s in list %2$s', $params); + case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action': + return (string) $l->t('%1$s reopened todo %3$s in list %2$s', $params); + case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self': + return (string) $l->t('You reopened todo %3$s in list %2$s', $params); + + } + + return false; + } + + /** + * The extension can define the type of parameters for translation + * + * Currently known types are: + * * file => will strip away the path of the file and add a tooltip with it + * * username => will add the avatar of the user + * + * @param string $app + * @param string $text + * @return array|false + */ + public function getSpecialParameterList($app, $text) { + if ($app === self::APP) { + switch ($text) { + case self::SUBJECT_ADD: + case self::SUBJECT_ADD . '_self': + case self::SUBJECT_DELETE: + case self::SUBJECT_DELETE . '_self': + case self::SUBJECT_UPDATE: + case self::SUBJECT_UPDATE . '_self': + case self::SUBJECT_SHARE_USER: + case self::SUBJECT_SHARE_USER . '_you': + case self::SUBJECT_UNSHARE_USER: + case self::SUBJECT_UNSHARE_USER . '_you': + case self::SUBJECT_UNSHARE_USER . '_self': + return [ + 0 => 'username', + //1 => 'calendar', + ]; + case self::SUBJECT_SHARE_USER . '_by': + case self::SUBJECT_UNSHARE_USER . '_by': + return [ + 0 => 'username', + //1 => 'calendar', + 2 => 'username', + ]; + case self::SUBJECT_SHARE_GROUP . '_you': + case self::SUBJECT_UNSHARE_GROUP . '_you': + return [ + //0 => 'group', + //1 => 'calendar', + ]; + case self::SUBJECT_SHARE_GROUP . '_by': + case self::SUBJECT_UNSHARE_GROUP . '_by': + return [ + //0 => 'group', + //1 => 'calendar', + 2 => 'username', + ]; + + case self::SUBJECT_OBJECT_ADD . '_event': + case self::SUBJECT_OBJECT_ADD . '_event_self': + case self::SUBJECT_OBJECT_DELETE . '_event': + case self::SUBJECT_OBJECT_DELETE . '_event_self': + case self::SUBJECT_OBJECT_UPDATE . '_event': + case self::SUBJECT_OBJECT_UPDATE . '_event_self': + + case self::SUBJECT_OBJECT_ADD . '_todo': + case self::SUBJECT_OBJECT_ADD . '_todo_self': + case self::SUBJECT_OBJECT_DELETE . '_todo': + case self::SUBJECT_OBJECT_DELETE . '_todo_self': + case self::SUBJECT_OBJECT_UPDATE . '_todo': + case self::SUBJECT_OBJECT_UPDATE . '_todo_self': + + case self::SUBJECT_OBJECT_UPDATE . '_todo_completed': + case self::SUBJECT_OBJECT_UPDATE . '_todo_completed_self': + case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action': + case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self': + return [ + 0 => 'username', + //1 => 'calendar', + //2 => 'object', + ]; + } + } + + return false; + } + + /** + * The extension can define the parameter grouping by returning the index as integer. + * In case no grouping is required false is to be returned. + * + * @param array $activity + * @return integer|false + */ + public function getGroupParameter($activity) { + return false; + } + + /** + * The extension can define additional navigation entries. The array returned has to contain two keys 'top' + * and 'apps' which hold arrays with the relevant entries. + * If no further entries are to be added false is no be returned. + * + * @return array|false + */ + public function getNavigation() { + $l = $this->getL10N(); + return [ + 'apps' => [ + self::CALENDAR => [ + 'id' => self::CALENDAR, + 'icon' => 'icon-calendar-dark', + 'name' => (string) $l->t('Calendar'), + 'url' => $this->URLGenerator->linkToRoute('activity.Activities.showList', ['filter' => self::CALENDAR]), + ], + self::CALENDAR_TODO => [ + 'id' => self::CALENDAR_TODO, + 'icon' => 'icon-checkmark', + 'name' => (string) $l->t('Todos'), + 'url' => $this->URLGenerator->linkToRoute('activity.Activities.showList', ['filter' => self::CALENDAR_TODO]), + ], + ], + 'top' => [], + ]; + } + + /** + * The extension can check if a custom filter (given by a query string like filter=abc) is valid or not. + * + * @param string $filterValue + * @return boolean + */ + public function isFilterValid($filterValue) { + return in_array($filterValue, [self::CALENDAR, self::CALENDAR_TODO]); + } + + /** + * The extension can filter the types based on the filter if required. + * In case no filter is to be applied false is to be returned unchanged. + * + * @param array $types + * @param string $filter + * @return array|false + */ + public function filterNotificationTypes($types, $filter) { + switch ($filter) { + case self::CALENDAR: + return array_intersect([self::CALENDAR, self::CALENDAR_EVENT], $types); + case self::CALENDAR_TODO: + return array_intersect([self::CALENDAR_TODO], $types); + } + return false; + } + + /** + * For a given filter the extension can specify the sql query conditions including parameters for that query. + * In case the extension does not know the filter false is to be returned. + * The query condition and the parameters are to be returned as array with two elements. + * E.g. return array('`app` = ? and `message` like ?', array('mail', 'ownCloud%')); + * + * @param string $filter + * @return array|false + */ + public function getQueryForFilter($filter) { + return false; + } + +} diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 07b61285eca26..fb608c2009de4 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -29,7 +29,6 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\DAV\Sharing\Backend; -use OCP\IConfig; use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; @@ -49,6 +48,8 @@ use Sabre\VObject\DateTimeParser; use Sabre\VObject\Reader; use Sabre\VObject\Recur\EventIterator; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; /** * Class CalDavBackend @@ -124,33 +125,33 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription /** @var IUserManager */ private $userManager; - - /** @var IConfig */ - private $config; /** @var ISecureRandom */ private $random; + /** @var EventDispatcherInterface */ + private $dispatcher; + /** * CalDavBackend constructor. * * @param IDBConnection $db * @param Principal $principalBackend * @param IUserManager $userManager - * @param IConfig $config * @param ISecureRandom $random + * @param EventDispatcherInterface $dispatcher */ public function __construct(IDBConnection $db, Principal $principalBackend, IUserManager $userManager, - IConfig $config, - ISecureRandom $random) { + ISecureRandom $random, + EventDispatcherInterface $dispatcher) { $this->db = $db; $this->principalBackend = $principalBackend; $this->userManager = $userManager; $this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar'); - $this->config = $config; $this->random = $random; + $this->dispatcher = $dispatcher; } /** @@ -613,7 +614,16 @@ function createCalendar($principalUri, $calendarUri, array $properties) { $query->setValue($column, $query->createNamedParameter($value)); } $query->execute(); - return $query->getLastInsertId(); + $calendarId = $query->getLastInsertId(); + + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent( + '\OCA\DAV\CalDAV\CalDavBackend::createCalendar', + [ + 'calendarId' => $calendarId, + 'calendarData' => $this->getCalendarById($calendarId), + ])); + + return $calendarId; } /** @@ -661,6 +671,15 @@ function updateCalendar($calendarId, PropPatch $propPatch) { $this->addChange($calendarId, "", 2); + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent( + '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', + [ + 'calendarId' => $calendarId, + 'calendarData' => $this->getCalendarById($calendarId), + 'shares' => $this->getShares($calendarId), + 'propertyMutations' => $mutations, + ])); + return true; }); } @@ -672,6 +691,14 @@ function updateCalendar($calendarId, PropPatch $propPatch) { * @return void */ function deleteCalendar($calendarId) { + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent( + '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', + [ + 'calendarId' => $calendarId, + 'calendarData' => $this->getCalendarById($calendarId), + 'shares' => $this->getShares($calendarId), + ])); + $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?'); $stmt->execute([$calendarId]); @@ -876,6 +903,15 @@ function createCalendarObject($calendarId, $objectUri, $calendarData) { ]) ->execute(); + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent( + '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', + [ + 'calendarId' => $calendarId, + 'calendarData' => $this->getCalendarById($calendarId), + 'shares' => $this->getShares($calendarId), + 'objectData' => $this->getCalendarObject($calendarId, $objectUri), + ] + )); $this->addChange($calendarId, $objectUri, 1); return '"' . $extraData['etag'] . '"'; @@ -917,6 +953,18 @@ function updateCalendarObject($calendarId, $objectUri, $calendarData) { ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); + $data = $this->getCalendarObject($calendarId, $objectUri); + if (is_array($data)) { + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent( + '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', + [ + 'calendarId' => $calendarId, + 'calendarData' => $this->getCalendarById($calendarId), + 'shares' => $this->getShares($calendarId), + 'objectData' => $data, + ] + )); + } $this->addChange($calendarId, $objectUri, 2); return '"' . $extraData['etag'] . '"'; @@ -949,6 +997,19 @@ public function setClassification($calendarObjectId, $classification) { * @return void */ function deleteCalendarObject($calendarId, $objectUri) { + $data = $this->getCalendarObject($calendarId, $objectUri); + if (is_array($data)) { + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent( + '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', + [ + 'calendarId' => $calendarId, + 'calendarData' => $this->getCalendarById($calendarId), + 'shares' => $this->getShares($calendarId), + 'objectData' => $data, + ] + )); + } + $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?'); $stmt->execute([$calendarId, $objectUri]); @@ -1648,6 +1709,16 @@ private function readBlob($cardData) { * @param array $remove */ public function updateShares($shareable, $add, $remove) { + $calendarId = $shareable->getResourceId(); + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent( + '\OCA\DAV\CalDAV\CalDavBackend::updateShares', + [ + 'calendarId' => $calendarId, + 'calendarData' => $this->getCalendarById($calendarId), + 'shares' => $this->getShares($calendarId), + 'add' => $add, + 'remove' => $remove, + ])); $this->sharingBackend->updateShares($shareable, $add, $remove); } diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php index da1f706a8b810..24990352fabe2 100644 --- a/apps/dav/lib/Command/CreateCalendar.php +++ b/apps/dav/lib/Command/CreateCalendar.php @@ -75,11 +75,11 @@ protected function execute(InputInterface $input, OutputInterface $output) { $this->userManager, $this->groupManager ); - $config = \OC::$server->getConfig(); $random = \OC::$server->getSecureRandom(); + $dispatcher = \OC::$server->getEventDispatcher(); $name = $input->getArgument('name'); - $caldav = new CalDavBackend($this->dbConnection, $principalBackend, $this->userManager, $config, $random); + $caldav = new CalDavBackend($this->dbConnection, $principalBackend, $this->userManager, $random, $dispatcher); $caldav->createCalendar("principals/users/$user", $name, []); } } diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 4c76dc30c3fcc..478f0929c2014 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -62,7 +62,7 @@ public function __construct() { $systemPrincipals->disableListing = $disableListing; $filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users'); $filesCollection->disableListing = $disableListing; - $caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $config, $random); + $caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $random, $dispatcher); $calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users'); $calendarRoot->disableListing = $disableListing; $publicCalendarRoot = new PublicCalendarRoot($caldavBackend); diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php index 2559ecbbf89e2..d15be72c77be1 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php @@ -22,18 +22,12 @@ namespace OCA\DAV\Tests\unit\CalDAV; -use DateTime; -use DateTimeZone; use OCA\DAV\CalDAV\CalDavBackend; -use OCA\DAV\CalDAV\Calendar; use OCA\DAV\Connector\Sabre\Principal; -use OCP\IL10N; -use OCP\IConfig; +use OCP\IUserManager; use OCP\Security\ISecureRandom; use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; -use Sabre\DAV\PropPatch; -use Sabre\DAV\Xml\Property\Href; -use Sabre\DAVACL\IACL; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; /** @@ -50,12 +44,10 @@ abstract class AbstractCalDavBackendTest extends TestCase { /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */ protected $principal; - - /** @var \OCP\IUserManager|\PHPUnit_Framework_MockObject_MockObject */ + /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ protected $userManager; - - /** var OCP\IConfig */ - protected $config; + /** @var EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $dispatcher; /** @var ISecureRandom */ private $random; @@ -67,9 +59,8 @@ abstract class AbstractCalDavBackendTest extends TestCase { public function setUp() { parent::setUp(); - $this->userManager = $this->getMockBuilder('OCP\IUserManager') - ->disableOriginalConstructor() - ->getMock(); + $this->userManager = $this->createMock(IUserManager::class); + $this->dispatcher = $this->createMock(EventDispatcherInterface::class); $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') ->disableOriginalConstructor() ->setMethods(['getPrincipalByPath', 'getGroupMembership']) @@ -83,21 +74,28 @@ public function setUp() { ->willReturn([self::UNIT_TEST_GROUP]); $db = \OC::$server->getDatabaseConnection(); - $this->config = \OC::$server->getConfig(); $this->random = \OC::$server->getSecureRandom(); - $this->backend = new CalDavBackend($db, $this->principal, $this->userManager, $this->config, $this->random); - $this->tearDown(); + $this->backend = new CalDavBackend($db, $this->principal, $this->userManager, $this->random, $this->dispatcher); + + $this->cleanUpBackend(); } public function tearDown() { + $this->cleanUpBackend(); parent::tearDown(); + } + public function cleanUpBackend() { if (is_null($this->backend)) { return; } - $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); - foreach ($books as $book) { - $this->backend->deleteCalendar($book['id']); + $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); + foreach ($calendars as $calendar) { + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar'); + + $this->backend->deleteCalendar($calendar['id']); } $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER); foreach ($subscriptions as $subscription) { @@ -106,6 +104,10 @@ public function tearDown() { } protected function createTestCalendar() { + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendar'); + $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', [ '{http://apple.com/ns/ical/}calendar-color' => '#1C4587FF' ]); @@ -143,6 +145,11 @@ protected function createEvent($calendarId, $start = '20130912T130000Z', $end = END:VCALENDAR EOD; $uri0 = $this->getUniqueID('event'); + + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'); + $this->backend->createCalendarObject($calendarId, $uri0, $calData); return $uri0; diff --git a/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php b/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php new file mode 100644 index 0000000000000..3585d69bad32c --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php @@ -0,0 +1,332 @@ + + * + * @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\DAV\Tests\unit\CalDAV\Activity; + +use OCA\DAV\CalDAV\Activity\Backend; +use OCA\DAV\CalDAV\Activity\Extension; +use OCP\Activity\IEvent; +use OCP\Activity\IManager; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserSession; +use Test\TestCase; + +class BackendTest extends TestCase { + + /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $activityManager; + + /** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $groupManager; + + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + protected $userSession; + + protected function setUp() { + parent::setUp(); + $this->activityManager = $this->createMock(IManager::class); + $this->groupManager = $this->createMock(IGroupManager::class); + $this->userSession = $this->createMock(IUserSession::class); + } + + /** + * @param array $methods + * @return Backend|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getBackend(array $methods = []) { + if (empty($methods)) { + return new Backend( + $this->activityManager, + $this->groupManager, + $this->userSession + ); + } else { + return $this->getMockBuilder(Backend::class) + ->setConstructorArgs([ + $this->activityManager, + $this->groupManager, + $this->userSession, + ]) + ->setMethods($methods) + ->getMock(); + } + } + + public function dataCallTriggerCalendarActivity() { + return [ + ['onCalendarAdd', [['data']], Extension::SUBJECT_ADD, [['data'], [], []]], + ['onCalendarUpdate', [['data'], ['shares'], ['changed-properties']], Extension::SUBJECT_UPDATE, [['data'], ['shares'], ['changed-properties']]], + ['onCalendarDelete', [['data'], ['shares']], Extension::SUBJECT_DELETE, [['data'], ['shares'], []]], + ]; + } + + /** + * @dataProvider dataCallTriggerCalendarActivity + * + * @param string $method + * @param array $payload + * @param string $expectedSubject + * @param array $expectedPayload + */ + public function testCallTriggerCalendarActivity($method, array $payload, $expectedSubject, array $expectedPayload) { + $backend = $this->getBackend(['triggerCalendarActivity']); + $backend->expects($this->once()) + ->method('triggerCalendarActivity') + ->willReturnCallback(function() use($expectedPayload, $expectedSubject) { + $arguments = func_get_args(); + $this->assertSame($expectedSubject, array_shift($arguments)); + $this->assertEquals($expectedPayload, $arguments); + }); + + call_user_func_array([$backend, $method], $payload); + } + + public function dataTriggerCalendarActivity() { + return [ + // Add calendar + [Extension::SUBJECT_ADD, [], [], [], '', '', null, []], + [Extension::SUBJECT_ADD, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], [], [], '', 'admin', null, ['admin']], + [Extension::SUBJECT_ADD, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], [], [], 'test2', 'test2', null, ['admin']], + + // Update calendar + [Extension::SUBJECT_UPDATE, [], [], [], '', '', null, []], + // No visible change - owner only + [Extension::SUBJECT_UPDATE, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], ['shares'], [], '', 'admin', null, ['admin']], + // Visible change + [Extension::SUBJECT_UPDATE, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], ['shares'], ['{DAV:}displayname' => 'Name'], '', 'admin', ['user1'], ['user1', 'admin']], + [Extension::SUBJECT_UPDATE, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], ['shares'], ['{DAV:}displayname' => 'Name'], 'test2', 'test2', ['user1'], ['user1', 'admin']], + + // Delete calendar + [Extension::SUBJECT_DELETE, [], [], [], '', '', null, []], + [Extension::SUBJECT_DELETE, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], ['shares'], [], '', 'admin', [], ['admin']], + [Extension::SUBJECT_DELETE, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], ['shares'], [], '', 'admin', ['user1'], ['user1', 'admin']], + [Extension::SUBJECT_DELETE, [ + 'principaluri' => 'principal/user/admin', + 'id' => 42, + '{DAV:}displayname' => 'Name of calendar', + ], ['shares'], [], 'test2', 'test2', ['user1'], ['user1', 'admin']], + ]; + } + + /** + * @dataProvider dataTriggerCalendarActivity + * @param string $action + * @param array $data + * @param array $shares + * @param array $changedProperties + * @param string $currentUser + * @param string $author + * @param string[]|null $shareUsers + * @param string[] $users + */ + public function testTriggerCalendarActivity($action, array $data, array $shares, array $changedProperties, $currentUser, $author, $shareUsers, array $users) { + $backend = $this->getBackend(['getUsersForShares']); + + if ($shareUsers === null) { + $backend->expects($this->never()) + ->method('getUsersForShares'); + } else { + $backend->expects($this->once()) + ->method('getUsersForShares') + ->with($shares) + ->willReturn($shareUsers); + } + + if ($author !== '') { + if ($currentUser !== '') { + $this->userSession->expects($this->once()) + ->method('getUser') + ->willReturn($this->getUserMock($currentUser)); + } else { + $this->userSession->expects($this->once()) + ->method('getUser') + ->willReturn(null); + } + + $event = $this->createMock(IEvent::class); + $this->activityManager->expects($this->once()) + ->method('generateEvent') + ->willReturn($event); + + $event->expects($this->once()) + ->method('setApp') + ->with('dav') + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setObject') + ->with(Extension::CALENDAR, $data['id']) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setType') + ->with(Extension::CALENDAR) + ->willReturnSelf(); + $event->expects($this->once()) + ->method('setAuthor') + ->with($author) + ->willReturnSelf(); + + $event->expects($this->exactly(sizeof($users))) + ->method('setAffectedUser') + ->willReturnSelf(); + $event->expects($this->exactly(sizeof($users))) + ->method('setSubject') + ->willReturnSelf(); + $this->activityManager->expects($this->exactly(sizeof($users))) + ->method('publish') + ->with($event); + } else { + $this->activityManager->expects($this->never()) + ->method('generateEvent'); + } + + $this->invokePrivate($backend, 'triggerCalendarActivity', [$action, $data, $shares, $changedProperties]); + } + + public function dataGetUsersForShares() { + return [ + [ + [], + [], + [], + ], + [ + [ + ['{http://owncloud.org/ns}principal' => 'principal/users/user1'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user2'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user2'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user2'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user3'], + ], + [], + ['user1', 'user2', 'user3'], + ], + [ + [ + ['{http://owncloud.org/ns}principal' => 'principal/users/user1'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user2'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user2'], + ['{http://owncloud.org/ns}principal' => 'principal/groups/group2'], + ['{http://owncloud.org/ns}principal' => 'principal/groups/group3'], + ], + ['group2' => null, 'group3' => null], + ['user1', 'user2'], + ], + [ + [ + ['{http://owncloud.org/ns}principal' => 'principal/users/user1'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user2'], + ['{http://owncloud.org/ns}principal' => 'principal/users/user2'], + ['{http://owncloud.org/ns}principal' => 'principal/groups/group2'], + ['{http://owncloud.org/ns}principal' => 'principal/groups/group3'], + ], + ['group2' => ['user1', 'user2', 'user3'], 'group3' => ['user2', 'user3', 'user4']], + ['user1', 'user2', 'user3', 'user4'], + ], + ]; + } + + /** + * @dataProvider dataGetUsersForShares + * @param array $shares + * @param array $groups + * @param array $expected + */ + public function testGetUsersForShares(array $shares, array $groups, array $expected) { + $backend = $this->getBackend(); + + $getGroups = []; + foreach ($groups as $gid => $members) { + if ($members === null) { + $getGroups[] = [$gid, null]; + continue; + } + + $group = $this->createMock(IGroup::class); + $group->expects($this->once()) + ->method('getUsers') + ->willReturn($this->getUsers($members)); + + $getGroups[] = [$gid, $group]; + } + + $this->groupManager->expects($this->exactly(sizeof($getGroups))) + ->method('get') + ->willReturnMap($getGroups); + + $users = $this->invokePrivate($backend, 'getUsersForShares', [$shares]); + sort($users); + $this->assertEquals($expected, $users); + } + + /** + * @param string[] $users + * @return IUser[]|\PHPUnit_Framework_MockObject_MockObject[] + */ + protected function getUsers(array $users) { + $list = []; + foreach ($users as $user) { + $list[] = $this->getUserMock($user); + } + return $list; + } + + /** + * @param string $uid + * @return IUser|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getUserMock($uid) { + $user = $this->createMock(IUser::class); + $user->expects($this->once()) + ->method('getUID') + ->willReturn($uid); + return $user; + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index 8349d98cd94bc..b5e700e8bc4f8 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -51,6 +51,9 @@ public function testCalendarOperations() { '{DAV:}displayname' => 'Unit test', '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar used for unit testing' ]); + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar'); $this->backend->updateCalendar($calendarId, $patch); $patch->commit(); $this->assertEquals(1, $this->backend->getCalendarsForUserCount(self::UNIT_TEST_USER)); @@ -60,6 +63,9 @@ public function testCalendarOperations() { $this->assertEquals('Calendar used for unit testing', $books[0]['{urn:ietf:params:xml:ns:caldav}calendar-description']); // delete the address book + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar'); $this->backend->deleteCalendar($books[0]['id']); $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); $this->assertEquals(0, count($books)); @@ -106,6 +112,9 @@ public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); $this->assertEquals(1, count($books)); $calendar = new Calendar($this->backend, $books[0], $l10n); + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::updateShares'); $this->backend->updateShares($calendar, $add, []); $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1); $this->assertEquals(1, count($books)); @@ -138,6 +147,9 @@ public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, END:VCALENDAR EOD; + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'); $this->backend->createCalendarObject($calendarId, $uri, $calData); /** @var IACL $child */ @@ -151,6 +163,9 @@ public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $this->assertAccess($groupCanWrite, self::UNIT_TEST_GROUP, '{DAV:}write', $acl); // delete the address book + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar'); $this->backend->deleteCalendar($books[0]['id']); $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); $this->assertEquals(0, count($books)); @@ -179,6 +194,9 @@ public function testCalendarObjectsOperations() { END:VCALENDAR EOD; + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'); $this->backend->createCalendarObject($calendarId, $uri, $calData); // get all the cards @@ -214,11 +232,17 @@ public function testCalendarObjectsOperations() { END:VEVENT END:VCALENDAR EOD; + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject'); $this->backend->updateCalendarObject($calendarId, $uri, $calData); $calendarObject = $this->backend->getCalendarObject($calendarId, $uri); $this->assertEquals($calData, $calendarObject['calendardata']); // delete the card + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject'); $this->backend->deleteCalendarObject($calendarId, $uri); $calendarObjects = $this->backend->getCalendarObjects($calendarId); $this->assertEquals(0, count($calendarObjects)); @@ -246,10 +270,19 @@ public function testMultiCalendarObjects() { END:VCALENDAR EOD; $uri0 = $this->getUniqueID('card'); + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'); $this->backend->createCalendarObject($calendarId, $uri0, $calData); $uri1 = $this->getUniqueID('card'); + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'); $this->backend->createCalendarObject($calendarId, $uri1, $calData); $uri2 = $this->getUniqueID('card'); + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'); $this->backend->createCalendarObject($calendarId, $uri2, $calData); // get all the cards @@ -270,8 +303,17 @@ public function testMultiCalendarObjects() { } // delete the card + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject'); $this->backend->deleteCalendarObject($calendarId, $uri0); + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject'); $this->backend->deleteCalendarObject($calendarId, $uri1); + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject'); $this->backend->deleteCalendarObject($calendarId, $uri2); $calendarObjects = $this->backend->getCalendarObjects($calendarId); $this->assertEquals(0, count($calendarObjects)); @@ -335,6 +377,10 @@ public function testSyncSupport() { } public function testPublications() { + $this->dispatcher->expects($this->at(0)) + ->method('dispatch') + ->with('\OCA\DAV\CalDAV\CalDavBackend::createCalendar'); + $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []); $calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0]; @@ -357,10 +403,8 @@ public function testPublications() { $calendar->setPublishStatus(false); $this->assertEquals(false, $calendar->getPublishStatus()); - $publicCalendarURI = md5($this->config->getSystemValue('secret', '') . $calendar->getResourceId()); $this->setExpectedException('Sabre\DAV\Exception\NotFound'); - $publicCalendar = $this->backend->getPublicCalendar($publicCalendarURI); - + $this->backend->getPublicCalendar($publicCalendarURI); } public function testSubscriptions() { diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php index 6dfec6d7e1f0f..59fa4747a93b8 100644 --- a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php @@ -9,6 +9,7 @@ use OCA\DAV\CalDAV\PublicCalendarRoot; use OCP\IUserManager; use OCP\Security\ISecureRandom; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; /** @@ -27,12 +28,11 @@ class PublicCalendarRootTest extends TestCase { private $publicCalendarRoot; /** @var IL10N */ private $l10n; - /** @var IUserManager */ - private $userManager; - /** @var Principal */ + /** @var Principal|\PHPUnit_Framework_MockObject_MockObject */ private $principal; - /** var IConfig */ - protected $config; + /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $userManager; + /** @var ISecureRandom */ private $random; @@ -40,19 +40,17 @@ public function setUp() { parent::setUp(); $db = \OC::$server->getDatabaseConnection(); - $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') - ->disableOriginalConstructor() - ->getMock(); - $this->config = \OC::$server->getConfig(); - $this->userManager = $this->getMockBuilder('\OCP\IUserManager')->getMock(); + $this->principal = $this->createMock('OCA\DAV\Connector\Sabre\Principal'); + $this->userManager = $this->createMock(IUserManager::class); $this->random = \OC::$server->getSecureRandom(); + $dispatcher = $this->createMock(EventDispatcherInterface::class); $this->backend = new CalDavBackend( $db, $this->principal, $this->userManager, - $this->config, - $this->random + $this->random, + $dispatcher ); $this->publicCalendarRoot = new PublicCalendarRoot($this->backend);