diff options
author | Thomas Citharel <tcit@tcit.fr> | 2019-03-16 16:19:25 +0100 |
---|---|---|
committer | Roeland Jago Douma <roeland@famdouma.nl> | 2019-08-15 20:02:56 +0200 |
commit | 7bddcc091d5fe0f5e01325e16524d44fe8c1fb74 (patch) | |
tree | 6af37e4e745f5816292a2c496d34b12afe95440e /apps | |
parent | f452e23a7db1742afa50eaa80b746afe769bdf7b (diff) | |
download | nextcloud-server-7bddcc091d5fe0f5e01325e16524d44fe8c1fb74.tar.gz nextcloud-server-7bddcc091d5fe0f5e01325e16524d44fe8c1fb74.zip |
Support event reminders (email and notifications)
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
Diffstat (limited to 'apps')
28 files changed, 2711 insertions, 6 deletions
diff --git a/apps/dav/appinfo/app.php b/apps/dav/appinfo/app.php index 70d01088be2..dd9e0e9c096 100644 --- a/apps/dav/appinfo/app.php +++ b/apps/dav/appinfo/app.php @@ -108,3 +108,6 @@ $calendarManager->register(function() use ($calendarManager, $app) { $app->setupCalendarProvider($calendarManager, $user->getUID()); } }); + +$app->registerNotifier(); +$app->registerCalendarReminders(); diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index dc90ac58188..91617e90024 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -23,6 +23,7 @@ <job>OCA\DAV\BackgroundJob\CleanupDirectLinksJob</job> <job>OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob</job> <job>OCA\DAV\BackgroundJob\CleanupInvitationTokenJob</job> + <job>OCA\DAV\BackgroundJob\EventReminderJob</job> </background-jobs> <repair-steps> diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 694231eebda..33977f31668 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -13,6 +13,7 @@ return array( 'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php', 'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php', 'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php', + 'OCA\\DAV\\BackgroundJob\\EventReminderJob' => $baseDir . '/../lib/BackgroundJob/EventReminderJob.php', 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', 'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => $baseDir . '/../lib/BackgroundJob/RefreshWebcalJob.php', 'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', @@ -51,6 +52,15 @@ return array( 'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => $baseDir . '/../lib/CalDAV/PublicCalendarRoot.php', 'OCA\\DAV\\CalDAV\\Publishing\\PublishPlugin' => $baseDir . '/../lib/CalDAV/Publishing/PublishPlugin.php', 'OCA\\DAV\\CalDAV\\Publishing\\Xml\\Publisher' => $baseDir . '/../lib/CalDAV/Publishing/Xml/Publisher.php', + 'OCA\\DAV\\CalDAV\\Reminder\\AbstractNotificationProvider' => $baseDir . '/../lib/CalDAV/Reminder/AbstractNotificationProvider.php', + 'OCA\\DAV\\CalDAV\\Reminder\\Backend' => $baseDir . '/../lib/CalDAV/Reminder/Backend.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProviderManager' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProviderManager.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\EmailProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\ProviderNotAvailableException' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\PushProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/PushProvider.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationTypeDoesNotExistException' => $baseDir . '/../lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php', + 'OCA\\DAV\\CalDAV\\Reminder\\Notifier' => $baseDir . '/../lib/CalDAV/Reminder/Notifier.php', + 'OCA\\DAV\\CalDAV\\Reminder\\ReminderService' => $baseDir . '/../lib/CalDAV/Reminder/ReminderService.php', 'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php', 'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php', 'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php', @@ -176,6 +186,7 @@ return array( 'OCA\\DAV\\Migration\\Version1005Date20180530124431' => $baseDir . '/../lib/Migration/Version1005Date20180530124431.php', 'OCA\\DAV\\Migration\\Version1006Date20180619154313' => $baseDir . '/../lib/Migration/Version1006Date20180619154313.php', 'OCA\\DAV\\Migration\\Version1006Date20180628111625' => $baseDir . '/../lib/Migration/Version1006Date20180628111625.php', + 'OCA\\DAV\\Migration\\Version1007Date20181005133326' => $baseDir . '/../lib/Migration/Version1007Date20181005133326.php', 'OCA\\DAV\\Migration\\Version1008Date20181030113700' => $baseDir . '/../lib/Migration/Version1008Date20181030113700.php', 'OCA\\DAV\\Migration\\Version1008Date20181105104826' => $baseDir . '/../lib/Migration/Version1008Date20181105104826.php', 'OCA\\DAV\\Migration\\Version1008Date20181105104833' => $baseDir . '/../lib/Migration/Version1008Date20181105104833.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 6f104916077..8ca83bb8b75 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -28,6 +28,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php', 'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php', 'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php', + 'OCA\\DAV\\BackgroundJob\\EventReminderJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/EventReminderJob.php', 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', 'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/RefreshWebcalJob.php', 'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', @@ -66,6 +67,15 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendarRoot.php', 'OCA\\DAV\\CalDAV\\Publishing\\PublishPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Publishing/PublishPlugin.php', 'OCA\\DAV\\CalDAV\\Publishing\\Xml\\Publisher' => __DIR__ . '/..' . '/../lib/CalDAV/Publishing/Xml/Publisher.php', + 'OCA\\DAV\\CalDAV\\Reminder\\AbstractNotificationProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/AbstractNotificationProvider.php', + 'OCA\\DAV\\CalDAV\\Reminder\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/Backend.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProviderManager' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProviderManager.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\EmailProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\ProviderNotAvailableException' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\PushProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/PushProvider.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationTypeDoesNotExistException' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php', + 'OCA\\DAV\\CalDAV\\Reminder\\Notifier' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/Notifier.php', + 'OCA\\DAV\\CalDAV\\Reminder\\ReminderService' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/ReminderService.php', 'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php', 'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php', 'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php', @@ -191,6 +201,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1005Date20180530124431' => __DIR__ . '/..' . '/../lib/Migration/Version1005Date20180530124431.php', 'OCA\\DAV\\Migration\\Version1006Date20180619154313' => __DIR__ . '/..' . '/../lib/Migration/Version1006Date20180619154313.php', 'OCA\\DAV\\Migration\\Version1006Date20180628111625' => __DIR__ . '/..' . '/../lib/Migration/Version1006Date20180628111625.php', + 'OCA\\DAV\\Migration\\Version1007Date20181005133326' => __DIR__ . '/..' . '/../lib/Migration/Version1007Date20181005133326.php', 'OCA\\DAV\\Migration\\Version1008Date20181030113700' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181030113700.php', 'OCA\\DAV\\Migration\\Version1008Date20181105104826' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105104826.php', 'OCA\\DAV\\Migration\\Version1008Date20181105104833' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105104833.php', diff --git a/apps/dav/js/settings-admin-caldav.js b/apps/dav/js/settings-admin-caldav.js index 1a40c208dfe..ad30ba6ad37 100644 --- a/apps/dav/js/settings-admin-caldav.js +++ b/apps/dav/js/settings-admin-caldav.js @@ -36,3 +36,9 @@ $('#caldavGenerateBirthdayCalendar').change(function() { $.post(OC.generateUrl('/apps/dav/disableBirthdayCalendar')); } }); + +$('#caldavSendRemindersNotifications').change(function() { + var val = $(this)[0].checked; + + OCP.AppConfig.setValue('dav', 'sendEventReminders', val ? 'yes' : 'no'); +}); diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 41570ee7442..2e3b95d8bfa 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -30,6 +30,12 @@ use OCA\DAV\CalDAV\Activity\Backend; use OCA\DAV\CalDAV\Activity\Provider\Event; use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\CalDAV\CalendarManager; +use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; +use OCA\DAV\CalDAV\Reminder\NotificationProviderManager; +use OCA\DAV\CalDAV\Reminder\Notifier; +use OCA\DAV\CalDAV\Reminder\ReminderService; use OCA\DAV\Capabilities; use OCA\DAV\CardDAV\ContactsManager; use OCA\DAV\CardDAV\PhotoCache; @@ -43,6 +49,8 @@ use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App { + const APP_ID = 'dav'; + /** * Application constructor. */ @@ -109,8 +117,7 @@ class Application extends App { } }); - // carddav/caldav sync event setup - $listener = function($event) { + $birthdayListener = function ($event) { if ($event instanceof GenericEvent) { /** @var BirthdayService $b */ $b = $this->getContainer()->query(BirthdayService::class); @@ -122,9 +129,9 @@ class Application extends App { } }; - $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $listener); - $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $listener); - $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function($event) { + $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $birthdayListener); + $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $birthdayListener); + $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function ($event) { if ($event instanceof GenericEvent) { /** @var BirthdayService $b */ $b = $this->getContainer()->query(BirthdayService::class); @@ -177,6 +184,11 @@ class Application extends App { $event->getArgument('calendarData'), $event->getArgument('shares') ); + + $reminderBackend = $this->getContainer()->query(ReminderBackend::class); + $reminderBackend->cleanRemindersForCalendar( + $event->getArgument('calendarId') + ); }); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function(GenericEvent $event) { /** @var Backend $backend */ @@ -187,6 +199,8 @@ class Application extends App { $event->getArgument('add'), $event->getArgument('remove') ); + + // Here we should recalculate if reminders should be sent to new or old sharees }); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', function(GenericEvent $event) { @@ -214,6 +228,16 @@ class Application extends App { $event->getArgument('shares'), $event->getArgument('objectData') ); + + /** @var ReminderService $reminderBackend */ + $reminderService= $this->getContainer()->query(ReminderService::class); + + $reminderService->onTouchCalendarObject( + $eventName, + $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); @@ -224,4 +248,28 @@ class Application extends App { return $this->getContainer()->query(SyncService::class); } + public function registerNotifier() { + $this->getContainer()->getServer()->getNotificationManager()->registerNotifier(function() { + return $this->getContainer()->query(Notifier::class); + }, function() { + $l = $this->getContainer()->getServer()->getL10NFactory()->get(self::APP_ID); + return [ + 'id' => self::APP_ID, + 'name' => $l->t('Calendars and Contacts'), + ]; + }); + } + + public function registerCalendarReminders(): void + { + try { + /** @var NotificationProviderManager $notificationProviderManager */ + $notificationProviderManager = $this->getContainer()->query(NotificationProviderManager::class); + $notificationProviderManager->registerProvider(EmailProvider::class); + $notificationProviderManager->registerProvider(PushProvider::class); + } catch(\Exception $ex) { + $this->getContainer()->getServer()->getLogger()->logException($ex); + } + } + } diff --git a/apps/dav/lib/BackgroundJob/EventReminderJob.php b/apps/dav/lib/BackgroundJob/EventReminderJob.php new file mode 100644 index 00000000000..e0a147e4203 --- /dev/null +++ b/apps/dav/lib/BackgroundJob/EventReminderJob.php @@ -0,0 +1,59 @@ +<?php +/** + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\BackgroundJob; + +use OC\BackgroundJob\TimedJob; +use OCA\DAV\CalDAV\Reminder\ReminderService; +use OCP\IConfig; + +class EventReminderJob extends TimedJob { + + /** @var ReminderService */ + private $reminderService; + + /** @var IConfig */ + private $config; + + /** + * EventReminderJob constructor. + * + * @param ReminderService $reminderService + * @param IConfig $config + */ + public function __construct(ReminderService $reminderService, IConfig $config) { + $this->reminderService = $reminderService; + $this->config = $config; + /** Run every 5 minutes */ + $this->setInterval(5); + } + + /** + * @param $arg + * @throws \OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException + * @throws \OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException + * @throws \OC\User\NoUserException + */ + public function run($arg): void + { + if ($this->config->getAppValue('dav', 'sendEventReminders', 'yes') === 'yes') { + $this->reminderService->processReminders(); + } + } +} diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 91281dc0cb3..62d3909ce39 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -1135,7 +1135,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription */ function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType=self::CALENDAR_TYPE_CALENDAR) { $extraData = $this->getDenormalizedData($calendarData); - $query = $this->db->getQueryBuilder(); $query->update('calendarobjects') ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB)) diff --git a/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php b/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php new file mode 100644 index 00000000000..ba928fac80f --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php @@ -0,0 +1,208 @@ +<?php +/** + * @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @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\CalDAV\Reminder; + +use \DateTime; +use \DateTimeImmutable; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as L10NFactory; +use OCP\IUser; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VEvent; +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\Parameter; +use Sabre\VObject\Property; + +abstract class AbstractNotificationProvider +{ + + public const NOTIFICATION_TYPE = ''; + + /** @var ILogger */ + protected $logger; + + /** @var L10NFactory */ + protected $l10nFactory; + + /** @var IL10N */ + protected $l10n; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** @var IConfig */ + protected $config; + + /** + * @param ILogger $logger + * @param L10NFactory $l10nFactory + * @param IConfig $config + * @param IUrlGenerator $urlGenerator + */ + public function __construct(ILogger $logger, L10NFactory $l10nFactory, IURLGenerator $urlGenerator, IConfig $config) { + $this->logger = $logger; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + $this->config = $config; + } + + /** + * Send notification + * + * @param VCalendar $vcalendar + * @param string $calendarDisplayName + * @param IUser $user + * @return void + */ + public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void {} + + /** + * @var VCalendar $vcalendar + * @var string $defaultValue + * @return array + * @throws \Exception + */ + protected function extractEventDetails(VCalendar $vcalendar, $defaultValue = ''): array + { + /** @var VEvent $vevent */ + $vevent = $vcalendar->VEVENT; + + /** @var Property $start */ + $start = $vevent->DTSTART; + if (isset($vevent->DTEND)) { + $end = $vevent->DTEND; + } elseif (isset($vevent->DURATION)) { + $isFloating = $vevent->DTSTART->isFloating(); + $end = clone $vevent->DTSTART; + $endDateTime = $end->getDateTime(); + $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue())); + $end->setDateTime($endDateTime, $isFloating); + } elseif (!$vevent->DTSTART->hasTime()) { + $isFloating = $vevent->DTSTART->isFloating(); + $end = clone $vevent->DTSTART; + $endDateTime = $end->getDateTime(); + $endDateTime = $endDateTime->modify('+1 day'); + $end->setDateTime($endDateTime, $isFloating); + } else { + $end = clone $vevent->DTSTART; + } + + return [ + 'title' => (string) $vevent->SUMMARY ?: $defaultValue, + 'description' => (string) $vevent->DESCRIPTION ?: $defaultValue, + 'start'=> $start->getDateTime(), + 'end' => $end->getDateTime(), + 'when' => $this->generateWhenString($start, $end), + 'url' => (string) $vevent->URL ?: $defaultValue, + 'location' => (string) $vevent->LOCATION ?: $defaultValue, + 'uid' => (string) $vevent->UID, + ]; + } + + /** + * @param Property $dtstart + * @param Property $dtend + * @return string + * @throws \Exception + */ + private function generateWhenString(Property $dtstart, Property $dtend): string + { + $isAllDay = $dtstart instanceof Property\ICalendar\Date; + + /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */ + /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */ + /** @var DateTimeImmutable $dtstartDt */ + $dtstartDt = $dtstart->getDateTime(); + /** @var DateTimeImmutable $dtendDt */ + $dtendDt = $dtend->getDateTime(); + + $diff = $dtstartDt->diff($dtendDt); + + $dtstartDt = new DateTime($dtstartDt->format(DateTime::ATOM)); + $dtendDt = new DateTime($dtendDt->format(DateTime::ATOM)); + + if ($isAllDay) { + // One day event + if ($diff->days === 1) { + return $this->l10n->l('date', $dtstartDt, ['width' => 'medium']); + } + + //event that spans over multiple days + $localeStart = $this->l10n->l('date', $dtstartDt, ['width' => 'medium']); + $localeEnd = $this->l10n->l('date', $dtendDt, ['width' => 'medium']); + + return $localeStart . ' - ' . $localeEnd; + } + + /** @var Property\ICalendar\DateTime $dtstart */ + /** @var Property\ICalendar\DateTime $dtend */ + $isFloating = $dtstart->isFloating(); + $startTimezone = $endTimezone = null; + if (!$isFloating) { + $prop = $dtstart->offsetGet('TZID'); + if ($prop instanceof Parameter) { + $startTimezone = $prop->getValue(); + } + + $prop = $dtend->offsetGet('TZID'); + if ($prop instanceof Parameter) { + $endTimezone = $prop->getValue(); + } + } + + $localeStart = $this->l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' . + $this->l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']); + + // always show full date with timezone if timezones are different + if ($startTimezone !== $endTimezone) { + $localeEnd = $this->l10n->l('datetime', $dtendDt, ['width' => 'medium|short']); + + return $localeStart . ' (' . $startTimezone . ') - ' . + $localeEnd . ' (' . $endTimezone . ')'; + } + + // show only end time if date is the same + if ($this->isDayEqual($dtstartDt, $dtendDt)) { + $localeEnd = $this->l10n->l('time', $dtendDt, ['width' => 'short']); + } else { + $localeEnd = $this->l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' . + $this->l10n->l('datetime', $dtendDt, ['width' => 'medium|short']); + } + + return $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')'; + } + + /** + * @param DateTime $dtStart + * @param DateTime $dtEnd + * @return bool + */ + private function isDayEqual(DateTime $dtStart, DateTime $dtEnd): bool + { + return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d'); + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/Backend.php b/apps/dav/lib/CalDAV/Reminder/Backend.php new file mode 100644 index 00000000000..c85e7c365dc --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/Backend.php @@ -0,0 +1,139 @@ +<?php +/** + * @copyright Copyright (c) 2019 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @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\CalDAV\Reminder; + +use OCP\IDBConnection; +use OCP\AppFramework\Utility\ITimeFactory; + +/** + * Class Backend + * + * @package OCA\DAV\CalDAV\Reminder + */ +class Backend { + + /** @var IDBConnection */ + protected $db; + + /** @var ITimeFactory */ + private $timeFactory; + + /** + * @param IDBConnection $db + * @param ITimeFactory $timeFactory + */ + public function __construct(IDBConnection $db, ITimeFactory $timeFactory) { + $this->db = $db; + $this->timeFactory = $timeFactory; + } + + /** + * @param string $uid + * @param string $calendarId + * @param string $uri + * @param string $type + * @param int $notificationDate + * @param int $eventStartDate + */ + public function insertReminder(string $uid, string $calendarId, string $uri, string $type, int $notificationDate, int $eventStartDate): void + { + $query = $this->db->getQueryBuilder(); + $query->insert('calendar_reminders') + ->values([ + 'uid' => $query->createNamedParameter($uid), + 'calendarid' => $query->createNamedParameter($calendarId), + 'objecturi' => $query->createNamedParameter($uri), + 'type' => $query->createNamedParameter($type), + 'notificationdate' => $query->createNamedParameter($notificationDate), + 'eventstartdate' => $query->createNamedParameter($eventStartDate), + ])->execute(); + } + + /** + * Cleans reminders in database + * + * @param int $calendarId + * @param string $objectUri + */ + public function cleanRemindersForEvent(int $calendarId, string $objectUri): void + { + $query = $this->db->getQueryBuilder(); + + $query->delete('calendar_reminders') + ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) + ->andWhere($query->expr()->eq('objecturi', $query->createNamedParameter($objectUri))) + ->execute(); + } + + /** + * Remove all reminders for a calendar + * + * @param integer $calendarId + * @return void + */ + public function cleanRemindersForCalendar(int $calendarId): void + { + $query = $this->db->getQueryBuilder(); + + $query->delete('calendar_reminders') + ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) + ->execute(); + } + + /** + * Remove a reminder by it's id + * + * @param integer $reminderId + * @return void + */ + public function removeReminder(int $reminderId): void + { + $query = $this->db->getQueryBuilder(); + + $query->delete('calendar_reminders') + ->where($query->expr()->eq('id', $query->createNamedParameter($reminderId))) + ->execute(); + } + + /** + * Get all reminders with a notification date before now + * + * @return array + * @throws \Exception + */ + public function getRemindersToProcess(): array + { + $query = $this->db->getQueryBuilder(); + $fields = ['cr.id', 'cr.calendarid', 'cr.objecturi', 'cr.type', 'cr.notificationdate', 'cr.uid', 'co.calendardata', 'c.displayname']; + $stmt = $query->select($fields) + ->from('calendar_reminders', 'cr') + ->where($query->expr()->lte('cr.notificationdate', $query->createNamedParameter($this->timeFactory->getTime()))) + ->andWhere($query->expr()->gte('cr.eventstartdate', $query->createNamedParameter($this->timeFactory->getTime()))) # We check that DTSTART isn't before + ->leftJoin('cr', 'calendars', 'c', $query->expr()->eq('cr.calendarid', 'c.id')) + ->leftJoin('cr', 'calendarobjects', 'co', $query->expr()->andX($query->expr()->eq('cr.calendarid', 'c.id'), $query->expr()->eq('co.uri', 'cr.objecturi'))) + ->execute(); + + return $stmt->fetchAll(); + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php new file mode 100644 index 00000000000..81d4474011c --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php @@ -0,0 +1,157 @@ +<?php +/** + * @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @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\CalDAV\Reminder\NotificationProvider; + +use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as L10NFactory; +use OCP\Mail\IEMailTemplate; +use OCP\Mail\IMailer; +use OCP\IUser; +use Sabre\VObject\Component\VCalendar; + +class EmailProvider extends AbstractNotificationProvider +{ + /** @var IMailer */ + private $mailer; + + public const NOTIFICATION_TYPE = 'EMAIL'; + + /** + * @param IConfig $config + * @param IMailer $mailer + * @param ILogger $logger + * @param L10NFactory $l10nFactory + * @param IUrlGenerator $urlGenerator + */ + public function __construct(IConfig $config, IMailer $mailer, ILogger $logger, + L10NFactory $l10nFactory, + IURLGenerator $urlGenerator) { + parent::__construct($logger, $l10nFactory, $urlGenerator, $config); + $this->mailer = $mailer; + } + + /** + * Send notification + * + * @param VCalendar $vcalendar + * @param string $calendarDisplayName + * @param IUser $user + * @return void + * @throws \Exception + */ + public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void + { + if ($user->getEMailAddress() === null) { + return; + } + + $lang = $this->config->getUserValue($user->getUID(), 'core', 'lang', $this->l10nFactory->findLanguage()); + $this->l10n = $this->l10nFactory->get('dav', $lang); + + $event = $this->extractEventDetails($vcalendar); + $fromEMail = \OCP\Util::getDefaultEmailAddress('invitations-noreply'); + + $message = $this->mailer->createMessage() + ->setFrom([$fromEMail => 'Nextcloud']) + // TODO: Set reply to from event creator + // ->setReplyTo([$sender => $senderName]) + ->setTo([$user->getEMailAddress() => $user->getDisplayName()]); + + $template = $this->mailer->createEMailTemplate('dav.calendarReminder', $event); + $template->addHeader(); + + $this->addSubjectAndHeading($template, $event['title']); + $this->addBulletList($template, $event, $calendarDisplayName); + + $template->addFooter(); + $message->useTemplate($template); + + $attachment = $this->mailer->createAttachment( + $vcalendar->serialize(), + $event['uid'].'.ics',// TODO(leon): Make file name unique, e.g. add event id + 'text/calendar' + ); + $message->attach($attachment); + + try { + $failed = $this->mailer->send($message); + if ($failed) { + $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]); + } + } catch(\Exception $ex) { + $this->logger->logException($ex, ['app' => 'dav']); + } + } + + /** + * @param IEMailTemplate $template + * @param string $summary + */ + private function addSubjectAndHeading(IEMailTemplate $template, string $summary): void + { + $template->setSubject('Notification: ' . $summary); + $template->addHeading($summary); + } + + /** + * @param IEMailTemplate $template + * @param array $eventData + * @param string $calendarDisplayName + */ + private function addBulletList(IEMailTemplate $template, array $eventData, string $calendarDisplayName): void + { + $template->addBodyListItem($calendarDisplayName, $this->l10n->t('Calendar:'), + $this->getAbsoluteImagePath('actions/info.svg')); + + $template->addBodyListItem($eventData['when'], $this->l10n->t('Date:'), + $this->getAbsoluteImagePath('places/calendar.svg')); + + if ($eventData['location']) { + $template->addBodyListItem((string) $eventData['location'], $this->l10n->t('Where:'), + $this->getAbsoluteImagePath('actions/address.svg')); + } + if ($eventData['description']) { + $template->addBodyListItem((string) $eventData['description'], $this->l10n->t('Description:'), + $this->getAbsoluteImagePath('actions/more.svg')); + } + if ($eventData['url']) { + $template->addBodyListItem((string) $eventData['url'], $this->l10n->t('Link:'), + $this->getAbsoluteImagePath('places/link.svg')); + } + } + + /** + * @param string $path + * @return string + */ + private function getAbsoluteImagePath($path): string + { + return $this->urlGenerator->getAbsoluteURL( + $this->urlGenerator->imagePath('core', $path) + ); + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php new file mode 100644 index 00000000000..bf736db8a34 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php @@ -0,0 +1,39 @@ +<?php +/** + * @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @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\CalDAV\Reminder\NotificationProvider; + +class ProviderNotAvailableException extends \Exception { + + /** + * ProviderNotAvailableException constructor. + * + * @since 16.0.0 + * + * @param string $type ReminderType + */ + public function __construct(string $type) { + parent::__construct("No notification provider for type $type available"); + } + +} diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php new file mode 100644 index 00000000000..1bb0e5c68b1 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php @@ -0,0 +1,101 @@ +<?php +/** + * @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @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\CalDAV\Reminder\NotificationProvider; + +use OCA\DAV\AppInfo\Application; +use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as L10NFactory; +use OCP\Notification\IManager; +use OCP\IUser; +use OCP\Notification\INotification; +use Sabre\VObject\Component\VCalendar; +use OCP\AppFramework\Utility\ITimeFactory; + +class PushProvider extends AbstractNotificationProvider +{ + + public const NOTIFICATION_TYPE = 'DISPLAY'; + + /** + * @var IManager + */ + private $manager; + + /** + * @var ITimeFactory + */ + private $timeFactory; + + /** + * @param IConfig $config + * @param IManager $manager + * @param ILogger $logger + * @param L10NFactory $l10nFactory + * @param IUrlGenerator $urlGenerator + * @param ITimeFactory $timeFactory + */ + public function __construct(IConfig $config, IManager $manager, ILogger $logger, + L10NFactory $l10nFactory, + IURLGenerator $urlGenerator, ITimeFactory $timeFactory) { + parent::__construct($logger, $l10nFactory, $urlGenerator, $config); + $this->manager = $manager; + $this->timeFactory = $timeFactory; + } + + /** + * Send notification + * + * @param VCalendar $vcalendar + * @param string $calendarDisplayName + * @param IUser $user + * @return void + * @throws \Exception + */ + public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void + { + + $lang = $this->config->getUserValue($user->getUID(), 'core', 'lang', $this->l10nFactory->findLanguage()); + $this->l10n = $this->l10nFactory->get('dav', $lang); + + $event = $this->extractEventDetails($vcalendar); + /** @var INotification $notification */ + $notification = $this->manager->createNotification(); + $notification->setApp(Application::APP_ID) + ->setUser($user->getUID()) + ->setDateTime($this->timeFactory->getDateTime()) + ->setObject(Application::APP_ID, $event['uid']) // $type and $id + ->setSubject('calendar_reminder', ['title' => $event['title'], 'start' => $event['start']->getTimestamp()]) // $subject and $parameters + ->setMessage('calendar_reminder', [ + 'when' => $event['when'], + 'description' => $event['description'], + 'location' => $event['location'], + 'calendar' => $calendarDisplayName + ]) + ; + $this->manager->notify($notification); + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php new file mode 100644 index 00000000000..389cbbd2bfa --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php @@ -0,0 +1,59 @@ +<?php +/** + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\CalDAV\Reminder; + +use OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException; + +class NotificationProviderManager { + + /** @var array */ + private $providers = []; + /** + * @var string $type + * @return AbstractNotificationProvider + * @throws ProviderNotAvailableException + * @throws NotificationTypeDoesNotExistException + */ + public function getProvider(string $type): AbstractNotificationProvider + { + if (in_array($type, ReminderService::REMINDER_TYPES, true)) { + if (isset($this->providers[$type])) { + return $this->providers[$type]; + } + throw new ProviderNotAvailableException($type); + } + throw new NotificationTypeDoesNotExistException($type); + } + + /** + * @param string $providerClassName + * @throws \OCP\AppFramework\QueryException + */ + public function registerProvider(string $providerClassName): void + { + $provider = \OC::$server->query($providerClassName); + + if (!$provider instanceof AbstractNotificationProvider) { + throw new \InvalidArgumentException('Invalid notification provider registered'); + } + + $this->providers[$provider::NOTIFICATION_TYPE] = $provider; + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php b/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php new file mode 100644 index 00000000000..ae4ec3bd3b7 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php @@ -0,0 +1,39 @@ +<?php +/** + * @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @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\CalDAV\Reminder; + +class NotificationTypeDoesNotExistException extends \Exception { + + /** + * NotificationTypeDoesNotExistException constructor. + * + * @since 16.0.0 + * + * @param string $type ReminderType + */ + public function __construct(string $type) { + parent::__construct("Type $type is not an accepted type of notification"); + } + +} diff --git a/apps/dav/lib/CalDAV/Reminder/Notifier.php b/apps/dav/lib/CalDAV/Reminder/Notifier.php new file mode 100644 index 00000000000..d95774e019e --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/Notifier.php @@ -0,0 +1,143 @@ +<?php +/** + * @copyright Copyright (c) 2019 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\CalDAV\Reminder; + +use OCA\DAV\AppInfo\Application; +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\Notification\INotification; +use OCP\Notification\INotifier; +use OCP\IURLGenerator; + +class Notifier implements INotifier { + + public static $units = array( + 'y' => 'year', + 'm' => 'month', + 'd' => 'day', + 'h' => 'hour', + 'i' => 'minute', + 's' => 'second', + ); + + /** @var IFactory */ + protected $factory; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** @var IL10N */ + protected $l; + + public function __construct(IFactory $factory, IURLGenerator $urlGenerator) { + $this->factory = $factory; + $this->urlGenerator = $urlGenerator; + } + + /** + * @param INotification $notification + * @param string $languageCode The code of the language that should be used to prepare the notification + * @return INotification + * @throws \Exception + */ + public function prepare(INotification $notification, $languageCode): INotification + { + if ($notification->getApp() !== Application::APP_ID) { + throw new \InvalidArgumentException('Notification not from this app'); + } + + // Read the language from the notification + $this->l = $this->factory->get('dav', $languageCode); + + if ($notification->getSubject() === 'calendar_reminder') { + $subjectParameters = $notification->getSubjectParameters(); + $notification->setParsedSubject($this->processEventTitle($subjectParameters)); + + $messageParameters = $notification->getMessageParameters(); + $notification->setParsedMessage($this->processEventDescription($messageParameters)); + $notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'places/calendar.svg'))); + return $notification; + } + // Unknown subject => Unknown notification => throw + throw new \InvalidArgumentException('Unknown subject'); + } + + /** + * @param array $event + * @return string + * @throws \Exception + */ + private function processEventTitle(array $event): string + { + $event_datetime = new \DateTime(); + $event_datetime->setTimestamp($event['start']); + $now = new \DateTime(); + + $diff = $event_datetime->diff($now); + + foreach (self::$units as $attribute => $unit) { + $count = $diff->$attribute; + if (0 !== $count) { + return $this->getPluralizedTitle($count, $diff->invert, $unit, $event['title']); + } + } + return ''; + } + + /** + * + * @param int $count + * @param int $invert + * @param string $unit + * @param string $title + * @return string + */ + private function getPluralizedTitle(int $count, int $invert, string $unit, string $title): string + { + if ($invert) { + return $this->l->n('%s (in one %s)', '%s (in %n %ss)', $count, [$title, $unit]); + } + // This should probably not show up + return $this->l->n('%s (one %s ago)', '%s (%n %ss ago)', $count, [$title, $unit]); + } + + /** + * @param array $event + * @return string + */ + private function processEventDescription(array $event): string + { + $description = [ + $this->l->t('Calendar: %s', $event['calendar']), + $this->l->t('Date: %s', $event['when']), + ]; + + if ($event['description']) { + $description[] = $this->l->t('Description: %s', $event['description']); + } + if ($event['location']) { + $description[] = $this->l->t('Where: %s', $event['location']); + } + return implode('<br>', $description); + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php new file mode 100644 index 00000000000..87c2ce10673 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php @@ -0,0 +1,185 @@ +<?php +/** + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\CalDAV\Reminder; + +use OC\User\NoUserException; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUserManager; +use OCP\IUserSession; +use Sabre\VObject; +use Sabre\VObject\Component\VAlarm; +use Sabre\VObject\Reader; + +class ReminderService { + + /** @var Backend */ + private $backend; + + /** @var NotificationProviderManager */ + private $notificationProviderManager; + + /** @var IUserManager */ + private $userManager; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IUserSession */ + private $userSession; + + public const REMINDER_TYPE_EMAIL = 'EMAIL'; + public const REMINDER_TYPE_DISPLAY = 'DISPLAY'; + public const REMINDER_TYPE_AUDIO = 'AUDIO'; + + public const REMINDER_TYPES = [self::REMINDER_TYPE_EMAIL, self::REMINDER_TYPE_DISPLAY, self::REMINDER_TYPE_AUDIO]; + + public function __construct(Backend $backend, + NotificationProviderManager $notificationProviderManager, + IUserManager $userManager, + IGroupManager $groupManager, + IUserSession $userSession) { + $this->backend = $backend; + $this->notificationProviderManager = $notificationProviderManager; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->userSession = $userSession; + } + + /** + * Process reminders to activate + * + * @throws NoUserException + * @throws NotificationProvider\ProviderNotAvailableException + * @throws NotificationTypeDoesNotExistException + */ + public function processReminders(): void + { + + $reminders = $this->backend->getRemindersToProcess(); + + foreach ($reminders as $reminder) { + $calendarData = Reader::read($reminder['calendardata']); + + $user = $this->userManager->get($reminder['uid']); + + if ($user === null) { + throw new NoUserException('User not found for calendar'); + } + + $notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']); + $notificationProvider->send($calendarData, $reminder['displayname'], $user); + $this->backend->removeReminder($reminder['id']); + } + } + + /** + * Saves reminders when a calendar object with some alarms was created/updated/deleted + * + * @param string $action + * @param array $calendarData + * @param array $shares + * @param array $objectData + * @return void + * @throws VObject\InvalidDataException + * @throws NoUserException + */ + public function onTouchCalendarObject(string $action, array $calendarData, array $shares, array $objectData): void + { + if (!isset($calendarData['principaluri'])) { + return; + } + + // Always remove existing reminders for this event + $this->backend->cleanRemindersForEvent($objectData['calendarid'], $objectData['uri']); + + /** + * If we are deleting the event, no need to go further + */ + if ($action === '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') { + return; + } + + $user = $this->userSession->getUser(); + + if ($user === null) { + throw new NoUserException('No user in session'); + } + + $users = $this->getUsersForShares($shares); + + $users[] = $user->getUID(); + + $vobject = VObject\Reader::read($objectData['calendardata']); + + foreach ($vobject->VEVENT->VALARM as $alarm) { + if ($alarm instanceof VAlarm) { + $type = strtoupper($alarm->ACTION->getValue()); + if (in_array($type, self::REMINDER_TYPES, true)) { + $time = $alarm->getEffectiveTriggerTime(); + + foreach ($users as $uid) { + $this->backend->insertReminder( + $uid, + $objectData['calendarid'], + $objectData['uri'], + $type, + $time->getTimestamp(), + $vobject->VEVENT->DTSTART->getDateTime()->getTimestamp()); + + } + } + } + } + } + + + /** + * Get all users that have access to a given calendar + * + * @param array $shares + * @return string[] + */ + private function getUsersForShares(array $shares): array + { + $users = $groups = []; + foreach ($shares as $share) { + $principal = explode('/', $share['{http://owncloud.org/ns}principal']); + if ($principal[1] === 'users') { + $users[] = $principal[2]; + } else if ($principal[1] === 'groups') { + $groups[] = $principal[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/Migration/Version1007Date20181005133326.php b/apps/dav/lib/Migration/Version1007Date20181005133326.php new file mode 100644 index 00000000000..1e4cce950ac --- /dev/null +++ b/apps/dav/lib/Migration/Version1007Date20181005133326.php @@ -0,0 +1,82 @@ +<?php + +declare(strict_types=1); + +namespace OCA\DAV\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; +use Doctrine\DBAL\Types\Type; + +/** + * Auto-generated migration step: Please modify to your needs! + */ +class Version1007Date20181005133326 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if (!$schema->hasTable('calendar_reminders')) { + $table = $schema->createTable('calendar_reminders'); + + $table->addColumn('id', Type::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('uid', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('calendarid', Type::BIGINT, [ + 'notnull' => false, + 'length' => 11, + ]); + $table->addColumn('objecturi', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('type', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('notificationdate', Type::DATETIME, [ + 'notnull' => false, + ]); + $table->addColumn('eventstartdate', Type::DATETIME, [ + 'notnull' => false, + ]); + + $table->setPrimaryKey(['id']); + $table->addIndex(['calendarid'], 'calendar_reminder_calendars'); + + return $schema; + } + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + } +} diff --git a/apps/dav/lib/Settings/CalDAVSettings.php b/apps/dav/lib/Settings/CalDAVSettings.php index f38143b5b4e..958c463b1d3 100644 --- a/apps/dav/lib/Settings/CalDAVSettings.php +++ b/apps/dav/lib/Settings/CalDAVSettings.php @@ -48,6 +48,7 @@ class CalDAVSettings implements ISettings { $parameters = [ 'send_invitations' => $this->config->getAppValue('dav', 'sendInvitations', 'yes'), 'generate_birthday_calendar' => $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes'), + 'send_reminders_notifications' => $this->config->getAppValue('dav', 'sendEventReminders', 'yes'), ]; return new TemplateResponse('dav', 'settings-admin-caldav', $parameters); diff --git a/apps/dav/templates/settings-admin-caldav.php b/apps/dav/templates/settings-admin-caldav.php index 87b159923d2..ba55a884702 100644 --- a/apps/dav/templates/settings-admin-caldav.php +++ b/apps/dav/templates/settings-admin-caldav.php @@ -72,4 +72,25 @@ script('dav', [ <em><?php p($l->t('Birthday calendars will be generated by a background job.')); ?></em><br> <em><?php p($l->t('Hence they will not be available immediately after enabling but will show up after some time.')); ?></em> </p> + <p> + <input type="checkbox" name="caldav_send_reminders_notifications" id="caldavSendRemindersNotifications" class="checkbox" + <?php ($_['send_reminders_notifications'] === 'yes') ? print_unescaped('checked="checked"') : null ?>/> + <label for="caldavSendRemindersNotifications"><?php p($l->t('Send notifications for events')); ?></label> + <br> + <em> + <?php print_unescaped(str_replace( + [ + '{emailopen}', + '{linkclose}', + ], + [ + '<a href="../admin#mail_general_settings">', + '</a>', + ], + $l->t('Please make sure to properly set up {emailopen}the email server{linkclose}.') + )); ?> + </em> + <br> + <em><?php p($l->t('Notifications will be send through background jobs, so these need to happen often enough.')); ?></em> + </p> </form> diff --git a/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php new file mode 100644 index 00000000000..960dd481dd2 --- /dev/null +++ b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php @@ -0,0 +1,68 @@ +<?php +declare(strict_types=1); +/** + * @copyright 2018, Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @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\Tests\unit\BackgroundJob; + +use OCA\DAV\BackgroundJob\EventReminderJob; +use OCA\DAV\CalDAV\Reminder\ReminderService; +use OCP\IConfig; +use Test\TestCase; + +class EventReminderJobTest extends TestCase { + + /** @var ReminderService|\PHPUnit\Framework\MockObject\MockObject */ + private $reminderService; + + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + private $config; + + /** @var EventReminderJob|\PHPUnit\Framework\MockObject\MockObject */ + private $backgroundJob; + + protected function setUp() { + parent::setUp(); + + $this->reminderService = $this->createMock(ReminderService::class); + $this->config = $this->createMock(IConfig::class); + + $this->backgroundJob = new EventReminderJob($this->reminderService, $this->config); + } + + public function data(): array + { + return [[true], [false]]; + } + + /** + * @dataProvider data + * @param bool $sendEventReminders + */ + public function testRun(bool $sendEventReminders): void + { + $this->config->expects($this->once())->method('getAppValue')->with('dav', 'sendEventReminders', 'yes')->willReturn($sendEventReminders ? 'yes' : 'no'); + $this->reminderService->expects($this->exactly($sendEventReminders ? 1 : 0))->method('processReminders'); + + $this->backgroundJob->run([]); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php new file mode 100644 index 00000000000..ba2e54af33c --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php @@ -0,0 +1,87 @@ +<?php +/** + * @copyright Copyright (c) 2019, Thomas Citharel + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Tests\unit\CalDAV\Reminder; + +use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as L10NFactory; +use OCP\IUser; +use Test\TestCase; +use Sabre\VObject\Component\VCalendar; + +abstract class AbstractNotificationProviderTest extends TestCase { + + /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ + protected $logger; + + /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10nFactory; + + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10n; + + /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ + protected $urlGenerator; + + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var AbstractNotificationProvider|\PHPUnit\Framework\MockObject\MockObject */ + protected $provider; + + /** + * @var VCalendar + */ + protected $vcalendar; + + /** + * @var string + */ + protected $calendarDisplayName; + + /** + * @var IUser|\PHPUnit\Framework\MockObject\MockObject + */ + protected $user; + + public function setUp() { + parent::setUp(); + + $this->logger = $this->createMock(ILogger::class); + $this->l10nFactory = $this->createMock(L10NFactory::class); + $this->l10n = $this->createMock(IL10N::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->config = $this->createMock(IConfig::class); + + $this->vcalendar = new VCalendar(); + $this->vcalendar->add('VEVENT', [ + 'SUMMARY' => 'Fellowship meeting', + 'DTSTART' => new \DateTime('2017-01-01 00:00:00') // 1483228800 + ]); + $this->calendarDisplayName = 'Personal'; + + $this->user = $this->createMock(IUser::class); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php b/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php new file mode 100644 index 00000000000..1cd979dc5d9 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php @@ -0,0 +1,313 @@ +<?php +/** + * @copyright Copyright (c) 2018, Thomas Citharel + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Tests\unit\CalDAV\Reminder; + +use OCP\IDBConnection; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\AppFramework\Utility\ITimeFactory; +use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend; +use Test\TestCase; + +class BackendTest extends TestCase { + + /** + * Reminder Backend + * + * @var ReminderBackend|\PHPUnit\Framework\MockObject\MockObject + */ + private $reminderBackend; + + /** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */ + private $dbConnection; + + /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ + private $timeFactory; + + public function setUp() { + parent::setUp(); + + $this->dbConnection = $this->createMock(IDBConnection::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->reminderBackend = new ReminderBackend($this->dbConnection, $this->timeFactory); + } + + public function testCleanRemindersForEvent(): void + { + /** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */ + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + + $expr->method('eq') + ->will($this->returnValueMap([ + ['calendarid', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ['objecturi', 'createNamedParameter-2', null, 'WHERE_CLAUSE_2'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + [1, \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ['object.ics', \PDO::PARAM_STR, null, 'createNamedParameter-2'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('delete') + ->with('calendar_reminders') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(3)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(6)) + ->method('andWhere') + ->with('WHERE_CLAUSE_2') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(7)) + ->method('execute') + ->with() + ->willReturn($stmt); + + $this->reminderBackend->cleanRemindersForEvent(1, 'object.ics'); + } + + public function testCleanRemindersForCalendar(): void + { + /** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */ + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + + $expr->method('eq') + ->will($this->returnValueMap([ + ['calendarid', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + [1337, \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('delete') + ->with('calendar_reminders') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(3)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('execute') + ->with() + ->willReturn($stmt); + + $this->reminderBackend->cleanRemindersForCalendar(1337); + } + + public function testRemoveReminder(): void + { + /** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */ + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + + $expr->method('eq') + ->will($this->returnValueMap([ + ['id', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + [16, \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('delete') + ->with('calendar_reminders') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(3)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('execute') + ->with() + ->willReturn($stmt); + + $this->reminderBackend->removeReminder(16); + } + + public function testGetRemindersToProcess(): void + { + $dbData = [[ + 'cr.id' => 30, + 'cr.calendarid' => 3, + 'cr.objecturi' => 'object.ics', + 'cr.type' => 'EMAIL', + 'cr.notificationdate' => 1337, + 'cr.uid' => 'user1', + 'co.calendardata' => 'BEGIN:VCALENDAR', + 'c.displayname' => 'My Calendar' + ]]; + + $this->timeFactory->expects($this->exactly(2)) + ->method('getTime') + ->with() + ->willReturn(1337); + + /** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */ + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->willReturn($queryBuilder); + $queryBuilder->method('expr') + ->willReturn($expr); + + $expr->method('eq') + ->willReturnMap([ + ['cr.calendarid', 'c.id', null, 'EQ_CLAUSE_1'], + ['co.uri', 'cr.objecturi', null, 'EQ_CLAUSE_2'], + ]); + $expr->method('andX') + ->willReturnMap([ + ['EQ_CLAUSE_1', 'EQ_CLAUSE_2', 'ANDX_CLAUSE'], + ]); + + $expr->method('lte') + ->with('cr.notificationdate', 'createNamedParameter-1', null) + ->willReturn('LTE_CLAUSE_1'); + + $expr->method('gte') + ->with('cr.eventstartdate', 'createNamedParameter-1', null) + ->willReturn('GTE_CLAUSE_2'); + + $queryBuilder->method('createNamedParameter') + ->willReturnMap([ + [1337, \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ]); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['cr.id', 'cr.calendarid', 'cr.objecturi', 'cr.type', 'cr.notificationdate', 'cr.uid', 'co.calendardata', 'c.displayname']) + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with('calendar_reminders', 'cr') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('LTE_CLAUSE_1') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(7)) + ->method('andWhere') + ->with('GTE_CLAUSE_2') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(9)) + ->method('leftJoin') + ->with('cr', 'calendars', 'c', 'EQ_CLAUSE_1') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(13)) + ->method('leftJoin') + ->with('cr', 'calendarobjects', 'co', 'ANDX_CLAUSE') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(14)) + ->method('execute') + ->with() + ->willReturn($stmt); + + $stmt->expects($this->once()) + ->method('fetchAll') + ->with() + ->willReturn($dbData); + + $actual = $this->reminderBackend->getRemindersToProcess(); + $this->assertEquals($dbData, $actual); + } + + public function testInsertReminder(): void + { + /** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */ + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['user1', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ['1', \PDO::PARAM_STR, null, 'createNamedParameter-2'], + ['object.ics', \PDO::PARAM_STR, null, 'createNamedParameter-3'], + ['EMAIL', \PDO::PARAM_STR, null, 'createNamedParameter-4'], + [1227, \PDO::PARAM_STR, null, 'createNamedParameter-5'], + [1337, \PDO::PARAM_STR, null, 'createNamedParameter-6'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('insert') + ->with('calendar_reminders') + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(7)) + ->method('values') + ->with([ + 'uid' => 'createNamedParameter-1', + 'calendarid' => 'createNamedParameter-2', + 'objecturi' => 'createNamedParameter-3', + 'type' => 'createNamedParameter-4', + 'notificationdate' => 'createNamedParameter-5', + 'eventstartdate' => 'createNamedParameter-6', + ]) + ->willReturn($queryBuilder); + $queryBuilder->expects($this->at(8)) + ->method('execute') + ->with() + ->willReturn($stmt); + + $actual = $this->reminderBackend->insertReminder('user1', '1', 'object.ics', 'EMAIL', 1227, 1337); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php new file mode 100644 index 00000000000..34a61b34fc5 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php @@ -0,0 +1,226 @@ +<?php +/** + * @copyright Copyright (c) 2019, Thomas Citharel + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Tests\unit\CalDAV\Reminder\NotificationProvider; + +use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider; +use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as L10NFactory; +use OCP\IUser; +use OCP\Mail\IEMailTemplate; +use OCP\Mail\IMailer; +use OCP\Mail\IAttachment; +use OCP\Mail\IMessage; +use Test\TestCase; +use OCA\DAV\Tests\unit\CalDAV\Reminder\AbstractNotificationProviderTest; + +class EmailProviderTest extends AbstractNotificationProviderTest { + + const USER_EMAIL = 'frodo@hobb.it'; + + /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ + protected $logger; + + /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10nFactory; + + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10n; + + /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ + protected $urlGenerator; + + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var IMailer|\PHPUnit\Framework\MockObject\MockObject */ + private $mailer; + + public function setUp() { + parent::setUp(); + + $this->mailer = $this->createMock(IMailer::class); + + $this->provider = new EmailProvider( + $this->config, + $this->mailer, + $this->logger, + $this->l10nFactory, + $this->urlGenerator + ); + } + + public function testSendWithNoUserEmail(): void + { + $this->user->expects($this->once()) + ->method('getEMailAddress') + ->with() + ->willReturn(null); + + $this->mailer + ->expects($this->never()) + ->method('send'); + + $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); + } + + public function testSendWithFailedRecipients(): void + { + $this->user->expects($this->exactly(2)) + ->method('getEMailAddress') + ->with() + ->willReturn(self::USER_EMAIL); + + $this->mailer + ->expects($this->once()) + ->method('send') + ->willReturn([self::USER_EMAIL]) + ; + + $this->logger + ->expects($this->once()) + ->method('error'); + + $l10n = $this->createMock(IL10N::class); + $this->l10nFactory + ->method('get') + ->willReturn($l10n); + + $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); + } + + public function testSendWithMailerFailure(): void + { + $this->user->expects($this->exactly(2)) + ->method('getEMailAddress') + ->with() + ->willReturn(self::USER_EMAIL); + + $ex = new \Exception(); + + $this->mailer + ->expects($this->once()) + ->method('send') + ->will($this->throwException($ex)) + ; + + $this->logger + ->expects($this->once()) + ->method('logException') + ->with($ex, ['app' => 'dav']); + + $l10n = $this->createMock(IL10N::class); + $this->l10nFactory + ->method('get') + ->willReturn($l10n); + + $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); + } + + public function testSend(): void + { + $this->user->expects($this->exactly(2)) + ->method('getEMailAddress') + ->with() + ->willReturn(self::USER_EMAIL); + + $this->user->expects($this->once()) + ->method('getDisplayName') + ->with() + ->willReturn('Frodo'); + + $this->urlGenerator + ->expects($this->exactly(2)) + ->method('getAbsoluteURL'); + + $this->urlGenerator + ->expects($this->exactly(2)) + ->method('imagePath'); + + $mailMessage = $this->createMock(IMessage::class); + $mailMessage->expects($this->once()) + ->method('setFrom') + ->with([\OCP\Util::getDefaultEmailAddress('invitations-noreply') => 'Nextcloud']) + ->willReturn($mailMessage); + + $mailMessage->expects($this->once()) + ->method('setTo') + ->with([self::USER_EMAIL => 'Frodo']) + ->willReturn($mailMessage); + + $mailMessage + ->expects($this->never()) + ->method('setReplyTo') + ->willReturn($mailMessage); + + $emailTemplate = $this->createMock(IEMailTemplate::class); + $this->mailer + ->expects($this->once()) + ->method('createEMailTemplate') + ->willReturn($emailTemplate); + + $emailTemplate->expects($this->once()) + ->method('setSubject') + ->with('Notification: Fellowship meeting'); + + $emailTemplate->expects($this->once()) + ->method('addHeader'); + + $emailTemplate->expects($this->once()) + ->method('addHeading'); + + $emailTemplate->expects($this->exactly(2)) + ->method('addBodyListItem'); + + $emailTemplate->expects($this->once()) + ->method('addFooter'); + + $mailMessage->expects($this->once()) + ->method('useTemplate') + ->with($emailTemplate); + + $this->mailer + ->expects($this->once()) + ->method('createMessage') + ->willReturn($mailMessage); + + $emailAttachment = $this->createMock(IAttachment::class); + $this->mailer + ->expects($this->once()) + ->method('createAttachment') + ->willReturn($emailAttachment); + + $this->mailer + ->expects($this->once()) + ->method('send'); + + $l10n = $this->createMock(IL10N::class); + $this->l10nFactory + ->method('get') + ->willReturn($l10n); + + $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php new file mode 100644 index 00000000000..e10afb44d27 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php @@ -0,0 +1,139 @@ +<?php +/** + * @copyright Copyright (c) 2019, Thomas Citharel + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Tests\unit\CalDAV\Reminder\NotificationProvider; + +use OCA\DAV\AppInfo\Application; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; +use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as L10NFactory; +use OCP\IUser; +use OCP\Notification\IManager; +use OCP\Notification\INotification; +use OCP\AppFramework\Utility\ITimeFactory; +use Test\TestCase; +use OCA\DAV\Tests\unit\CalDAV\Reminder\AbstractNotificationProviderTest; + +class PushProviderTest extends AbstractNotificationProviderTest { + + /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ + protected $logger; + + /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10nFactory; + + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10n; + + /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ + protected $urlGenerator; + + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ + private $manager; + + /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ + private $timeFactory; + + public function setUp() { + parent::setUp(); + + $this->manager = $this->createMock(IManager::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + + $this->provider = new PushProvider( + $this->config, + $this->manager, + $this->logger, + $this->l10nFactory, + $this->urlGenerator, + $this->timeFactory + ); + } + + public function testSend(): void + { + $notification = $this->createMock(INotification::class); + $notification + ->expects($this->once()) + ->method('setApp') + ->with(Application::APP_ID) + ->willReturn($notification); + + $notification + ->expects($this->once()) + ->method('setUser') + ->willReturn($notification) + ; + + $notification + ->expects($this->once()) + ->method('setDateTime') + ->willReturn($notification) + ; + + $notification + ->expects($this->once()) + ->method('setObject') + ->willReturn($notification) + ; + + $notification + ->expects($this->once()) + ->method('setSubject') + ->willReturn($notification) + ; + + $notification + ->expects($this->once()) + ->method('setMessage') + ->willReturn($notification) + ; + + $this->manager + ->expects($this->once()) + ->method('createNotification') + ->willReturn($notification); + + $this->manager + ->expects($this->once()) + ->method('notify') + ->with($notification); + + $l10n = $this->createMock(IL10N::class); + $this->l10nFactory + ->method('get') + ->willReturn($l10n); + + $this->timeFactory->expects($this->once()) + ->method('getDateTime') + ->with() + ->willReturn(new \DateTime()); + + $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php new file mode 100644 index 00000000000..d962b631c10 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php @@ -0,0 +1,100 @@ +<?php +/** + * @copyright Copyright (c) 2019, Thomas Citharel + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Tests\unit\CalDAV\Reminder; + +use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; +use OCA\DAV\CalDAV\Reminder\NotificationProviderManager; +use OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException; +use OCA\DAV\Capabilities; +use Test\TestCase; + +class NotificationProviderManagerTest extends TestCase { + + /** @var NotificationProviderManager|\PHPUnit\Framework\MockObject\MockObject */ + private $providerManager; + + /** + * @throws \OCP\AppFramework\QueryException + */ + public function setUp() { + parent::setUp(); + + $this->providerManager = new NotificationProviderManager(); + $this->providerManager->registerProvider(EmailProvider::class); + } + + /** + * @expectedException OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException + * @expectedExceptionMessage Type NOT EXISTENT is not an accepted type of notification + * @throws ProviderNotAvailableException + * @throws NotificationTypeDoesNotExistException + */ + public function testGetProviderForUnknownType(): void + { + $this->providerManager->getProvider('NOT EXISTENT'); + } + + /** + * @expectedException OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException + * @expectedExceptionMessage No notification provider for type AUDIO available + * @throws NotificationTypeDoesNotExistException + * @throws ProviderNotAvailableException + */ + public function testGetProviderForUnRegisteredType(): void + { + $this->providerManager->getProvider('AUDIO'); + } + + /** + * @throws NotificationTypeDoesNotExistException + * @throws ProviderNotAvailableException + */ + public function testGetProvider(): void + { + $provider = $this->providerManager->getProvider('EMAIL'); + $this->assertInstanceOf(EmailProvider::class, $provider); + } + + /** + * @throws NotificationTypeDoesNotExistException + * @throws ProviderNotAvailableException + * @throws \OCP\AppFramework\QueryException + */ + public function testRegisterProvider(): void + { + $this->providerManager->registerProvider(PushProvider::class); + $provider = $this->providerManager->getProvider('DISPLAY'); + $this->assertInstanceOf(PushProvider::class, $provider); + } + + /** + * @expectedExceptionMessage Invalid notification provider registered + * @expectedException \InvalidArgumentException + * @throws \OCP\AppFramework\QueryException + */ + public function testRegisterBadProvider(): void + { + $this->providerManager->registerProvider(Capabilities::class); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php new file mode 100644 index 00000000000..b9695b33fdc --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php @@ -0,0 +1,184 @@ +<?php +/** + * @copyright Copyright (c) 2019 Thomas Citharel <tcit@tcit.fr> + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Tests\unit\CalDAV\Reminder; + +use OCA\DAV\AppInfo\Application; +use OCA\DAV\CalDAV\Reminder\Notifier; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; +use OCP\Notification\INotification; +use Test\TestCase; + +class NotifierTest extends TestCase { + /** @var Notifier */ + protected $notifier; + + /** @var IFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $factory; + /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ + protected $urlGenerator; + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + protected $l; + + protected function setUp() { + parent::setUp(); + + $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->l = $this->createMock(IL10N::class); + $this->l->expects($this->any()) + ->method('t') + ->willReturnCallback(function($string, $args) { + return vsprintf($string, $args); + }); + $this->l->expects($this->any()) + ->method('n') + ->willReturnCallback(function($textSingular, $textPlural, $count, $args) { + $text = $count === 1 ? $textSingular : $textPlural; + $text = str_replace('%n', (string)$count, $text); + return vsprintf($text, $args); + }); + $this->factory = $this->createMock(IFactory::class); + $this->factory->expects($this->any()) + ->method('get') + ->willReturn($this->l); + + $this->notifier = new Notifier( + $this->factory, + $this->urlGenerator + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Notification not from this app + */ + public function testPrepareWrongApp(): void + { + /** @var INotification|\PHPUnit\Framework\MockObject\MockObject $notification */ + $notification = $this->createMock(INotification::class); + + $notification->expects($this->once()) + ->method('getApp') + ->willReturn('notifications'); + $notification->expects($this->never()) + ->method('getSubject'); + + $this->notifier->prepare($notification, 'en'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unknown subject + */ + public function testPrepareWrongSubject() { + /** @var INotification|\PHPUnit\Framework\MockObject\MockObject $notification */ + $notification = $this->createMock(INotification::class); + + $notification->expects($this->once()) + ->method('getApp') + ->willReturn(Application::APP_ID); + $notification->expects($this->once()) + ->method('getSubject') + ->willReturn('wrong subject'); + + $this->notifier->prepare($notification, 'en'); + } + + public function dataPrepare(): array + { + return [ + [ + 'calendar_reminder', + [ + 'title' => 'foo', + 'start' => time() - 60 * 60 * 24 + ], + 'foo (one day ago)', + [ + 'when' => 'foo', + 'description' => 'bar', + 'location' => 'NC Headquarters', + 'calendar' => 'Personal' + ], + 'Calendar: Personal<br>Date: foo<br>Description: bar<br>Where: NC Headquarters' + ], + ]; + } + + /** + * @dataProvider dataPrepare + * + * @param string $subjectType + * @param array $subjectParams + * @param string $subject + * @param array $messageParams + * @param string $message + * @throws \Exception + */ + public function testPrepare(string $subjectType, array $subjectParams, string $subject, array $messageParams, string $message): void + { + /** @var INotification|\PHPUnit\Framework\MockObject\MockObject $notification */ + $notification = $this->createMock(INotification::class); + + $notification->expects($this->once()) + ->method('getApp') + ->willReturn(Application::APP_ID); + $notification->expects($this->once()) + ->method('getSubject') + ->willReturn($subjectType); + $notification->expects($this->once()) + ->method('getSubjectParameters') + ->willReturn($subjectParams); + $notification->expects($this->once()) + ->method('getMessageParameters') + ->willReturn($messageParams); + + $notification->expects($this->once()) + ->method('setParsedSubject') + ->with($subject) + ->willReturnSelf(); + + $notification->expects($this->once()) + ->method('setParsedMessage') + ->with($message) + ->willReturnSelf(); + + $this->urlGenerator->expects($this->once()) + ->method('imagePath') + ->with('core', 'places/calendar.svg') + ->willReturn('icon-url'); + $this->urlGenerator->expects($this->once()) + ->method('getAbsoluteURL') + ->with('icon-url') + ->willReturn('absolute-icon-url'); + $notification->expects($this->once()) + ->method('setIcon') + ->with('absolute-icon-url') + ->willReturnSelf(); + + $return = $this->notifier->prepare($notification, 'en'); + + $this->assertEquals($notification, $return); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php new file mode 100644 index 00000000000..061c3f16e38 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php @@ -0,0 +1,276 @@ +<?php +/** + * @copyright Copyright (c) 2019, Thomas Citharel + * + * @author Thomas Citharel <tcit@tcit.fr> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\DAV\Tests\unit\CalDAV\Reminder; + +use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use OCA\DAV\CalDAV\Reminder\Backend; +use OCA\DAV\CalDAV\Reminder\NotificationProviderManager; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; +use OCA\DAV\CalDAV\Reminder\ReminderService; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use Test\TestCase; + +class ReminderServiceTest extends TestCase { + + /** @var Backend|\PHPUnit\Framework\MockObject\MockObject */ + private $backend; + + /** @var NotificationProviderManager|\PHPUnit\Framework\MockObject\MockObject */ + private $notificationProviderManager; + + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + private $userManager; + + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject*/ + private $groupManager; + + /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ + private $userSession; + + public const CALENDAR_DATA = <<<EOD +BEGIN:VCALENDAR +PRODID:-//Nextcloud calendar v1.6.4 +BEGIN:VEVENT +CREATED:20160602T133732 +DTSTAMP:20160602T133732 +LAST-MODIFIED:20160602T133732 +UID:wej2z68l9h +SUMMARY:Test Event +LOCATION:Somewhere ... +DESCRIPTION:maybe .... +DTSTART;TZID=Europe/Berlin;VALUE=DATE:20160609 +DTEND;TZID=Europe/Berlin;VALUE=DATE:20160610 +BEGIN:VALARM +ACTION:EMAIL +TRIGGER:-PT15M +END:VALARM +END:VEVENT +END:VCALENDAR +EOD; + + public function setUp() { + parent::setUp(); + + $this->backend = $this->createMock(Backend::class); + $this->notificationProviderManager = $this->createMock(NotificationProviderManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->groupManager = $this->createMock(IGroupManager::class); + $this->userSession = $this->createMock(IUserSession::class); + } + + public function dataTestProcessReminders(): array + { + return [ + [ + [], null + ], + [ + [ + [ + 'calendardata' => self::CALENDAR_DATA, + 'displayname' => 'Personal', + 'type' => 'EMAIL', + 'uid' => 1, + 'id' => 1, + ], + ], + $this->createMock(EmailProvider::class), + ], + [ + [ + [ + 'calendardata' => self::CALENDAR_DATA, + 'displayname' => 'Personal', + 'type' => 'DISPLAY', + 'uid' => 1, + 'id' => 1, + ], + ], + $this->createMock(PushProvider::class), + ] + ]; + } + + /** + * @dataProvider dataTestProcessReminders + * @param array $reminders + * @param AbstractNotificationProvider|null $notificationProvider + * @throws \OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException + * @throws \OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException + * @throws \OC\User\NoUserException + */ + public function testProcessReminders(array $reminders, ?AbstractNotificationProvider $notificationProvider): void + { + $user = $this->createMock(IUser::class); + + $this->backend->expects($this->once())->method('getRemindersToProcess')->willReturn($reminders); + if (count($reminders) > 0) { + $this->userManager->expects($this->exactly(count($reminders)))->method('get')->willReturn($user); + $this->backend->expects($this->exactly(count($reminders)))->method('removeReminder'); + $this->notificationProviderManager->expects($this->exactly(count($reminders)))->method('getProvider')->willReturn($notificationProvider); + } + + $reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession); + $reminderService->processReminders(); + } + + /** + * @expectedException OC\User\NoUserException + */ + public function testProcessReminderWithBadUser(): void + { + $this->backend->expects($this->once())->method('getRemindersToProcess')->willReturn([ + [ + 'calendardata' => self::CALENDAR_DATA, + 'type' => 'DISPLAY', + 'uid' => 1, + 'id' => 1, + ] + ]); + $this->userManager->expects($this->once())->method('get')->with(1)->willReturn(null); + $reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession); + $reminderService->processReminders(); + } + + public function providesTouchCalendarObject(): array + { + return [ + [ + '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', + [ + 'principaluri' => 'principals/users/personal' + ], + [], + [ + 'calendarid' => 1, + 'uri' => 'something.ics', + ], + 0 + ], + [ + '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', + [ + 'principaluri' => 'principals/users/personal' + ], + [], + [ + 'calendarid' => 1, + 'uri' => 'something.ics', + 'calendardata' => self::CALENDAR_DATA + ], + 0 + ], + [ + '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', + [ + 'principaluri' => 'principals/users/someone', + 'uri' => 'personal' + ], + [ + [ + '{http://owncloud.org/ns}principal' => 'principals/users/someone' + ] + ], + [ + 'calendarid' => 1, + 'uri' => 'something.ics', + 'calendardata' => self::CALENDAR_DATA + ], + 0 + ], + [ + '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', + [ + 'principaluri' => 'principals/users/someone', + 'uri' => 'personal' + ], + [ + [ + '{http://owncloud.org/ns}principal' => 'principals/groups/somegroup' + ] + ], + [ + 'calendarid' => 1, + 'uri' => 'something.ics', + 'calendardata' => self::CALENDAR_DATA + ], + 1 + ] + ]; + } + + /** + * @dataProvider providesTouchCalendarObject + * @param string $action + * @param array $calendarData + * @param array $shares + * @param array $objectData + * @param int $numberOfGroups + * @throws \OC\User\NoUserException + * @throws \Sabre\VObject\InvalidDataException + */ + public function testOnTouchCalendarObject(string $action, array $calendarData, array $shares, array $objectData, int $numberOfGroups): void + { + $this->backend->expects($this->once())->method('cleanRemindersForEvent')->with($objectData['calendarid'], $objectData['uri']); + + if ($action !== '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('user'); + + $this->userSession->expects($this->once())->method('getUser')->willReturn($user); + if ($numberOfGroups === 0) { + $this->backend->expects($this->exactly(count($shares) + 1))->method('insertReminder'); + } else { + $group = $this->createMock(IGroup::class); + $groupUser = $this->createMock(IUser::class); + $groupUser->expects($this->once())->method('getUID')->willReturn('groupuser'); + $group->expects($this->once())->method('getUsers')->willReturn([$groupUser]); + $this->groupManager->expects($this->exactly($numberOfGroups))->method('get')->willReturn($group); + } + } + $reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession); + $reminderService->onTouchCalendarObject($action, $calendarData, $shares, $objectData); + } + + /** + * @expectedException OC\User\NoUserException + */ + public function testOnTouchCalendarObjectWithNoSession(): void + { + $this->backend->expects($this->once())->method('cleanRemindersForEvent'); + $this->userSession->expects($this->once())->method('getUser')->willReturn(null); + + $reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession); + $reminderService->onTouchCalendarObject('', ['principaluri' => 'foo'], [], ['calendarid' => 1, 'uri' => 'bar']); + } + + public function testOnTouchCalendarObjectWithNoCalendarURI(): void + { + $reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession); + $this->assertNull($reminderService->onTouchCalendarObject('', [], [], [])); + } +} |