diff options
-rw-r--r-- | apps/dav/appinfo/info.xml | 1 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_classmap.php | 2 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_static.php | 2 | ||||
-rw-r--r-- | apps/dav/lib/AppInfo/Application.php | 7 | ||||
-rw-r--r-- | apps/dav/lib/CardDAV/Activity/Backend.php | 79 | ||||
-rw-r--r-- | apps/dav/lib/CardDAV/Activity/Provider/Card.php | 149 | ||||
-rw-r--r-- | apps/dav/lib/Listener/CardListener.php | 111 |
7 files changed, 294 insertions, 57 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 37a43fca3a2..ff99623e382 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -77,6 +77,7 @@ <provider>OCA\DAV\CalDAV\Activity\Provider\Event</provider> <provider>OCA\DAV\CalDAV\Activity\Provider\Todo</provider> <provider>OCA\DAV\CardDAV\Activity\Provider\Addressbook</provider> + <provider>OCA\DAV\CardDAV\Activity\Provider\Card</provider> </providers> </activity> diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 1687bc632fe..bedb7b6ec78 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -89,6 +89,7 @@ return array( 'OCA\\DAV\\CardDAV\\Activity\\Filter' => $baseDir . '/../lib/CardDAV/Activity/Filter.php', 'OCA\\DAV\\CardDAV\\Activity\\Provider\\Addressbook' => $baseDir . '/../lib/CardDAV/Activity/Provider/Addressbook.php', 'OCA\\DAV\\CardDAV\\Activity\\Provider\\Base' => $baseDir . '/../lib/CardDAV/Activity/Provider/Base.php', + 'OCA\\DAV\\CardDAV\\Activity\\Provider\\Card' => $baseDir . '/../lib/CardDAV/Activity/Provider/Card.php', 'OCA\\DAV\\CardDAV\\Activity\\Setting' => $baseDir . '/../lib/CardDAV/Activity/Setting.php', 'OCA\\DAV\\CardDAV\\AddressBook' => $baseDir . '/../lib/CardDAV/AddressBook.php', 'OCA\\DAV\\CardDAV\\AddressBookImpl' => $baseDir . '/../lib/CardDAV/AddressBookImpl.php', @@ -215,6 +216,7 @@ return array( 'OCA\\DAV\\Listener\\CalendarContactInteractionListener' => $baseDir . '/../lib/Listener/CalendarContactInteractionListener.php', 'OCA\\DAV\\Listener\\CalendarDeletionDefaultUpdaterListener' => $baseDir . '/../lib/Listener/CalendarDeletionDefaultUpdaterListener.php', 'OCA\\DAV\\Listener\\CalendarObjectReminderUpdaterListener' => $baseDir . '/../lib/Listener/CalendarObjectReminderUpdaterListener.php', + 'OCA\\DAV\\Listener\\CardListener' => $baseDir . '/../lib/Listener/CardListener.php', 'OCA\\DAV\\Migration\\BuildCalendarSearchIndex' => $baseDir . '/../lib/Migration/BuildCalendarSearchIndex.php', 'OCA\\DAV\\Migration\\BuildCalendarSearchIndexBackgroundJob' => $baseDir . '/../lib/Migration/BuildCalendarSearchIndexBackgroundJob.php', 'OCA\\DAV\\Migration\\BuildSocialSearchIndex' => $baseDir . '/../lib/Migration/BuildSocialSearchIndex.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index f2ac36ba90d..0a81ac8b4e9 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -104,6 +104,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CardDAV\\Activity\\Filter' => __DIR__ . '/..' . '/../lib/CardDAV/Activity/Filter.php', 'OCA\\DAV\\CardDAV\\Activity\\Provider\\Addressbook' => __DIR__ . '/..' . '/../lib/CardDAV/Activity/Provider/Addressbook.php', 'OCA\\DAV\\CardDAV\\Activity\\Provider\\Base' => __DIR__ . '/..' . '/../lib/CardDAV/Activity/Provider/Base.php', + 'OCA\\DAV\\CardDAV\\Activity\\Provider\\Card' => __DIR__ . '/..' . '/../lib/CardDAV/Activity/Provider/Card.php', 'OCA\\DAV\\CardDAV\\Activity\\Setting' => __DIR__ . '/..' . '/../lib/CardDAV/Activity/Setting.php', 'OCA\\DAV\\CardDAV\\AddressBook' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBook.php', 'OCA\\DAV\\CardDAV\\AddressBookImpl' => __DIR__ . '/..' . '/../lib/CardDAV/AddressBookImpl.php', @@ -230,6 +231,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Listener\\CalendarContactInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarContactInteractionListener.php', 'OCA\\DAV\\Listener\\CalendarDeletionDefaultUpdaterListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarDeletionDefaultUpdaterListener.php', 'OCA\\DAV\\Listener\\CalendarObjectReminderUpdaterListener' => __DIR__ . '/..' . '/../lib/Listener/CalendarObjectReminderUpdaterListener.php', + 'OCA\\DAV\\Listener\\CardListener' => __DIR__ . '/..' . '/../lib/Listener/CardListener.php', 'OCA\\DAV\\Migration\\BuildCalendarSearchIndex' => __DIR__ . '/..' . '/../lib/Migration/BuildCalendarSearchIndex.php', 'OCA\\DAV\\Migration\\BuildCalendarSearchIndexBackgroundJob' => __DIR__ . '/..' . '/../lib/Migration/BuildCalendarSearchIndexBackgroundJob.php', 'OCA\\DAV\\Migration\\BuildSocialSearchIndex' => __DIR__ . '/..' . '/../lib/Migration/BuildSocialSearchIndex.php', diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 16615f59b05..d6c20f81d96 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -62,12 +62,16 @@ use OCA\DAV\Events\CalendarObjectDeletedEvent; use OCA\DAV\Events\CalendarObjectUpdatedEvent; use OCA\DAV\Events\CalendarShareUpdatedEvent; use OCA\DAV\Events\CalendarUpdatedEvent; +use OCA\DAV\Events\CardCreatedEvent; +use OCA\DAV\Events\CardDeletedEvent; +use OCA\DAV\Events\CardUpdatedEvent; use OCA\DAV\HookManager; use OCA\DAV\Listener\ActivityUpdaterListener; use OCA\DAV\Listener\AddressbookListener; use OCA\DAV\Listener\CalendarContactInteractionListener; use OCA\DAV\Listener\CalendarDeletionDefaultUpdaterListener; use OCA\DAV\Listener\CalendarObjectReminderUpdaterListener; +use OCA\DAV\Listener\CardListener; use OCA\DAV\Search\ContactsSearchProvider; use OCA\DAV\Search\EventsSearchProvider; use OCA\DAV\Search\TasksSearchProvider; @@ -141,6 +145,9 @@ class Application extends App implements IBootstrap { $context->registerEventListener(AddressBookDeletedEvent::class, AddressbookListener::class); $context->registerEventListener(AddressBookUpdatedEvent::class, AddressbookListener::class); $context->registerEventListener(AddressBookShareUpdatedEvent::class, AddressbookListener::class); + $context->registerEventListener(CardCreatedEvent::class, CardListener::class); + $context->registerEventListener(CardDeletedEvent::class, CardListener::class); + $context->registerEventListener(CardUpdatedEvent::class, CardListener::class); $context->registerNotifierService(Notifier::class); } diff --git a/apps/dav/lib/CardDAV/Activity/Backend.php b/apps/dav/lib/CardDAV/Activity/Backend.php index a3005f96b2b..16b17e476e0 100644 --- a/apps/dav/lib/CardDAV/Activity/Backend.php +++ b/apps/dav/lib/CardDAV/Activity/Backend.php @@ -29,6 +29,7 @@ declare(strict_types=1); namespace OCA\DAV\CardDAV\Activity; use OCA\DAV\CardDAV\Activity\Provider\Addressbook; +use OCA\DAV\CardDAV\Activity\Provider\Card; use OCP\Activity\IEvent; use OCP\Activity\IManager as IActivityManager; use OCP\App\IAppManager; @@ -384,19 +385,19 @@ class Backend { } /** - * Creates activities when a calendar object was created/updated/deleted + * Creates activities when a card was created/updated/deleted * * @param string $action - * @param array $calendarData + * @param array $addressbookData * @param array $shares - * @param array $objectData + * @param array $cardData */ - public function onTouchCalendarObject($action, array $calendarData, array $shares, array $objectData) { - if (!isset($calendarData['principaluri'])) { + public function triggerCardActivity(string $action, array $addressbookData, array $shares, array $cardData): void { + if (!isset($addressbookData['principaluri'])) { return; } - $principal = explode('/', $calendarData['principaluri']); + $principal = explode('/', $addressbookData['principaluri']); $owner = array_pop($principal); $currentUser = $this->userSession->getUser(); @@ -406,20 +407,12 @@ class Backend { $currentUser = $owner; } - $classification = $objectData['classification'] ?? CalDavBackend::CLASSIFICATION_PUBLIC; - $object = $this->getObjectNameAndType($objectData); - $action = $action . '_' . $object['type']; - - if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'COMPLETED') { - $action .= '_completed'; - } elseif ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'NEEDS-ACTION') { - $action .= '_needs_action'; - } + $card = $this->getCardNameAndId($cardData); $event = $this->activityManager->generateEvent(); $event->setApp('dav') - ->setObject('calendar', (int) $calendarData['id']) - ->setType($object['type'] === 'event' ? 'calendar_event' : 'calendar_todo') + ->setObject('addressbook', (int) $addressbookData['id']) + ->setType('card') ->setAuthor($currentUser); $users = $this->getUsersForShares($shares); @@ -427,31 +420,19 @@ class Backend { // Users for share can return the owner itself if the calendar is published foreach (array_unique($users) as $user) { - if ($classification === CalDavBackend::CLASSIFICATION_PRIVATE && $user !== $owner) { - // Private events are only shown to the owner - continue; - } - $params = [ 'actor' => $event->getAuthor(), - 'calendar' => [ - 'id' => (int) $calendarData['id'], - 'uri' => $calendarData['uri'], - 'name' => $calendarData['{DAV:}displayname'], + 'addressbook' => [ + 'id' => (int) $addressbookData['id'], + 'uri' => $addressbookData['uri'], + 'name' => $addressbookData['{DAV:}displayname'], ], - 'object' => [ - 'id' => $object['id'], - 'name' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $owner ? 'Busy' : $object['name'], - 'classified' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $owner, + 'card' => [ + 'id' => $card['id'], + 'name' => $card['name'], ], ]; - if ($object['type'] === 'event' && strpos($action, Event::SUBJECT_OBJECT_DELETE) === false && $this->appManager->isEnabledForUser('calendar')) { - $params['object']['link']['object_uri'] = $objectData['uri']; - $params['object']['link']['calendar_uri'] = $calendarData['uri']; - $params['object']['link']['owner'] = $owner; - } - $event->setAffectedUser($user) ->setSubject( @@ -464,28 +445,12 @@ class Backend { } /** - * @param array $objectData - * @return string[]|bool + * @param array $cardData + * @return string[] */ - 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 ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'event']; - } - return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'todo', 'status' => (string) $component->STATUS]; + protected function getCardNameAndId(array $cardData): array { + $vObject = Reader::read($cardData['carddata']); + return ['id' => (string) $vObject->UID, 'name' => (string) ($vObject->FN ?? '')]; } /** diff --git a/apps/dav/lib/CardDAV/Activity/Provider/Card.php b/apps/dav/lib/CardDAV/Activity/Provider/Card.php new file mode 100644 index 00000000000..11e3476591f --- /dev/null +++ b/apps/dav/lib/CardDAV/Activity/Provider/Card.php @@ -0,0 +1,149 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2021 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\DAV\CardDAV\Activity\Provider; + +use OCP\Activity\IEvent; +use OCP\Activity\IEventMerger; +use OCP\Activity\IManager; +use OCP\App\IAppManager; +use OCP\IGroupManager; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUserManager; +use OCP\L10N\IFactory; + +class Card extends Base { + public const SUBJECT_ADD = 'card_add'; + public const SUBJECT_UPDATE = 'card_update'; + public const SUBJECT_DELETE = 'card_delete'; + + /** @var IFactory */ + protected $languageFactory; + + /** @var IL10N */ + protected $l; + + /** @var IManager */ + protected $activityManager; + + /** @var IEventMerger */ + protected $eventMerger; + + /** @var IAppManager */ + protected $appManager; + + public function __construct(IFactory $languageFactory, + IURLGenerator $url, + IManager $activityManager, + IUserManager $userManager, + IGroupManager $groupManager, + IEventMerger $eventMerger, + IAppManager $appManager) { + parent::__construct($userManager, $groupManager, $url); + $this->languageFactory = $languageFactory; + $this->activityManager = $activityManager; + $this->eventMerger = $eventMerger; + $this->appManager = $appManager; + } + + /** + * @param string $language + * @param IEvent $event + * @param IEvent|null $previousEvent + * @return IEvent + * @throws \InvalidArgumentException + * @since 11.0.0 + */ + public function parse($language, IEvent $event, IEvent $previousEvent = null) { + if ($event->getApp() !== 'dav' || $event->getType() !== 'card') { + throw new \InvalidArgumentException(); + } + + $this->l = $this->languageFactory->get('dav', $language); + + if ($this->activityManager->getRequirePNG()) { + $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/contacts-dark.png'))); + } else { + $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/contacts.svg'))); + } + + if ($event->getSubject() === self::SUBJECT_ADD) { + $subject = $this->l->t('{actor} created contact {card} in addressbook {addressbook}'); + } elseif ($event->getSubject() === self::SUBJECT_ADD . '_self') { + $subject = $this->l->t('You created contact {card} in addressbook {addressbook}'); + } elseif ($event->getSubject() === self::SUBJECT_DELETE ) { + $subject = $this->l->t('{actor} deleted contact {card} from addressbook {addressbook}'); + } elseif ($event->getSubject() === self::SUBJECT_DELETE . '_self') { + $subject = $this->l->t('You deleted contact {card} from addressbook {addressbook}'); + } elseif ($event->getSubject() === self::SUBJECT_UPDATE) { + $subject = $this->l->t('{actor} updated contact {card} in addressbook {addressbook}'); + } elseif ($event->getSubject() === self::SUBJECT_UPDATE . '_self') { + $subject = $this->l->t('You updated contact {card} in addressbook {addressbook}'); + } else { + throw new \InvalidArgumentException(); + } + + $parsedParameters = $this->getParameters($event); + $this->setSubjects($event, $subject, $parsedParameters); + + $event = $this->eventMerger->mergeEvents('card', $event, $previousEvent); + return $event; + } + + protected function getParameters(IEvent $event): array { + $subject = $event->getSubject(); + $parameters = $event->getSubjectParameters(); + + switch ($subject) { + case self::SUBJECT_ADD: + case self::SUBJECT_DELETE: + case self::SUBJECT_UPDATE: + return [ + 'actor' => $this->generateUserParameter($parameters['actor']), + 'addressbook' => $this->generateAddressbookParameter($parameters['addressbook'], $this->l), + 'card' => $this->generateCardParameter($parameters['card']), + ]; + case self::SUBJECT_ADD . '_self': + case self::SUBJECT_DELETE . '_self': + case self::SUBJECT_UPDATE . '_self': + return [ + 'addressbook' => $this->generateAddressbookParameter($parameters['addressbook'], $this->l), + 'card' => $this->generateCardParameter($parameters['card']), + ]; + } + + throw new \InvalidArgumentException(); + } + + private function generateCardParameter(array $cardData): array { + return [ + 'type' => 'addressbook-contact', + 'id' => $cardData['id'], + 'name' => $cardData['name'], + + ]; + } +} diff --git a/apps/dav/lib/Listener/CardListener.php b/apps/dav/lib/Listener/CardListener.php new file mode 100644 index 00000000000..c9a6c5fce1b --- /dev/null +++ b/apps/dav/lib/Listener/CardListener.php @@ -0,0 +1,111 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2021 Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + */ + +namespace OCA\DAV\Listener; + +use OCA\DAV\CardDAV\Activity\Provider\Card; +use OCA\DAV\CardDAV\Activity\Backend as ActivityBackend; +use OCA\DAV\CardDAV\CardDavBackend; +use OCA\DAV\Events\CardCreatedEvent; +use OCA\DAV\Events\CardDeletedEvent; +use OCA\DAV\Events\CardUpdatedEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use Psr\Log\LoggerInterface; +use Throwable; +use function sprintf; + +class CardListener implements IEventListener { + + /** @var ActivityBackend */ + private $activityBackend; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(ActivityBackend $activityBackend, + LoggerInterface $logger) { + $this->activityBackend = $activityBackend; + $this->logger = $logger; + } + + public function handle(Event $event): void { + if ($event instanceof CardCreatedEvent) { + try { + $this->activityBackend->triggerCardActivity( + Card::SUBJECT_ADD, + $event->getAddressBookData(), + $event->getShares(), + $event->getCardData() + ); + + $this->logger->debug( + sprintf('Activity generated for a new card in addressbook %d', $event->getAddressBookId()) + ); + } catch (Throwable $e) { + // Any error with activities shouldn't abort the addressbook creation, so we just log it + $this->logger->error('Error generating activities for a new card in addressbook: ' . $e->getMessage(), [ + 'exception' => $e, + ]); + } + } else if ($event instanceof CardUpdatedEvent) { + try { + $this->activityBackend->triggerCardActivity( + Card::SUBJECT_UPDATE, + $event->getAddressBookData(), + $event->getShares(), + $event->getCardData() + ); + + $this->logger->debug( + sprintf('Activity generated for a changed card in addressbook %d', $event->getAddressBookId()) + ); + } catch (Throwable $e) { + // Any error with activities shouldn't abort the addressbook update, so we just log it + $this->logger->error('Error generating activities for a changed card in addressbook: ' . $e->getMessage(), [ + 'exception' => $e, + ]); + } + } else if ($event instanceof CardDeletedEvent) { + try { + $this->activityBackend->triggerCardActivity( + Card::SUBJECT_DELETE, + $event->getAddressBookData(), + $event->getShares(), + $event->getCardData() + ); + + $this->logger->debug( + sprintf('Activity generated for a deleted card in addressbook %d', $event->getAddressBookId()) + ); + } catch (Throwable $e) { + // Any error with activities shouldn't abort the addressbook deletion, so we just log it + $this->logger->error('Error generating activities for a deleted card in addressbook: ' . $e->getMessage(), [ + 'exception' => $e, + ]); + } + } + } +} |