From 7bddcc091d5fe0f5e01325e16524d44fe8c1fb74 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sat, 16 Mar 2019 16:19:25 +0100 Subject: [PATCH 01/10] Support event reminders (email and notifications) Signed-off-by: Thomas Citharel --- apps/dav/appinfo/app.php | 3 + apps/dav/appinfo/info.xml | 1 + .../composer/composer/autoload_classmap.php | 11 + .../dav/composer/composer/autoload_static.php | 11 + apps/dav/js/settings-admin-caldav.js | 6 + apps/dav/lib/AppInfo/Application.php | 58 +++- .../lib/BackgroundJob/EventReminderJob.php | 59 ++++ apps/dav/lib/CalDAV/CalDavBackend.php | 1 - .../Reminder/AbstractNotificationProvider.php | 208 ++++++++++++ apps/dav/lib/CalDAV/Reminder/Backend.php | 139 ++++++++ .../NotificationProvider/EmailProvider.php | 157 +++++++++ .../ProviderNotAvailableException.php | 39 +++ .../NotificationProvider/PushProvider.php | 101 ++++++ .../Reminder/NotificationProviderManager.php | 59 ++++ .../NotificationTypeDoesNotExistException.php | 39 +++ apps/dav/lib/CalDAV/Reminder/Notifier.php | 143 ++++++++ .../lib/CalDAV/Reminder/ReminderService.php | 185 +++++++++++ .../Version1007Date20181005133326.php | 82 +++++ apps/dav/lib/Settings/CalDAVSettings.php | 1 + apps/dav/templates/settings-admin-caldav.php | 21 ++ .../BackgroundJob/EventReminderJobTest.php | 68 ++++ .../AbstractNotificationProviderTest.php | 87 +++++ .../unit/CalDAV/Reminder/BackendTest.php | 313 ++++++++++++++++++ .../EmailProviderTest.php | 226 +++++++++++++ .../NotificationProvider/PushProviderTest.php | 139 ++++++++ .../NotificationProviderManagerTest.php | 100 ++++++ .../unit/CalDAV/Reminder/NotifierTest.php | 184 ++++++++++ .../CalDAV/Reminder/ReminderServiceTest.php | 276 +++++++++++++++ 28 files changed, 2711 insertions(+), 6 deletions(-) create mode 100644 apps/dav/lib/BackgroundJob/EventReminderJob.php create mode 100644 apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php create mode 100644 apps/dav/lib/CalDAV/Reminder/Backend.php create mode 100644 apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php create mode 100644 apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php create mode 100644 apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php create mode 100644 apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php create mode 100644 apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php create mode 100644 apps/dav/lib/CalDAV/Reminder/Notifier.php create mode 100644 apps/dav/lib/CalDAV/Reminder/ReminderService.php create mode 100644 apps/dav/lib/Migration/Version1007Date20181005133326.php create mode 100644 apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php diff --git a/apps/dav/appinfo/app.php b/apps/dav/appinfo/app.php index 70d01088be..dd9e0e9c09 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 dc90ac5818..91617e9002 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -23,6 +23,7 @@ OCA\DAV\BackgroundJob\CleanupDirectLinksJob OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob OCA\DAV\BackgroundJob\CleanupInvitationTokenJob + OCA\DAV\BackgroundJob\EventReminderJob diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 694231eebd..33977f3166 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 6f10491607..8ca83bb8b7 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 1a40c208df..ad30ba6ad3 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 41570ee744..2e3b95d8bf 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 0000000000..e0a147e420 --- /dev/null +++ b/apps/dav/lib/BackgroundJob/EventReminderJob.php @@ -0,0 +1,59 @@ + + * + * @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 + * + */ +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 91281dc0cb..62d3909ce3 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 0000000000..ba928fac80 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php @@ -0,0 +1,208 @@ + + * + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\CalDAV\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 0000000000..c85e7c365d --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/Backend.php @@ -0,0 +1,139 @@ + + * + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\CalDAV\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 0000000000..81d4474011 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php @@ -0,0 +1,157 @@ + + * + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\CalDAV\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 0000000000..bf736db8a3 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php @@ -0,0 +1,39 @@ + + * + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\CalDAV\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 0000000000..1bb0e5c68b --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php @@ -0,0 +1,101 @@ + + * + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\CalDAV\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 0000000000..389cbbd2bf --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php @@ -0,0 +1,59 @@ + + * + * @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 + * + */ +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 0000000000..ae4ec3bd3b --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php @@ -0,0 +1,39 @@ + + * + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\CalDAV\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 0000000000..d95774e019 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/Notifier.php @@ -0,0 +1,143 @@ + + * + * @author Thomas Citharel + * + * @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 + * + */ + +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('
', $description); + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php new file mode 100644 index 0000000000..87c2ce1067 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php @@ -0,0 +1,185 @@ + + * + * @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 + * + */ +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 0000000000..1e4cce950a --- /dev/null +++ b/apps/dav/lib/Migration/Version1007Date20181005133326.php @@ -0,0 +1,82 @@ +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 f38143b5b4..958c463b1d 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 87b159923d..ba55a88470 100644 --- a/apps/dav/templates/settings-admin-caldav.php +++ b/apps/dav/templates/settings-admin-caldav.php @@ -72,4 +72,25 @@ script('dav', [ t('Birthday calendars will be generated by a background job.')); ?>
t('Hence they will not be available immediately after enabling but will show up after some time.')); ?>

+

+ /> + +
+ + ', + '', + ], + $l->t('Please make sure to properly set up {emailopen}the email server{linkclose}.') + )); ?> + +
+ t('Notifications will be send through background jobs, so these need to happen often enough.')); ?> +

diff --git a/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php new file mode 100644 index 0000000000..960dd481dd --- /dev/null +++ b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php @@ -0,0 +1,68 @@ + + * + * @author Thomas Citharel + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Tests\unit\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 0000000000..ba2e54af33 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php @@ -0,0 +1,87 @@ + + * + * @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 + * + */ +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 0000000000..1cd979dc5d --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php @@ -0,0 +1,313 @@ + + * + * @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 + * + */ +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 0000000000..34a61b34fc --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php @@ -0,0 +1,226 @@ + + * + * @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 + * + */ +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 0000000000..e10afb44d2 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php @@ -0,0 +1,139 @@ + + * + * @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 + * + */ +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 0000000000..d962b631c1 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php @@ -0,0 +1,100 @@ + + * + * @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 + * + */ +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 0000000000..b9695b33fd --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php @@ -0,0 +1,184 @@ + + * + * @author Thomas Citharel + * + * @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 + * + */ + +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
Date: foo
Description: bar
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 0000000000..061c3f16e3 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php @@ -0,0 +1,276 @@ + + * + * @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 + * + */ +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 = <<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('', [], [], [])); + } +} From d74315ac3d31d7ec30fa7fa13ef87c34bd4f6181 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 2 Aug 2019 16:25:35 +0200 Subject: [PATCH 02/10] add occ dav:send-event-reminders, so you don't have to rely on the background-job Signed-off-by: Georg Ehrke --- apps/dav/appinfo/info.xml | 1 + .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + apps/dav/lib/Command/SendEventReminders.php | 74 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 apps/dav/lib/Command/SendEventReminders.php diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 91617e9002..6e0219e91d 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -46,6 +46,7 @@ OCA\DAV\Command\CreateCalendar OCA\DAV\Command\MoveCalendar OCA\DAV\Command\ListCalendars + OCA\DAV\Command\SendEventReminders OCA\DAV\Command\SyncBirthdayCalendar OCA\DAV\Command\SyncSystemAddressBook OCA\DAV\Command\RemoveInvalidShares diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 33977f3166..59bcf7caf4 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -96,6 +96,7 @@ return array( 'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php', 'OCA\\DAV\\Command\\MoveCalendar' => $baseDir . '/../lib/Command/MoveCalendar.php', 'OCA\\DAV\\Command\\RemoveInvalidShares' => $baseDir . '/../lib/Command/RemoveInvalidShares.php', + 'OCA\\DAV\\Command\\SendEventReminders' => $baseDir . '/../lib/Command/SendEventReminders.php', 'OCA\\DAV\\Command\\SyncBirthdayCalendar' => $baseDir . '/../lib/Command/SyncBirthdayCalendar.php', 'OCA\\DAV\\Command\\SyncSystemAddressBook' => $baseDir . '/../lib/Command/SyncSystemAddressBook.php', 'OCA\\DAV\\Comments\\CommentNode' => $baseDir . '/../lib/Comments/CommentNode.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 8ca83bb8b7..a28743ce43 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -111,6 +111,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php', 'OCA\\DAV\\Command\\MoveCalendar' => __DIR__ . '/..' . '/../lib/Command/MoveCalendar.php', 'OCA\\DAV\\Command\\RemoveInvalidShares' => __DIR__ . '/..' . '/../lib/Command/RemoveInvalidShares.php', + 'OCA\\DAV\\Command\\SendEventReminders' => __DIR__ . '/..' . '/../lib/Command/SendEventReminders.php', 'OCA\\DAV\\Command\\SyncBirthdayCalendar' => __DIR__ . '/..' . '/../lib/Command/SyncBirthdayCalendar.php', 'OCA\\DAV\\Command\\SyncSystemAddressBook' => __DIR__ . '/..' . '/../lib/Command/SyncSystemAddressBook.php', 'OCA\\DAV\\Comments\\CommentNode' => __DIR__ . '/..' . '/../lib/Comments/CommentNode.php', diff --git a/apps/dav/lib/Command/SendEventReminders.php b/apps/dav/lib/Command/SendEventReminders.php new file mode 100644 index 0000000000..02d8a28726 --- /dev/null +++ b/apps/dav/lib/Command/SendEventReminders.php @@ -0,0 +1,74 @@ + + * + * @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 + * + */ +namespace OCA\DAV\Command; + +use OCA\DAV\CalDAV\Reminder\ReminderService; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class SendEventReminders extends Command { + + /** @var ReminderService */ + protected $reminderService; + + /** @var IConfig */ + protected $config; + + public function __construct(string $name = null, + ReminderService $reminderService, + IConfig $config) { + parent::__construct($name); + $this->reminderService = $reminderService; + $this->config = $config; + } + + /** + * @inheritDoc + */ + protected function configure():void { + $this + ->setName('dav:send-event-reminders') + ->setDescription('Sends event reminders'); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + */ + protected function execute(InputInterface $input, OutputInterface $output):void { + if ($this->config->getAppValue('dav', 'sendEventReminders', 'yes') !== 'yes') { + $output->writeln('Sending event reminders disabled!'); + $output->writeln('Please run "php occ config:app:set dav sendEventReminders --value yes"'); + return; + } + + if ($this->config->getAppValue('dav', 'sendEventRemindersMode', 'backgroundjob') !== 'occ') { + $output->writeln('Sending event reminders mode set to background-job!'); + $output->writeln('Please run "php occ config:app:set dav sendEventRemindersMode --value occ"'); + return; + } + + $this->reminderService->processReminders(); + } +} From 36b9b51297165afcb9071d72f83cf431b01b4edd Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 2 Aug 2019 16:26:51 +0200 Subject: [PATCH 03/10] run send reminders background-job only when mode is not set to occ Signed-off-by: Georg Ehrke --- apps/dav/lib/BackgroundJob/EventReminderJob.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/dav/lib/BackgroundJob/EventReminderJob.php b/apps/dav/lib/BackgroundJob/EventReminderJob.php index e0a147e420..dfa76ffe16 100644 --- a/apps/dav/lib/BackgroundJob/EventReminderJob.php +++ b/apps/dav/lib/BackgroundJob/EventReminderJob.php @@ -50,10 +50,15 @@ class EventReminderJob extends TimedJob { * @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(); + public function run($arg):void { + if ($this->config->getAppValue('dav', 'sendEventReminders', 'yes') !== 'yes') { + return; } + + if ($this->config->getAppValue('dav', 'sendEventRemindersMode', 'backgroundjob') !== 'backgroundjob') { + return; + } + + $this->reminderService->processReminders(); } } From 11fa45196ec5229e640a4e27569f94929510d7da Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 2 Aug 2019 16:34:39 +0200 Subject: [PATCH 04/10] smaller syntax changes to CalDAV reminder classes Signed-off-by: Georg Ehrke --- .../Reminder/AbstractNotificationProvider.php | 15 ++++++--------- apps/dav/lib/CalDAV/Reminder/Backend.php | 15 +++++---------- .../NotificationProvider/EmailProvider.php | 17 +++++++---------- .../NotificationProvider/PushProvider.php | 8 +++----- .../Reminder/NotificationProviderManager.php | 6 ++---- apps/dav/lib/CalDAV/Reminder/Notifier.php | 17 +++++++---------- .../dav/lib/CalDAV/Reminder/ReminderService.php | 16 ++++++++-------- 7 files changed, 38 insertions(+), 56 deletions(-) diff --git a/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php b/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php index ba928fac80..1d858627de 100644 --- a/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php @@ -37,9 +37,9 @@ use Sabre\VObject\DateTimeParser; use Sabre\VObject\Parameter; use Sabre\VObject\Property; -abstract class AbstractNotificationProvider -{ +abstract class AbstractNotificationProvider { + /** @var string */ public const NOTIFICATION_TYPE = ''; /** @var ILogger */ @@ -78,7 +78,7 @@ abstract class AbstractNotificationProvider * @param IUser $user * @return void */ - public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void {} + abstract function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void; /** * @var VCalendar $vcalendar @@ -86,8 +86,7 @@ abstract class AbstractNotificationProvider * @return array * @throws \Exception */ - protected function extractEventDetails(VCalendar $vcalendar, $defaultValue = ''): array - { + protected function extractEventDetails(VCalendar $vcalendar, $defaultValue = ''):array { /** @var VEvent $vevent */ $vevent = $vcalendar->VEVENT; @@ -129,8 +128,7 @@ abstract class AbstractNotificationProvider * @return string * @throws \Exception */ - private function generateWhenString(Property $dtstart, Property $dtend): string - { + private function generateWhenString(Property $dtstart, Property $dtend):string { $isAllDay = $dtstart instanceof Property\ICalendar\Date; /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */ @@ -201,8 +199,7 @@ abstract class AbstractNotificationProvider * @param DateTime $dtEnd * @return bool */ - private function isDayEqual(DateTime $dtStart, DateTime $dtEnd): 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 index c85e7c365d..087a5785f3 100644 --- a/apps/dav/lib/CalDAV/Reminder/Backend.php +++ b/apps/dav/lib/CalDAV/Reminder/Backend.php @@ -56,8 +56,7 @@ class Backend { * @param int $notificationDate * @param int $eventStartDate */ - public function insertReminder(string $uid, string $calendarId, string $uri, string $type, int $notificationDate, int $eventStartDate): void - { + 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([ @@ -76,8 +75,7 @@ class Backend { * @param int $calendarId * @param string $objectUri */ - public function cleanRemindersForEvent(int $calendarId, string $objectUri): void - { + public function cleanRemindersForEvent(int $calendarId, string $objectUri):void { $query = $this->db->getQueryBuilder(); $query->delete('calendar_reminders') @@ -92,8 +90,7 @@ class Backend { * @param integer $calendarId * @return void */ - public function cleanRemindersForCalendar(int $calendarId): void - { + public function cleanRemindersForCalendar(int $calendarId):void { $query = $this->db->getQueryBuilder(); $query->delete('calendar_reminders') @@ -107,8 +104,7 @@ class Backend { * @param integer $reminderId * @return void */ - public function removeReminder(int $reminderId): void - { + public function removeReminder(int $reminderId):void { $query = $this->db->getQueryBuilder(); $query->delete('calendar_reminders') @@ -122,8 +118,7 @@ class Backend { * @return array * @throws \Exception */ - public function getRemindersToProcess(): array - { + 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) diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php index 81d4474011..f05439932b 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php @@ -33,11 +33,12 @@ use OCP\Mail\IMailer; use OCP\IUser; use Sabre\VObject\Component\VCalendar; -class EmailProvider extends AbstractNotificationProvider -{ +class EmailProvider extends AbstractNotificationProvider { + /** @var IMailer */ private $mailer; + /** @var string */ public const NOTIFICATION_TYPE = 'EMAIL'; /** @@ -63,8 +64,7 @@ class EmailProvider extends AbstractNotificationProvider * @return void * @throws \Exception */ - public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void - { + public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user):void { if ($user->getEMailAddress() === null) { return; } @@ -111,8 +111,7 @@ class EmailProvider extends AbstractNotificationProvider * @param IEMailTemplate $template * @param string $summary */ - private function addSubjectAndHeading(IEMailTemplate $template, string $summary): void - { + private function addSubjectAndHeading(IEMailTemplate $template, string $summary):void { $template->setSubject('Notification: ' . $summary); $template->addHeading($summary); } @@ -122,8 +121,7 @@ class EmailProvider extends AbstractNotificationProvider * @param array $eventData * @param string $calendarDisplayName */ - private function addBulletList(IEMailTemplate $template, array $eventData, string $calendarDisplayName): void - { + private function addBulletList(IEMailTemplate $template, array $eventData, string $calendarDisplayName):void { $template->addBodyListItem($calendarDisplayName, $this->l10n->t('Calendar:'), $this->getAbsoluteImagePath('actions/info.svg')); @@ -148,8 +146,7 @@ class EmailProvider extends AbstractNotificationProvider * @param string $path * @return string */ - private function getAbsoluteImagePath($path): string - { + private function getAbsoluteImagePath(string $path):string { return $this->urlGenerator->getAbsoluteURL( $this->urlGenerator->imagePath('core', $path) ); diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php index 1bb0e5c68b..f04b8e4c45 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php @@ -35,9 +35,9 @@ use OCP\Notification\INotification; use Sabre\VObject\Component\VCalendar; use OCP\AppFramework\Utility\ITimeFactory; -class PushProvider extends AbstractNotificationProvider -{ +class PushProvider extends AbstractNotificationProvider { + /** @var string */ public const NOTIFICATION_TYPE = 'DISPLAY'; /** @@ -75,9 +75,7 @@ class PushProvider extends AbstractNotificationProvider * @return void * @throws \Exception */ - public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void - { - + 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); diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php index 389cbbd2bf..0a2579aedf 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php @@ -31,8 +31,7 @@ class NotificationProviderManager { * @throws ProviderNotAvailableException * @throws NotificationTypeDoesNotExistException */ - public function getProvider(string $type): AbstractNotificationProvider - { + public function getProvider(string $type):AbstractNotificationProvider { if (in_array($type, ReminderService::REMINDER_TYPES, true)) { if (isset($this->providers[$type])) { return $this->providers[$type]; @@ -46,8 +45,7 @@ class NotificationProviderManager { * @param string $providerClassName * @throws \OCP\AppFramework\QueryException */ - public function registerProvider(string $providerClassName): void - { + public function registerProvider(string $providerClassName):void { $provider = \OC::$server->query($providerClassName); if (!$provider instanceof AbstractNotificationProvider) { diff --git a/apps/dav/lib/CalDAV/Reminder/Notifier.php b/apps/dav/lib/CalDAV/Reminder/Notifier.php index d95774e019..ff34208a79 100644 --- a/apps/dav/lib/CalDAV/Reminder/Notifier.php +++ b/apps/dav/lib/CalDAV/Reminder/Notifier.php @@ -31,14 +31,15 @@ use OCP\IURLGenerator; class Notifier implements INotifier { - public static $units = array( + /** @var array */ + public static $units = [ 'y' => 'year', 'm' => 'month', 'd' => 'day', 'h' => 'hour', 'i' => 'minute', 's' => 'second', - ); + ]; /** @var IFactory */ protected $factory; @@ -60,8 +61,7 @@ class Notifier implements INotifier { * @return INotification * @throws \Exception */ - public function prepare(INotification $notification, $languageCode): INotification - { + public function prepare(INotification $notification, string $languageCode):INotification { if ($notification->getApp() !== Application::APP_ID) { throw new \InvalidArgumentException('Notification not from this app'); } @@ -87,8 +87,7 @@ class Notifier implements INotifier { * @return string * @throws \Exception */ - private function processEventTitle(array $event): string - { + private function processEventTitle(array $event):string { $event_datetime = new \DateTime(); $event_datetime->setTimestamp($event['start']); $now = new \DateTime(); @@ -112,8 +111,7 @@ class Notifier implements INotifier { * @param string $title * @return string */ - private function getPluralizedTitle(int $count, int $invert, string $unit, string $title): 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]); } @@ -125,8 +123,7 @@ class Notifier implements INotifier { * @param array $event * @return string */ - private function processEventDescription(array $event): string - { + private function processEventDescription(array $event):string { $description = [ $this->l->t('Calendar: %s', $event['calendar']), $this->l->t('Date: %s', $event['when']), diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php index 87c2ce1067..ce6c846061 100644 --- a/apps/dav/lib/CalDAV/Reminder/ReminderService.php +++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php @@ -49,7 +49,11 @@ class ReminderService { 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 const REMINDER_TYPES = [ + self::REMINDER_TYPE_EMAIL, + self::REMINDER_TYPE_DISPLAY, + self::REMINDER_TYPE_AUDIO + ]; public function __construct(Backend $backend, NotificationProviderManager $notificationProviderManager, @@ -70,9 +74,7 @@ class ReminderService { * @throws NotificationProvider\ProviderNotAvailableException * @throws NotificationTypeDoesNotExistException */ - public function processReminders(): void - { - + public function processReminders():void { $reminders = $this->backend->getRemindersToProcess(); foreach ($reminders as $reminder) { @@ -101,8 +103,7 @@ class ReminderService { * @throws VObject\InvalidDataException * @throws NoUserException */ - public function onTouchCalendarObject(string $action, array $calendarData, array $shares, array $objectData): void - { + public function onTouchCalendarObject(string $action, array $calendarData, array $shares, array $objectData):void { if (!isset($calendarData['principaluri'])) { return; } @@ -157,8 +158,7 @@ class ReminderService { * @param array $shares * @return string[] */ - private function getUsersForShares(array $shares): array - { + private function getUsersForShares(array $shares):array { $users = $groups = []; foreach ($shares as $share) { $principal = explode('/', $share['{http://owncloud.org/ns}principal']); From a6f792616c37bb63fa826bb6d897738ad5227d6d Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 2 Aug 2019 16:36:50 +0200 Subject: [PATCH 05/10] Implement getID and getName in Reminder/Notifier as required since Nextcloud 17 Signed-off-by: Georg Ehrke --- apps/dav/lib/CalDAV/Reminder/Notifier.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/dav/lib/CalDAV/Reminder/Notifier.php b/apps/dav/lib/CalDAV/Reminder/Notifier.php index ff34208a79..3718d5b29a 100644 --- a/apps/dav/lib/CalDAV/Reminder/Notifier.php +++ b/apps/dav/lib/CalDAV/Reminder/Notifier.php @@ -55,6 +55,26 @@ class Notifier implements INotifier { $this->urlGenerator = $urlGenerator; } + /** + * Identifier of the notifier, only use [a-z0-9_] + * + * @return string + * @since 17.0.0 + */ + public function getID():string { + return 'dav'; + } + + /** + * Human readable name describing the notifier + * + * @return string + * @since 17.0.0 + */ + public function getName():string { + return $this->factory->get('dav')->t('Calendar'); + } + /** * @param INotification $notification * @param string $languageCode The code of the language that should be used to prepare the notification From c5147529af4fac3eb8ac7bc4db3d10867600edaf Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 2 Aug 2019 16:40:35 +0200 Subject: [PATCH 06/10] Stop using deprecated registerNotifier, use registerNotifierService instead Signed-off-by: Georg Ehrke --- apps/dav/lib/AppInfo/Application.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 2e3b95d8bf..1c5505d058 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -248,20 +248,14 @@ 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 registerNotifier():void { + $this->getContainer() + ->getServer() + ->getNotificationManager() + ->registerNotifierService(Notifier::class); } - public function registerCalendarReminders(): void - { + public function registerCalendarReminders():void { try { /** @var NotificationProviderManager $notificationProviderManager */ $notificationProviderManager = $this->getContainer()->query(NotificationProviderManager::class); From 7c4a8a3bdf6d8c0c12cf7cab93cc465c83c4ec8f Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 2 Aug 2019 16:49:53 +0200 Subject: [PATCH 07/10] Provide a fake AudioProvider that is basically the same as PushProvider, better then not showing reminders at all for now Signed-off-by: Georg Ehrke --- .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + apps/dav/lib/AppInfo/Application.php | 2 ++ .../NotificationProvider/AudioProvider.php | 36 +++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 59bcf7caf4..bd26290cc9 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -55,6 +55,7 @@ return array( '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\\AudioProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/AudioProvider.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', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index a28743ce43..1a0ce9d02f 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -70,6 +70,7 @@ class ComposerStaticInitDAV '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\\AudioProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/AudioProvider.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', diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 1c5505d058..5d17dc5a24 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -31,6 +31,7 @@ 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\AudioProvider; use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider; use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; use OCA\DAV\CalDAV\Reminder\NotificationProviderManager; @@ -259,6 +260,7 @@ class Application extends App { try { /** @var NotificationProviderManager $notificationProviderManager */ $notificationProviderManager = $this->getContainer()->query(NotificationProviderManager::class); + $notificationProviderManager->registerProvider(AudioProvider::class); $notificationProviderManager->registerProvider(EmailProvider::class); $notificationProviderManager->registerProvider(PushProvider::class); } catch(\Exception $ex) { diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php new file mode 100644 index 0000000000..6e702bcaca --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php @@ -0,0 +1,36 @@ + + * + * @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 + * + */ +namespace OCA\DAV\CalDAV\Reminder\NotificationProvider; + +/** + * Class AudioProvider + * + * This class only extends PushProvider at the moment. It does not provide true + * audio-alarms yet, but it's better than no alarm at all right now. + * + * @package OCA\DAV\CalDAV\Reminder\NotificationProvider + */ +class AudioProvider extends PushProvider { + + /** @var string */ + public const NOTIFICATION_TYPE = 'AUDIO'; +} From bcce568c6d892ccacc598c5d5bfa6b24c3284741 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 9 Aug 2019 20:25:21 +0200 Subject: [PATCH 08/10] Support recurring events + repeating alarms Signed-off-by: Georg Ehrke --- apps/dav/appinfo/info.xml | 2 +- .../composer/composer/autoload_classmap.php | 5 +- .../dav/composer/composer/autoload_static.php | 5 +- apps/dav/lib/AppInfo/Application.php | 4 +- .../Reminder/AbstractNotificationProvider.php | 205 ----- apps/dav/lib/CalDAV/Reminder/Backend.php | 183 +++-- .../CalDAV/Reminder/INotificationProvider.php | 47 ++ .../NotificationProvider/AbstractProvider.php | 191 +++++ .../NotificationProvider/AudioProvider.php | 1 + .../NotificationProvider/EmailProvider.php | 503 +++++++++++-- .../ProviderNotAvailableException.php | 2 +- .../NotificationProvider/PushProvider.php | 108 ++- .../Reminder/NotificationProviderManager.php | 42 +- .../NotificationTypeDoesNotExistException.php | 3 +- apps/dav/lib/CalDAV/Reminder/Notifier.php | 294 ++++++-- .../lib/CalDAV/Reminder/ReminderService.php | 710 ++++++++++++++++-- apps/dav/lib/Command/SendEventReminders.php | 14 +- .../Version1004Date20170825134824.php | 3 +- .../Version1007Date20181005133326.php | 82 -- .../Version1012Date20190808122342.php | 116 +++ 20 files changed, 1926 insertions(+), 594 deletions(-) delete mode 100644 apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php create mode 100644 apps/dav/lib/CalDAV/Reminder/INotificationProvider.php create mode 100644 apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php delete mode 100644 apps/dav/lib/Migration/Version1007Date20181005133326.php create mode 100644 apps/dav/lib/Migration/Version1012Date20190808122342.php diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 6e0219e91d..81b93de055 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ WebDAV WebDAV endpoint WebDAV endpoint - 1.11.1 + 1.13.0 agpl owncloud.org DAV diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index bd26290cc9..a1c2d671b8 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -52,9 +52,10 @@ 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\\INotificationProvider' => $baseDir . '/../lib/CalDAV/Reminder/INotificationProvider.php', 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProviderManager' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProviderManager.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AbstractProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php', 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AudioProvider' => $baseDir . '/../lib/CalDAV/Reminder/NotificationProvider/AudioProvider.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', @@ -188,7 +189,6 @@ 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', @@ -197,6 +197,7 @@ return array( 'OCA\\DAV\\Migration\\Version1008Date20181114084440' => $baseDir . '/../lib/Migration/Version1008Date20181114084440.php', 'OCA\\DAV\\Migration\\Version1011Date20190725113607' => $baseDir . '/../lib/Migration/Version1011Date20190725113607.php', 'OCA\\DAV\\Migration\\Version1011Date20190806104428' => $baseDir . '/../lib/Migration/Version1011Date20190806104428.php', + 'OCA\\DAV\\Migration\\Version1012Date20190808122342' => $baseDir . '/../lib/Migration/Version1012Date20190808122342.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php', 'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 1a0ce9d02f..39488419f8 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -67,9 +67,10 @@ 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\\INotificationProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/INotificationProvider.php', 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProviderManager' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProviderManager.php', + 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AbstractProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php', 'OCA\\DAV\\CalDAV\\Reminder\\NotificationProvider\\AudioProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Reminder/NotificationProvider/AudioProvider.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', @@ -203,7 +204,6 @@ 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', @@ -212,6 +212,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1008Date20181114084440' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181114084440.php', 'OCA\\DAV\\Migration\\Version1011Date20190725113607' => __DIR__ . '/..' . '/../lib/Migration/Version1011Date20190725113607.php', 'OCA\\DAV\\Migration\\Version1011Date20190806104428' => __DIR__ . '/..' . '/../lib/Migration/Version1011Date20190806104428.php', + 'OCA\\DAV\\Migration\\Version1012Date20190808122342' => __DIR__ . '/..' . '/../lib/Migration/Version1012Date20190808122342.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php', 'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php', diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 5d17dc5a24..80e9dea882 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -231,12 +231,10 @@ class Application extends App { ); /** @var ReminderService $reminderBackend */ - $reminderService= $this->getContainer()->query(ReminderService::class); + $reminderService = $this->getContainer()->query(ReminderService::class); $reminderService->onTouchCalendarObject( $eventName, - $event->getArgument('calendarData'), - $event->getArgument('shares'), $event->getArgument('objectData') ); }; diff --git a/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php b/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php deleted file mode 100644 index 1d858627de..0000000000 --- a/apps/dav/lib/CalDAV/Reminder/AbstractNotificationProvider.php +++ /dev/null @@ -1,205 +0,0 @@ - - * - * @author Thomas Citharel - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OCA\DAV\CalDAV\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 { - - /** @var string */ - 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 - */ - abstract 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 index 087a5785f3..be65c35da0 100644 --- a/apps/dav/lib/CalDAV/Reminder/Backend.php +++ b/apps/dav/lib/CalDAV/Reminder/Backend.php @@ -1,8 +1,11 @@ + * @copyright Copyright (c) 2019, Thomas Citharel + * @copyright Copyright (c) 2019, Georg Ehrke * * @author Thomas Citharel + * @author Georg Ehrke * * @license GNU AGPL version 3 or any later version * @@ -20,7 +23,6 @@ * along with this program. If not, see . * */ - namespace OCA\DAV\CalDAV\Reminder; use OCP\IDBConnection; @@ -40,61 +42,119 @@ class Backend { private $timeFactory; /** + * Backend constructor. + * * @param IDBConnection $db * @param ITimeFactory $timeFactory */ - public function __construct(IDBConnection $db, 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 + * Get all reminders with a notification date before now + * + * @return array + * @throws \Exception */ - public function insertReminder(string $uid, string $calendarId, string $uri, string $type, int $notificationDate, int $eventStartDate):void { + public function getRemindersToProcess():array { + $query = $this->db->getQueryBuilder(); + $query->select(['cr.*', 'co.calendardata', 'c.displayname', 'c.principaluri']) + ->from('calendar_reminders', 'cr') +// ->where($query->expr()->lte('cr.notification_date', $query->createNamedParameter($this->timeFactory->getTime()))) + ->leftJoin('cr', 'calendarobjects', 'co', $query->expr()->eq('cr.object_id', 'co.id')) + ->leftJoin('cr', 'calendars', 'c', $query->expr()->eq('cr.calendar_id', 'c.id')); + $stmt = $query->execute(); + + return array_map( + [$this, 'fixRowTyping'], + $stmt->fetchAll() + ); + } + + /** + * Get all scheduled reminders for an event + * + * @param int $objectId + * @return array + */ + public function getAllScheduledRemindersForEvent(int $objectId):array { + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from('calendar_reminders') + ->where($query->expr()->eq('object_id', $query->createNamedParameter($objectId))); + $stmt = $query->execute(); + + return array_map( + [$this, 'fixRowTyping'], + $stmt->fetchAll() + ); + } + + /** + * Insert a new reminder into the database + * + * @param int $calendarId + * @param int $objectId + * @param string $uid + * @param bool $isRecurring + * @param int $recurrenceId + * @param bool $isRecurrenceException + * @param string $eventHash + * @param string $alarmHash + * @param string $type + * @param bool $isRelative + * @param int $notificationDate + * @param bool $isRepeatBased + * @return int The insert id + */ + public function insertReminder(int $calendarId, + int $objectId, + string $uid, + bool $isRecurring, + int $recurrenceId, + bool $isRecurrenceException, + string $eventHash, + string $alarmHash, + string $type, + bool $isRelative, + int $notificationDate, + bool $isRepeatBased):int { $query = $this->db->getQueryBuilder(); $query->insert('calendar_reminders') ->values([ + 'calendar_id' => $query->createNamedParameter($calendarId), + 'object_id' => $query->createNamedParameter($objectId), 'uid' => $query->createNamedParameter($uid), - 'calendarid' => $query->createNamedParameter($calendarId), - 'objecturi' => $query->createNamedParameter($uri), + 'is_recurring' => $query->createNamedParameter($isRecurring ? 1 : 0), + 'recurrence_id' => $query->createNamedParameter($recurrenceId), + 'is_recurrence_exception' => $query->createNamedParameter($isRecurrenceException ? 1 : 0), + 'event_hash' => $query->createNamedParameter($eventHash), + 'alarm_hash' => $query->createNamedParameter($alarmHash), '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))) + 'is_relative' => $query->createNamedParameter($isRelative ? 1 : 0), + 'notification_date' => $query->createNamedParameter($notificationDate), + 'is_repeat_based' => $query->createNamedParameter($isRepeatBased ? 1 : 0), + ]) ->execute(); + + return $query->getLastInsertId(); } /** - * Remove all reminders for a calendar + * Sets a new notificationDate on an existing reminder * - * @param integer $calendarId - * @return void + * @param int $reminderId + * @param int $newNotificationDate */ - public function cleanRemindersForCalendar(int $calendarId):void { + public function updateReminder(int $reminderId, + int $newNotificationDate):void { $query = $this->db->getQueryBuilder(); - - $query->delete('calendar_reminders') - ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) + $query->update('calendar_reminders') + ->set('notification_date', $query->createNamedParameter($newNotificationDate)) + ->where($query->expr()->eq('id', $query->createNamedParameter($reminderId))) ->execute(); } @@ -113,22 +173,47 @@ class Backend { } /** - * Get all reminders with a notification date before now + * Cleans reminders in database * - * @return array - * @throws \Exception + * @param int $objectId */ - public function getRemindersToProcess():array { + public function cleanRemindersForEvent(int $objectId):void { $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(); + $query->delete('calendar_reminders') + ->where($query->expr()->eq('object_id', $query->createNamedParameter($objectId))) + ->execute(); + } + + /** + * Remove all reminders for a calendar + * + * @param int $calendarId + * @return void + */ + public function cleanRemindersForCalendar(int $calendarId):void { + $query = $this->db->getQueryBuilder(); + + $query->delete('calendar_reminders') + ->where($query->expr()->eq('calendar_id', $query->createNamedParameter($calendarId))) + ->execute(); + } + + /** + * @param array $row + * @return array + */ + private function fixRowTyping(array $row): array { + $row['id'] = (int) $row['id']; + $row['calendar_id'] = (int) $row['calendar_id']; + $row['object_id'] = (int) $row['object_id']; + $row['is_recurring'] = (bool) $row['is_recurring']; + $row['recurrence_id'] = (int) $row['recurrence_id']; + $row['is_recurrence_exception'] = (bool) $row['is_recurrence_exception']; + $row['is_relative'] = (bool) $row['is_relative']; + $row['notification_date'] = (int) $row['notification_date']; + $row['is_repeat_based'] = (bool) $row['is_repeat_based']; + + return $row; } } diff --git a/apps/dav/lib/CalDAV/Reminder/INotificationProvider.php b/apps/dav/lib/CalDAV/Reminder/INotificationProvider.php new file mode 100644 index 0000000000..d0e526eb0e --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/INotificationProvider.php @@ -0,0 +1,47 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\DAV\CalDAV\Reminder; + +use OCP\IUser; +use Sabre\VObject\Component\VEvent; + +/** + * Interface INotificationProvider + * + * @package OCA\DAV\CalDAV\Reminder + */ +interface INotificationProvider { + + /** + * Send notification + * + * @param VEvent $vevent + * @param string $calendarDisplayName + * @param IUser[] $users + * @return void + */ + public function send(VEvent $vevent, + string $calendarDisplayName, + array $users=[]): void; +} \ No newline at end of file diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php new file mode 100644 index 0000000000..6b2364c802 --- /dev/null +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php @@ -0,0 +1,191 @@ + + * @author Georg Ehrke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\DAV\CalDAV\Reminder\NotificationProvider; + +use \DateTime; +use \DateTimeImmutable; +use OCA\DAV\CalDAV\Reminder\INotificationProvider; +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\VEvent; +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\Parameter; +use Sabre\VObject\Property; + +/** + * Class AbstractProvider + * + * @package OCA\DAV\CalDAV\Reminder\NotificationProvider + */ +abstract class AbstractProvider implements INotificationProvider { + + /** @var string */ + public const NOTIFICATION_TYPE = ''; + + /** @var ILogger */ + protected $logger; + + /** @var L10NFactory */ + private $l10nFactory; + + /** @var IL10N[] */ + private $l10ns; + + /** @var string */ + private $fallbackLanguage; + + /** @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 VEvent $vevent + * @param string $calendarDisplayName + * @param IUser[] $users + * @return void + */ + abstract public function send(VEvent $vevent, + string $calendarDisplayName, + array $users=[]): void; + + /** + * @return string + */ + protected function getFallbackLanguage():string { + if ($this->fallbackLanguage) { + return $this->fallbackLanguage; + } + + $fallbackLanguage = $this->l10nFactory->findLanguage(); + $this->fallbackLanguage = $fallbackLanguage; + + return $fallbackLanguage; + } + + /** + * @param string $lang + * @return bool + */ + protected function hasL10NForLang(string $lang):bool { + return $this->l10nFactory->languageExists('dav', $lang); + } + + /** + * @param string $lang + * @return IL10N + */ + protected function getL10NForLang(string $lang):IL10N { + if (isset($this->l10ns[$lang])) { + return $this->l10ns[$lang]; + } + + $l10n = $this->l10nFactory->get('dav', $lang); + $this->l10ns[$lang] = $l10n; + + return $l10n; + } + + /** + * @param VEvent $vevent + * @return string + */ + private function getStatusOfEvent(VEvent $vevent):string { + if ($vevent->STATUS) { + return (string) $vevent->STATUS; + } + + // Doesn't say so in the standard, + // but we consider events without a status + // to be confirmed + return 'CONFIRMED'; + } + + /** + * @param VEvent $vevent + * @return bool + */ + protected function isEventTentative(VEvent $vevent):bool { + return $this->getStatusOfEvent($vevent) === 'TENTATIVE'; + } + + /** + * @param VEvent $vevent + * @return Property\ICalendar\DateTime + */ + protected function getDTEndFromEvent(VEvent $vevent):Property\ICalendar\DateTime { + if (isset($vevent->DTEND)) { + return $vevent->DTEND; + } + + if (isset($vevent->DURATION)) { + $isFloating = $vevent->DTSTART->isFloating(); + /** @var Property\ICalendar\DateTime $end */ + $end = clone $vevent->DTSTART; + $endDateTime = $end->getDateTime(); + $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue())); + $end->setDateTime($endDateTime, $isFloating); + + return $end; + } + + if (!$vevent->DTSTART->hasTime()) { + $isFloating = $vevent->DTSTART->isFloating(); + /** @var Property\ICalendar\DateTime $end */ + $end = clone $vevent->DTSTART; + $endDateTime = $end->getDateTime(); + $endDateTime = $endDateTime->modify('+1 day'); + $end->setDateTime($endDateTime, $isFloating); + + return $end; + } + + return clone $vevent->DTSTART; + } +} diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php index 6e702bcaca..ad4ac342f6 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AudioProvider.php @@ -1,4 +1,5 @@ + * @copyright Copyright (c) 2019, Thomas Citharel + * @copyright Copyright (c) 2019, Georg Ehrke * * @author Thomas Citharel + * @author Georg Ehrke * * @license GNU AGPL version 3 or any later version * @@ -20,27 +23,36 @@ * along with this program. If not, see . * */ - namespace OCA\DAV\CalDAV\Reminder\NotificationProvider; -use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use DateTime; +use DateTimeImmutable; use OCP\IConfig; +use OCP\IL10N; 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; +use Sabre\VObject\Component\VEvent; +use Sabre\VObject; +use Sabre\VObject\Parameter; +use Sabre\VObject\Property; -class EmailProvider extends AbstractNotificationProvider { - - /** @var IMailer */ - private $mailer; +/** + * Class EmailProvider + * + * @package OCA\DAV\CalDAV\Reminder\NotificationProvider + */ +class EmailProvider extends AbstractProvider { /** @var string */ public const NOTIFICATION_TYPE = 'EMAIL'; + /** @var IMailer */ + private $mailer; + /** * @param IConfig $config * @param IMailer $mailer @@ -48,7 +60,9 @@ class EmailProvider extends AbstractNotificationProvider { * @param L10NFactory $l10nFactory * @param IUrlGenerator $urlGenerator */ - public function __construct(IConfig $config, IMailer $mailer, ILogger $logger, + public function __construct(IConfig $config, + IMailer $mailer, + ILogger $logger, L10NFactory $l10nFactory, IURLGenerator $urlGenerator) { parent::__construct($logger, $l10nFactory, $urlGenerator, $config); @@ -56,90 +70,100 @@ class EmailProvider extends AbstractNotificationProvider { } /** - * Send notification + * Send out notification via email * - * @param VCalendar $vcalendar + * @param VEvent $vevent * @param string $calendarDisplayName - * @param IUser $user - * @return void + * @param array $users * @throws \Exception */ - public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user):void { - if ($user->getEMailAddress() === null) { - return; - } + public function send(VEvent $vevent, + string $calendarDisplayName, + array $users=[]):void { + $fallbackLanguage = $this->getFallbackLanguage(); - $lang = $this->config->getUserValue($user->getUID(), 'core', 'lang', $this->l10nFactory->findLanguage()); - $this->l10n = $this->l10nFactory->get('dav', $lang); + $emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users); + $emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent); - $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' + // Quote from php.net: + // If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. + // => if there are duplicate email addresses, it will always take the system value + $emailAddresses = array_merge( + $emailAddressesOfAttendees, + $emailAddressesOfSharees ); - $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)]); + $sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage); + $organizer = $this->getOrganizerEMailAndNameFromEvent($vevent); + + foreach($sortedByLanguage as $lang => $emailAddresses) { + if ($this->hasL10NForLang($lang)) { + $lang = $fallbackLanguage; + } + $l10n = $this->getL10NForLang($lang); + $fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply'); + + $message = $this->mailer->createMessage(); + $message->setFrom([$fromEMail]); + if ($organizer) { + $message->setReplyTo($organizer); + } + $message->setBcc($emailAddresses); + + $template = $this->mailer->createEMailTemplate('dav.calendarReminder'); + $template->addHeader(); + + $this->addSubjectAndHeading($template, $l10n, $vevent); + $this->addBulletList($template, $l10n, $calendarDisplayName, $vevent); + + $template->addFooter(); + $message->useTemplate($template); + + 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']); } - } catch(\Exception $ex) { - $this->logger->logException($ex, ['app' => 'dav']); } } /** * @param IEMailTemplate $template - * @param string $summary + * @param IL10N $l10n + * @param VEvent $vevent */ - private function addSubjectAndHeading(IEMailTemplate $template, string $summary):void { - $template->setSubject('Notification: ' . $summary); - $template->addHeading($summary); + private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void { + $template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n)); + $template->addHeading($this->getTitleFromVEvent($vevent, $l10n)); } /** * @param IEMailTemplate $template - * @param array $eventData + * @param IL10N $l10n * @param string $calendarDisplayName + * @param array $eventData */ - private function addBulletList(IEMailTemplate $template, array $eventData, string $calendarDisplayName):void { - $template->addBodyListItem($calendarDisplayName, $this->l10n->t('Calendar:'), + private function addBulletList(IEMailTemplate $template, + IL10N $l10n, + string $calendarDisplayName, + VEvent $vevent):void { + $template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'), $this->getAbsoluteImagePath('actions/info.svg')); - $template->addBodyListItem($eventData['when'], $this->l10n->t('Date:'), + $template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'), $this->getAbsoluteImagePath('places/calendar.svg')); - if ($eventData['location']) { - $template->addBodyListItem((string) $eventData['location'], $this->l10n->t('Where:'), + if (isset($vevent->LOCATION)) { + $template->addBodyListItem((string) $vevent->LOCATION, $l10n->t('Where:'), $this->getAbsoluteImagePath('actions/address.svg')); } - if ($eventData['description']) { - $template->addBodyListItem((string) $eventData['description'], $this->l10n->t('Description:'), + if (isset($vevent->DESCRIPTION)) { + $template->addBodyListItem((string) $vevent->DESCRIPTION, $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')); - } } /** @@ -151,4 +175,355 @@ class EmailProvider extends AbstractNotificationProvider { $this->urlGenerator->imagePath('core', $path) ); } + + /** + * @param VEvent $vevent + * @return array|null + */ + private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array { + if (!$vevent->ORGANIZER) { + return null; + } + + $organizer = $vevent->ORGANZIER; + if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) { + return null; + } + + $organizerEMail = substr($organizer->getValue(), 7); + + $name = $organizer->offsetGet('CN'); + if ($name instanceof Parameter) { + return [$organizerEMail => $name]; + } + + return [$organizerEMail]; + } + + /** + * @param array $sortedByLanguage + * @param IUser[] $users + * @param string $defaultLanguage + */ + private function sortUsersByLanguage(array &$sortedByLanguage, + array $users, + string $defaultLanguage):void { + /** + * @var array $sortedByLanguage + * [ + * 'de' => ['a@b.com', 'c@d.com'], + * ... + * ] + */ + foreach($users as $user) { + /** @var IUser $user */ + $emailAddress = $user->getEMailAddress(); + $lang = $this->config->getUserValue($user->getUID(), + 'core', 'lang', $defaultLanguage); + + if (!isset($sortedByLanguage[$lang])) { + $sortedByLanguage[$lang] = []; + } + + $sortedByLanguage[$lang][] = $emailAddress; + } + } + + /** + * @param array $emails + * @param string $defaultLanguage + * @return array + */ + private function sortEMailAddressesByLanguage(array $emails, + string $defaultLanguage):array { + $sortedByLanguage = []; + + foreach($emails as $emailAddress => $parameters) { + if (isset($parameters['LANG'])) { + $lang = $parameters['LANG']; + } else { + $lang = $defaultLanguage; + } + + if (!isset($sortedByLanguage[$lang])) { + $sortedByLanguage[$lang] = []; + } + + $sortedByLanguage[$lang][] = $emailAddress; + } + + return $sortedByLanguage; + } + + /** + * @param VEvent $vevent + * @return array + */ + private function getAllEMailAddressesFromEvent(VEvent $vevent):array { + $emailAddresses = []; + + if (isset($vevent->ATTENDEE)) { + foreach ($vevent->ATTENDEE as $attendee) { + if (!($attendee instanceof VObject\Property)) { + continue; + } + + $cuType = $this->getCUTypeOfAttendee($attendee); + if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) { + // Don't send emails to things + continue; + } + + $partstat = $this->getPartstatOfAttendee($attendee); + if ($partstat === 'DECLINED') { + // Don't send out emails to people who declined + continue; + } + if ($partstat === 'DELEGATED') { + $delegates = $attendee->offsetGet('DELEGATED-TO'); + if (!($delegates instanceof VObject\Parameter)) { + continue; + } + + $emailAddressesOfDelegates = $delegates->getParts(); + foreach($emailAddressesOfDelegates as $addressesOfDelegate) { + if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) { + $emailAddresses[substr($addressesOfDelegate, 7)] = []; + } + } + + continue; + } + + $emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee); + if ($emailAddressOfAttendee !== null) { + $properties = []; + + $langProp = $attendee->offsetGet('LANG'); + if ($langProp instanceof VObject\Parameter) { + $properties['LANG'] = $langProp->getValue(); + } + + $emailAddresses[$emailAddressOfAttendee] = $properties; + } + } + } + + if (isset($vevent->ORGANIZER)) { + $emailAddresses[$this->getEMailAddressOfAttendee($vevent->ORGANIZER)] = []; + } + + return $emailAddresses; + } + + + + /** + * @param VObject\Property $attendee + * @return string + */ + private function getCUTypeOfAttendee(VObject\Property $attendee):string { + $cuType = $attendee->offsetGet('CUTYPE'); + if ($cuType instanceof VObject\Parameter) { + return strtoupper($cuType->getValue()); + } + + return 'INDIVIDUAL'; + } + + /** + * @param VObject\Property $attendee + * @return string + */ + private function getPartstatOfAttendee(VObject\Property $attendee):string { + $partstat = $attendee->offsetGet('PARTSTAT'); + if ($partstat instanceof VObject\Parameter) { + return strtoupper($partstat->getValue()); + } + + return 'NEEDS-ACTION'; + } + + /** + * @param VObject\Property $attendee + * @return bool + */ + private function hasAttendeeMailURI(VObject\Property $attendee):bool { + return strcasecmp($attendee->getValue(), 'mailto:') === 0; + } + + /** + * @param VObject\Property $attendee + * @return string|null + */ + private function getEMailAddressOfAttendee(VObject\Property $attendee):?string { + if (!$this->hasAttendeeMailURI($attendee)) { + return null; + } + + return substr($attendee->getValue(), 7); + } + + /** + * @param array $users + * @return array + */ + private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array { + $emailAddresses = []; + + foreach($users as $user) { + $emailAddress = $user->getEMailAddress(); + if ($emailAddress) { + $lang = $this->getLangForUser($user); + if ($lang) { + $emailAddresses[$emailAddress] = [ + 'LANG' => $lang, + ]; + } else { + $emailAddresses[$emailAddress] = []; + } + } + } + + return array_unique($emailAddresses); + } + + /** + * @param IUser $user + * @return string + */ + private function getLangForUser(IUser $user): ?string { + return $this->config->getUserValue($user->getUID(), 'core', 'lang', null); + } + + /** + * @param IL10N $l10n + * @param VEvent $vevent + * @return string + * @throws \Exception + */ + private function generateDateString(IL10N $l10n, VEvent $vevent):string { + $isAllDay = $vevent->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 = $vevent->DTSTART->getDateTime(); + /** @var \DateTimeImmutable $dtendDt */ + $dtendDt = $this->getDTEndFromEvent($vevent)->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->getDateString($l10n, $dtstartDt); + } + + return implode(' - ', [ + $this->getDateString($l10n, $dtstartDt), + $this->getDateString($l10n, $dtendDt), + ]); + } + + $startTimezone = $endTimezone = null; + if (!$vevent->DTSTART->isFloating()) { + $startTimezone = $vevent->DTSTART->getDateTime()->getTimezone()->getName(); + $endTimezone = $this->getDTEndFromEvent($vevent)->getDateTime()->getTimezone()->getName(); + } + + $localeStart = implode(', ', [ + $this->getWeekDayName($l10n, $dtstartDt), + $this->getDateTimeString($l10n, $dtstartDt) + ]); + + // always show full date with timezone if timezones are different + if ($startTimezone !== $endTimezone) { + $localeEnd = implode(', ', [ + $this->getWeekDayName($l10n, $dtendDt), + $this->getDateTimeString($l10n, $dtendDt) + ]); + + return $localeStart + . ' (' . $startTimezone . ') ' + . ' - ' + . $localeEnd + . ' (' . $endTimezone . ')'; + } + + // Show only the time if the day is the same + $localeEnd = $this->isDayEqual($dtstartDt, $dtendDt) + ? $this->getTimeString($l10n, $dtendDt) + : implode(', ', [ + $this->getWeekDayName($l10n, $dtendDt), + $this->getDateTimeString($l10n, $dtendDt) + ]); + + 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'); + } + + /** + * @param IL10N $l10n + * @param DateTime $dt + * @return string + */ + private function getWeekDayName(IL10N $l10n, DateTime $dt):string { + return $l10n->l('weekdayName', $dt, ['width' => 'abbreviated']); + } + + /** + * @param IL10N $l10n + * @param DateTime $dt + * @return string + */ + private function getDateString(IL10N $l10n, DateTime $dt):string { + return $l10n->l('date', $dt, ['width' => 'medium']); + } + + /** + * @param IL10N $l10n + * @param DateTime $dt + * @return string + */ + private function getDateTimeString(IL10N $l10n, DateTime $dt):string { + return $l10n->l('datetime', $dt, ['width' => 'medium|short']); + } + + /** + * @param IL10N $l10n + * @param DateTime $dt + * @return string + */ + private function getTimeString(IL10N $l10n, DateTime $dt):string { + return $l10n->l('time', $dt, ['width' => 'short']); + } + + /** + * @param VEvent $vevent + * @param IL10N $l10n + * @return string + */ + private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string { + if (isset($vevent->SUMMARY)) { + return (string)$vevent->SUMMARY; + } + + return $l10n->t('Untitled event'); + } } diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php index bf736db8a3..bfa6db9585 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/ProviderNotAvailableException.php @@ -1,4 +1,5 @@ * @@ -20,7 +21,6 @@ * along with this program. If not, see . * */ - namespace OCA\DAV\CalDAV\Reminder\NotificationProvider; class ProviderNotAvailableException extends \Exception { diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php index f04b8e4c45..2e580fd78a 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php @@ -1,8 +1,11 @@ + * @copyright Copyright (c) 2019, Thomas Citharel + * @copyright Copyright (c) 2019, Georg Ehrke * * @author Thomas Citharel + * @author Georg Ehrke * * @license GNU AGPL version 3 or any later version * @@ -20,11 +23,9 @@ * along with this program. If not, see . * */ - 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; @@ -32,22 +33,24 @@ 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; +use Sabre\VObject\Component\VEvent; +use Sabre\VObject\Property; -class PushProvider extends AbstractNotificationProvider { +/** + * Class PushProvider + * + * @package OCA\DAV\CalDAV\Reminder\NotificationProvider + */ +class PushProvider extends AbstractProvider { /** @var string */ public const NOTIFICATION_TYPE = 'DISPLAY'; - /** - * @var IManager - */ + /** @var IManager */ private $manager; - /** - * @var ITimeFactory - */ + /** @var ITimeFactory */ private $timeFactory; /** @@ -58,42 +61,75 @@ class PushProvider extends AbstractNotificationProvider { * @param IUrlGenerator $urlGenerator * @param ITimeFactory $timeFactory */ - public function __construct(IConfig $config, IManager $manager, ILogger $logger, + public function __construct(IConfig $config, + IManager $manager, + ILogger $logger, L10NFactory $l10nFactory, - IURLGenerator $urlGenerator, ITimeFactory $timeFactory) { + IURLGenerator $urlGenerator, + ITimeFactory $timeFactory) { parent::__construct($logger, $l10nFactory, $urlGenerator, $config); $this->manager = $manager; $this->timeFactory = $timeFactory; } /** - * Send notification + * Send push notification to all users. * - * @param VCalendar $vcalendar + * @param VEvent $vevent * @param string $calendarDisplayName - * @param IUser $user - * @return void + * @param IUser[] $users * @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); + public function send(VEvent $vevent, + string $calendarDisplayName=null, + array $users=[]):void { + $eventDetails = $this->extractEventDetails($vevent); + $eventDetails['calendar_displayname'] = $calendarDisplayName; - $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); + foreach($users as $user) { + /** @var INotification $notification */ + $notification = $this->manager->createNotification(); + $notification->setApp(Application::APP_ID) + ->setUser($user->getUID()) + ->setDateTime($this->timeFactory->getDateTime()) + ->setObject(Application::APP_ID, (string) $vevent->UID) + ->setSubject('calendar_reminder', [ + 'title' => $eventDetails['title'], + 'start_atom' => $eventDetails['start_atom'] + ]) + ->setMessage('calendar_reminder', $eventDetails); + + $this->manager->notify($notification); + } } + + /** + * @var VEvent $vevent + * @return array + * @throws \Exception + */ + protected function extractEventDetails(VEvent $vevent):array { + /** @var Property\ICalendar\DateTime $start */ + $start = $vevent->DTSTART; + $end = $this->getDTEndFromEvent($vevent); + + return [ + 'title' => isset($vevent->SUMMARY) + ? ((string) $vevent->SUMMARY) + : null, + 'description' => isset($vevent->DESCRIPTION) + ? ((string) $vevent->DESCRIPTION) + : null, + 'location' => isset($vevent->LOCATION) + ? ((string) $vevent->LOCATION) + : null, + 'all_day' => $start instanceof Property\ICalendar\Date, + 'start_atom' => $start->getDateTime()->format(\DateTime::ATOM), + 'start_is_floating' => $start->isFloating(), + 'start_timezone' => $start->getDateTime()->getTimezone()->getName(), + 'end_atom' => $end->getDateTime()->format(\DateTime::ATOM), + 'end_is_floating' => $end->isFloating(), + 'end_timezone' => $end->getDateTime()->getTimezone()->getName(), + ]; + } } diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php index 0a2579aedf..3d54970562 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php @@ -1,6 +1,11 @@ + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -19,36 +24,55 @@ */ namespace OCA\DAV\CalDAV\Reminder; -use OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException; - +/** + * Class NotificationProviderManager + * + * @package OCA\DAV\CalDAV\Reminder + */ class NotificationProviderManager { - /** @var array */ + /** @var INotificationProvider[] */ private $providers = []; + + /** + * Checks whether a provider for a given ACTION exists + * + * @param string $type + * @return bool + */ + public function hasProvider(string $type):bool { + return (\in_array($type, ReminderService::REMINDER_TYPES, true) + && isset($this->providers[$type])); + } + /** - * @var string $type - * @return AbstractNotificationProvider - * @throws ProviderNotAvailableException + * Get provider for a given ACTION + * + * @param string $type + * @return INotificationProvider + * @throws NotificationProvider\ProviderNotAvailableException * @throws NotificationTypeDoesNotExistException */ - public function getProvider(string $type):AbstractNotificationProvider { + public function getProvider(string $type):INotificationProvider { if (in_array($type, ReminderService::REMINDER_TYPES, true)) { if (isset($this->providers[$type])) { return $this->providers[$type]; } - throw new ProviderNotAvailableException($type); + throw new NotificationProvider\ProviderNotAvailableException($type); } throw new NotificationTypeDoesNotExistException($type); } /** + * Registers a new provider + * * @param string $providerClassName * @throws \OCP\AppFramework\QueryException */ public function registerProvider(string $providerClassName):void { $provider = \OC::$server->query($providerClassName); - if (!$provider instanceof AbstractNotificationProvider) { + if (!$provider instanceof INotificationProvider) { throw new \InvalidArgumentException('Invalid notification provider registered'); } diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php b/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php index ae4ec3bd3b..c060089785 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationTypeDoesNotExistException.php @@ -1,6 +1,7 @@ + * @copyright Copyright (c) 2019, Thomas Citharel * * @author Thomas Citharel * diff --git a/apps/dav/lib/CalDAV/Reminder/Notifier.php b/apps/dav/lib/CalDAV/Reminder/Notifier.php index 3718d5b29a..4bad984178 100644 --- a/apps/dav/lib/CalDAV/Reminder/Notifier.php +++ b/apps/dav/lib/CalDAV/Reminder/Notifier.php @@ -1,8 +1,11 @@ + * @copyright Copyright (c) 2019, Thomas Citharel + * @copyright Copyright (c) 2019, Georg Ehrke * * @author Thomas Citharel + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -22,37 +25,47 @@ namespace OCA\DAV\CalDAV\Reminder; +use DateTime; use OCA\DAV\AppInfo\Application; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\IL10N; use OCP\L10N\IFactory; use OCP\Notification\INotification; use OCP\Notification\INotifier; use OCP\IURLGenerator; +/** + * Class Notifier + * + * @package OCA\DAV\CalDAV\Reminder + */ class Notifier implements INotifier { - /** @var array */ - public static $units = [ - 'y' => 'year', - 'm' => 'month', - 'd' => 'day', - 'h' => 'hour', - 'i' => 'minute', - 's' => 'second', - ]; - /** @var IFactory */ - protected $factory; + private $l10nFactory; /** @var IURLGenerator */ - protected $urlGenerator; + private $urlGenerator; /** @var IL10N */ - protected $l; + private $l10n; - public function __construct(IFactory $factory, IURLGenerator $urlGenerator) { - $this->factory = $factory; + /** @var ITimeFactory */ + private $timeFactory; + + /** + * Notifier constructor. + * + * @param IFactory $factory + * @param IURLGenerator $urlGenerator + * @param ITimeFactory $timeFactory + */ + public function __construct(IFactory $factory, + IURLGenerator $urlGenerator, + ITimeFactory $timeFactory) { + $this->l10nFactory = $factory; $this->urlGenerator = $urlGenerator; + $this->timeFactory = $timeFactory; } /** @@ -62,7 +75,7 @@ class Notifier implements INotifier { * @since 17.0.0 */ public function getID():string { - return 'dav'; + return Application::APP_ID; } /** @@ -72,89 +85,236 @@ class Notifier implements INotifier { * @since 17.0.0 */ public function getName():string { - return $this->factory->get('dav')->t('Calendar'); + if ($this->l10n) { + return $this->l10n->t('Calendar'); + } + + return $this->l10nFactory->get('dav')->t('Calendar'); } /** + * Prepare sending the notification + * * @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, string $languageCode):INotification { + public function prepare(INotification $notification, + string $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); + $this->l10n = $this->l10nFactory->get('dav', $languageCode); - if ($notification->getSubject() === 'calendar_reminder') { - $subjectParameters = $notification->getSubjectParameters(); - $notification->setParsedSubject($this->processEventTitle($subjectParameters)); + // Handle notifier subjects + switch($notification->getSubject()) { + case 'calendar_reminder': + return $this->prepareReminderNotification($notification); + + default: + throw new \InvalidArgumentException('Unknown subject'); - $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 + * @param INotification $notification + * @return INotification + */ + private function prepareReminderNotification(INotification $notification):INotification { + $imagePath = $this->urlGenerator->imagePath('core', 'places/calendar.svg'); + $iconUrl = $this->urlGenerator->getAbsoluteURL($imagePath); + $notification->setIcon($iconUrl); + + $this->prepareNotificationSubject($notification); + $this->prepareNotificationMessage($notification); + + return $notification; + } + + /** + * Sets the notification subject based on the parameters set in PushProvider + * + * @param INotification $notification + */ + private function prepareNotificationSubject(INotification $notification): void { + $parameters = $notification->getSubjectParameters(); + + $startTime = \DateTime::createFromFormat(\DateTimeInterface::ATOM, $parameters['start_atom']); + $now = $this->timeFactory->getDateTime(); + $title = $this->getTitleFromParameters($parameters); + + $diff = $startTime->diff($now); + if ($diff === false) { + return; + } + + $components = []; + if ($diff->y) { + $components[] = $this->l10n->n('%n year', '%n years', $diff->y); + } + if ($diff->m) { + $components[] = $this->l10n->n('%n month', '%n months', $diff->m); + } + if ($diff->d) { + $components[] = $this->l10n->n('%n day', '%n days', $diff->d); + } + if ($diff->h) { + $components[] = $this->l10n->n('%n hour', '%n hours', $diff->h); + } + if ($diff->i) { + $components[] = $this->l10n->n('%n minute', '%n minutes', $diff->i); + } + + // Limiting to the first three components to prevent + // the string from getting too long + $firstThreeComponents = array_slice($components, 0, 2); + $diffLabel = implode(', ', $firstThreeComponents); + + if ($diff->invert) { + $title = $this->l10n->t('%s (in %s)', [$title, $diffLabel]); + } else { + $title = $this->l10n->t('%s (%s ago)', [$title, $diffLabel]); + } + + $notification->setParsedSubject($title); + } + + /** + * Sets the notification message based on the parameters set in PushProvider + * + * @param INotification $notification + */ + private function prepareNotificationMessage(INotification $notification): void { + $parameters = $notification->getMessageParameters(); + + $description = [ + $this->l10n->t('Calendar: %s', $parameters['calendar_displayname']), + $this->l10n->t('Date: %s', $this->generateDateString($parameters)), + ]; + if ($parameters['description']) { + $description[] = $this->l10n->t('Description: %s', $parameters['description']); + } + if ($parameters['location']) { + $description[] = $this->l10n->t('Where: %s', $parameters['location']); + } + + $message = implode("\r\n", $description); + $notification->setParsedMessage($message); + } + + /** + * @param array $parameters + * @return string + */ + private function getTitleFromParameters(array $parameters):string { + return $parameters['title'] ?? $this->l10n->t('Untitled event'); + } + + /** + * @param array $parameters * @return string * @throws \Exception */ - private function processEventTitle(array $event):string { - $event_datetime = new \DateTime(); - $event_datetime->setTimestamp($event['start']); - $now = new \DateTime(); + private function generateDateString(array $parameters):string { + $startDateTime = DateTime::createFromFormat(DATE_ATOM, $parameters['start_atom']); + $endDateTime = DateTime::createFromFormat(DATE_ATOM, $parameters['end_atom']); + $isAllDay = $parameters['all_day']; + $diff = $startDateTime->diff($endDateTime); - $diff = $event_datetime->diff($now); + if ($isAllDay) { + // One day event + if ($diff->days === 1) { + return $this->getDateString($startDateTime); + } - foreach (self::$units as $attribute => $unit) { - $count = $diff->$attribute; - if (0 !== $count) { - return $this->getPluralizedTitle($count, $diff->invert, $unit, $event['title']); - } - } - return ''; + return implode(' - ', [ + $this->getDateString($startDateTime), + $this->getDateString($endDateTime), + ]); + } + + $startTimezone = $endTimezone = null; + if (!$parameters['start_is_floating']) { + $startTimezone = $parameters['start_timezone']; + $endTimezone = $parameters['end_timezone']; + } + + $localeStart = implode(', ', [ + $this->getWeekDayName($startDateTime), + $this->getDateTimeString($startDateTime) + ]); + + // always show full date with timezone if timezones are different + if ($startTimezone !== $endTimezone) { + $localeEnd = implode(', ', [ + $this->getWeekDayName($endDateTime), + $this->getDateTimeString($endDateTime) + ]); + + return $localeStart + . ' (' . $startTimezone . ') ' + . ' - ' + . $localeEnd + . ' (' . $endTimezone . ')'; + } + + // Show only the time if the day is the same + $localeEnd = $this->isDayEqual($startDateTime, $endDateTime) + ? $this->getTimeString($endDateTime) + : implode(', ', [ + $this->getWeekDayName($endDateTime), + $this->getDateTimeString($endDateTime) + ]); + + return $localeStart + . ' - ' + . $localeEnd + . ' (' . $startTimezone . ')'; } /** - * - * @param int $count - * @param int $invert - * @param string $unit - * @param string $title - * @return string + * @param DateTime $dtStart + * @param DateTime $dtEnd + * @return bool */ - 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]); + private function isDayEqual(DateTime $dtStart, + DateTime $dtEnd):bool { + return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d'); } /** - * @param array $event + * @param DateTime $dt * @return string */ - private function processEventDescription(array $event):string { - $description = [ - $this->l->t('Calendar: %s', $event['calendar']), - $this->l->t('Date: %s', $event['when']), - ]; + private function getWeekDayName(DateTime $dt):string { + return $this->l10n->l('weekdayName', $dt, ['width' => 'abbreviated']); + } - if ($event['description']) { - $description[] = $this->l->t('Description: %s', $event['description']); - } - if ($event['location']) { - $description[] = $this->l->t('Where: %s', $event['location']); - } - return implode('
', $description); + /** + * @param DateTime $dt + * @return string + */ + private function getDateString(DateTime $dt):string { + return $this->l10n->l('date', $dt, ['width' => 'medium']); + } + + /** + * @param DateTime $dt + * @return string + */ + private function getDateTimeString(DateTime $dt):string { + return $this->l10n->l('datetime', $dt, ['width' => 'medium|short']); + } + + /** + * @param DateTime $dt + * @return string + */ + private function getTimeString(DateTime $dt):string { + return $this->l10n->l('time', $dt, ['width' => 'short']); } } diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php index ce6c846061..ad428eef74 100644 --- a/apps/dav/lib/CalDAV/Reminder/ReminderService.php +++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php @@ -1,6 +1,11 @@ + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -19,14 +24,19 @@ */ namespace OCA\DAV\CalDAV\Reminder; -use OC\User\NoUserException; +use DateTimeImmutable; +use OCA\DAV\CalDAV\CalDavBackend; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\IGroup; use OCP\IGroupManager; +use OCP\IUser; use OCP\IUserManager; -use OCP\IUserSession; use Sabre\VObject; use Sabre\VObject\Component\VAlarm; -use Sabre\VObject\Reader; +use Sabre\VObject\Component\VEvent; +use Sabre\VObject\ParseException; +use Sabre\VObject\Recur\EventIterator; +use Sabre\VObject\Recur\NoInstancesException; class ReminderService { @@ -42,144 +52,710 @@ class ReminderService { /** @var IGroupManager */ private $groupManager; - /** @var IUserSession */ - private $userSession; + /** @var CalDavBackend */ + private $caldavBackend; + + /** @var ITimeFactory */ + private $timeFactory; public const REMINDER_TYPE_EMAIL = 'EMAIL'; public const REMINDER_TYPE_DISPLAY = 'DISPLAY'; public const REMINDER_TYPE_AUDIO = 'AUDIO'; + /** + * @var String[] + * + * Official RFC5545 reminder types + */ public const REMINDER_TYPES = [ self::REMINDER_TYPE_EMAIL, self::REMINDER_TYPE_DISPLAY, self::REMINDER_TYPE_AUDIO ]; + /** + * ReminderService constructor. + * + * @param Backend $backend + * @param NotificationProviderManager $notificationProviderManager + * @param IUserManager $userManager + * @param IGroupManager $groupManager + * @param CalDavBackend $caldavBackend + * @param ITimeFactory $timeFactory + */ public function __construct(Backend $backend, NotificationProviderManager $notificationProviderManager, IUserManager $userManager, IGroupManager $groupManager, - IUserSession $userSession) { + CalDavBackend $caldavBackend, + ITimeFactory $timeFactory) { $this->backend = $backend; $this->notificationProviderManager = $notificationProviderManager; $this->userManager = $userManager; $this->groupManager = $groupManager; - $this->userSession = $userSession; + $this->caldavBackend = $caldavBackend; + $this->timeFactory = $timeFactory; } /** * 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']); + foreach($reminders as $reminder) { + $vcalendar = $this->parseCalendarData($reminder['calendardata']); + if (!$vcalendar) { + $this->backend->removeReminder($reminder['id']); + continue; + } - $user = $this->userManager->get($reminder['uid']); + $vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']); + if (!$vevent) { + $this->backend->removeReminder($reminder['id']); + continue; + } - if ($user === null) { - throw new NoUserException('User not found for calendar'); + if ($this->wasEventCancelled($vevent)) { + $this->deleteOrProcessNext($reminder, $vevent); + continue; + } + + if (!$this->notificationProviderManager->hasProvider($reminder['type'])) { + $this->deleteOrProcessNext($reminder, $vevent); + continue; + } + + $users = $this->getAllUsersWithWriteAccessToCalendar($reminder['calendar_id']); + $user = $this->getUserFromPrincipalURI($reminder['principaluri']); + if ($user) { + $users[] = $user; } $notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']); - $notificationProvider->send($calendarData, $reminder['displayname'], $user); - $this->backend->removeReminder($reminder['id']); + $notificationProvider->send($vevent, $reminder['displayname'], $users); + + $this->deleteOrProcessNext($reminder, $vevent); } } /** - * 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'])) { + public function onTouchCalendarObject(string $action, + array $objectData):void { + // We only support VEvents for now + if (strcasecmp($objectData['component'], 'vevent') !== 0) { return; } - // Always remove existing reminders for this event - $this->backend->cleanRemindersForEvent($objectData['calendarid'], $objectData['uri']); + switch($action) { + case '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject': + $this->onCalendarObjectCreate($objectData); + break; - /** - * If we are deleting the event, no need to go further - */ - if ($action === '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') { + case '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject': + $this->onCalendarObjectEdit($objectData); + break; + + case '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject': + $this->onCalendarObjectDelete($objectData); + break; + + default: + break; + } + } + + /** + * @param array $objectData + */ + private function onCalendarObjectCreate(array $objectData):void { + /** @var VObject\Component\VCalendar $vcalendar */ + $vcalendar = $this->parseCalendarData($objectData['calendardata']); + if (!$vcalendar) { return; } - $user = $this->userSession->getUser(); - - if ($user === null) { - throw new NoUserException('No user in session'); + $vevents = $this->getAllVEventsFromVCalendar($vcalendar); + if (count($vevents) === 0) { + return; } - $users = $this->getUsersForShares($shares); + $uid = (string) $vevents[0]->UID; + $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents); + $masterItem = $this->getMasterItemFromListOfVEvents($vevents); + $now = $this->timeFactory->getDateTime(); + $isRecurring = $masterItem ? $this->isRecurring($masterItem) : false; - $users[] = $user->getUID(); + foreach($recurrenceExceptions as $recurrenceException) { + $eventHash = $this->getEventHash($recurrenceException); - $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()); - - } + foreach($recurrenceException->VALARM as $valarm) { + /** @var VAlarm $valarm */ + $alarmHash = $this->getAlarmHash($valarm); + $triggerTime = $valarm->getEffectiveTriggerTime(); + $diff = $now->diff($triggerTime); + if ($diff->invert === 1) { + continue; } + + $alarms = $this->getRemindersForVAlarm($valarm, $objectData, + $eventHash, $alarmHash, true, true); + $this->writeRemindersToDatabase($alarms); + } + } + + if ($masterItem) { + $processedAlarms = []; + $masterAlarms = []; + $masterHash = $this->getEventHash($masterItem); + + foreach($masterItem->VALARM as $valarm) { + $masterAlarms[] = $this->getAlarmHash($valarm); + } + + try { + $iterator = new EventIterator($vevents, $uid); + } catch (NoInstancesException $e) { + // This event is recurring, but it doesn't have a single + // instance. We are skipping this event from the output + // entirely. + return; + } + + while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) { + $event = $iterator->getEventObject(); + + // Recurrence-exceptions are handled separately, so just ignore them here + if (\in_array($event, $recurrenceExceptions, true)) { + $iterator->next(); + continue; + } + + foreach($event->VALARM as $valarm) { + /** @var VAlarm $valarm */ + $alarmHash = $this->getAlarmHash($valarm); + if (\in_array($alarmHash, $processedAlarms, true)) { + continue; + } + + if (!\in_array((string) $valarm->ACTION, self::REMINDER_TYPES, true)) { + // Action allows x-name, we don't insert reminders + // into the database if they are not standard + $processedAlarms[] = $alarmHash; + continue; + } + + $triggerTime = $valarm->getEffectiveTriggerTime(); + + // If effective trigger time is in the past + // just skip and generate for next event + $diff = $now->diff($triggerTime); + if ($diff->invert === 1) { + // If an absolute alarm is in the past, + // just add it to processedAlarms, so + // we don't extend till eternity + if (!$this->isAlarmRelative($valarm)) { + $processedAlarms[] = $alarmHash; + } + + continue; + } + + $alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false); + $this->writeRemindersToDatabase($alarms); + $processedAlarms[] = $alarmHash; + } + + $iterator->next(); } } } + /** + * @param array $objectData + */ + private function onCalendarObjectEdit(array $objectData):void { + // TODO - this can be vastly improved + // - get cached reminders + // - ... + + $this->onCalendarObjectDelete($objectData); + $this->onCalendarObjectCreate($objectData); + } /** - * Get all users that have access to a given calendar - * - * @param array $shares - * @return string[] + * @param array $objectData */ - private function getUsersForShares(array $shares):array { - $users = $groups = []; + private function onCalendarObjectDelete(array $objectData):void { + $this->backend->cleanRemindersForEvent((int) $objectData['id']); + } + + /** + * @param VAlarm $valarm + * @param array $objectData + * @param string|null $eventHash + * @param string|null $alarmHash + * @param bool $isRecurring + * @param bool $isRecurrenceException + * @return array + */ + private function getRemindersForVAlarm(VAlarm $valarm, + array $objectData, + string $eventHash=null, + string $alarmHash=null, + bool $isRecurring=false, + bool $isRecurrenceException=false):array { + if ($eventHash === null) { + $eventHash = $this->getEventHash($valarm->parent); + } + if ($alarmHash === null) { + $alarmHash = $this->getAlarmHash($valarm); + } + + $recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($valarm->parent); + $isRelative = $this->isAlarmRelative($valarm); + /** @var DateTimeImmutable $notificationDate */ + $notificationDate = $valarm->getEffectiveTriggerTime(); + $clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone()); + $clonedNotificationDate->setTimestamp($notificationDate->getTimestamp()); + + $alarms = []; + + $alarms[] = [ + 'calendar_id' => $objectData['calendarid'], + 'object_id' => $objectData['id'], + 'uid' => (string) $valarm->parent->UID, + 'is_recurring' => $isRecurring, + 'recurrence_id' => $recurrenceId, + 'is_recurrence_exception' => $isRecurrenceException, + 'event_hash' => $eventHash, + 'alarm_hash' => $alarmHash, + 'type' => (string) $valarm->ACTION, + 'is_relative' => $isRelative, + 'notification_date' => $notificationDate->getTimestamp(), + 'is_repeat_based' => false, + ]; + + $repeat = $valarm->REPEAT ? (int) $valarm->REPEAT : 0; + for($i = 0; $i < $repeat; $i++) { + if ($valarm->DURATION === null) { + continue; + } + + $clonedNotificationDate->add($valarm->DURATION->getDateInterval()); + $alarms[] = [ + 'calendar_id' => $objectData['calendarid'], + 'object_id' => $objectData['id'], + 'uid' => (string) $valarm->parent->UID, + 'is_recurring' => $isRecurring, + 'recurrence_id' => $recurrenceId, + 'is_recurrence_exception' => $isRecurrenceException, + 'event_hash' => $eventHash, + 'alarm_hash' => $alarmHash, + 'type' => (string) $valarm->ACTION, + 'is_relative' => $isRelative, + 'notification_date' => $clonedNotificationDate->getTimestamp(), + 'is_repeat_based' => true, + ]; + } + + return $alarms; + } + + /** + * @param array $reminders + */ + private function writeRemindersToDatabase(array $reminders): void { + foreach($reminders as $reminder) { + $this->backend->insertReminder( + (int) $reminder['calendar_id'], + (int) $reminder['object_id'], + $reminder['uid'], + $reminder['is_recurring'], + (int) $reminder['recurrence_id'], + $reminder['is_recurrence_exception'], + $reminder['event_hash'], + $reminder['alarm_hash'], + $reminder['type'], + $reminder['is_relative'], + (int) $reminder['notification_date'], + $reminder['is_repeat_based'] + ); + } + } + + /** + * @param array $reminder + * @param VEvent $vevent + */ + private function deleteOrProcessNext(array $reminder, + VObject\Component\VEvent $vevent):void { + if ($reminder['is_repeat_based'] || + !$reminder['is_recurring'] || + !$reminder['is_relative'] || + $reminder['is_recurrence_exception']) { + + $this->backend->removeReminder($reminder['id']); + return; + } + + $vevents = $this->getAllVEventsFromVCalendar($vevent->parent); + $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents); + $now = $this->timeFactory->getDateTime(); + + try { + $iterator = new EventIterator($vevents, $reminder['uid']); + } catch (NoInstancesException $e) { + // This event is recurring, but it doesn't have a single + // instance. We are skipping this event from the output + // entirely. + return; + } + + while($iterator->valid()) { + $event = $iterator->getEventObject(); + + // Recurrence-exceptions are handled separately, so just ignore them here + if (\in_array($event, $recurrenceExceptions, true)) { + $iterator->next(); + continue; + } + + $recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($event); + if ($reminder['recurrence_id'] >= $recurrenceId) { + $iterator->next(); + continue; + } + + foreach($event->VALARM as $valarm) { + /** @var VAlarm $valarm */ + $alarmHash = $this->getAlarmHash($valarm); + if ($alarmHash !== $reminder['alarm_hash']) { + continue; + } + + $triggerTime = $valarm->getEffectiveTriggerTime(); + + // If effective trigger time is in the past + // just skip and generate for next event + $diff = $now->diff($triggerTime); + if ($diff->invert === 1) { + continue; + } + + $this->backend->removeReminder($reminder['id']); + $alarms = $this->getRemindersForVAlarm($valarm, [ + 'calendarid' => $reminder['calendar_id'], + 'id' => $reminder['object_id'], + ], $reminder['event_hash'], $alarmHash, true, false); + $this->writeRemindersToDatabase($alarms); + + // Abort generating reminders after creating one successfully + return; + } + + $iterator->next(); + } + + $this->backend->removeReminder($reminder['id']); + } + + /** + * @param int $calendarId + * @return IUser[] + */ + private function getAllUsersWithWriteAccessToCalendar(int $calendarId):array { + $shares = $this->caldavBackend->getShares($calendarId); + + $users = []; + $userIds = []; + $groups = []; foreach ($shares as $share) { + // Only consider writable shares + if ($share['readOnly']) { + continue; + } + $principal = explode('/', $share['{http://owncloud.org/ns}principal']); if ($principal[1] === 'users') { - $users[] = $principal[2]; + $user = $this->userManager->get($principal[2]); + if ($user) { + $users[] = $user; + $userIds[] = $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(); + foreach ($groups as $gid) { + $group = $this->groupManager->get($gid); + if ($group instanceof IGroup) { + foreach ($group->getUsers() as $user) { + if (!\in_array($user->getUID(), $userIds, true)) { + $users[] = $user; + $userIds[] = $user->getUID(); } } } } - return array_unique($users); + return $users; + } + + /** + * Gets a hash of the event. + * If the hash changes, we have to update all relative alarms. + * + * @param VEvent $vevent + * @return string + */ + private function getEventHash(VEvent $vevent):string { + $properties = [ + (string) $vevent->DTSTART->serialize(), + ]; + + if ($vevent->DTEND) { + $properties[] = (string) $vevent->DTEND->serialize(); + } + if ($vevent->DURATION) { + $properties[] = (string) $vevent->DURATION->serialize(); + } + if ($vevent->{'RECURRENCE-ID'}) { + $properties[] = (string) $vevent->{'RECURRENCE-ID'}->serialize(); + } + if ($vevent->RRULE) { + $properties[] = (string) $vevent->RRULE->serialize(); + } + if ($vevent->EXDATE) { + $properties[] = (string) $vevent->EXDATE->serialize(); + } + if ($vevent->RDATE) { + $properties[] = (string) $vevent->RDATE->serialize(); + } + + return md5(implode('::', $properties)); + } + + /** + * Gets a hash of the alarm. + * If the hash changes, we have to update oc_dav_reminders. + * + * @param VAlarm $valarm + * @return string + */ + private function getAlarmHash(VAlarm $valarm):string { + $properties = [ + (string) $valarm->ACTION->serialize(), + (string) $valarm->TRIGGER->serialize(), + ]; + + if ($valarm->DURATION) { + $properties[] = (string) $valarm->DURATION->serialize(); + } + if ($valarm->REPEAT) { + $properties[] = (string) $valarm->REPEAT->serialize(); + } + + return md5(implode('::', $properties)); + } + + /** + * @param VObject\Component\VCalendar $vcalendar + * @param int $recurrenceId + * @param bool $isRecurrenceException + * @return VEvent|null + */ + private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar, + int $recurrenceId, + bool $isRecurrenceException):?VEvent { + $vevents = $this->getAllVEventsFromVCalendar($vcalendar); + if (count($vevents) === 0) { + return null; + } + + $uid = (string) $vevents[0]->UID; + $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents); + $masterItem = $this->getMasterItemFromListOfVEvents($vevents); + + // Handle recurrence-exceptions first, because recurrence-expansion is expensive + if ($isRecurrenceException) { + foreach($recurrenceExceptions as $recurrenceException) { + if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) { + return $recurrenceException; + } + } + + return null; + } + + if ($masterItem) { + try { + $iterator = new EventIterator($vevents, $uid); + } catch (NoInstancesException $e) { + // This event is recurring, but it doesn't have a single + // instance. We are skipping this event from the output + // entirely. + return null; + } + + while ($iterator->valid()) { + $event = $iterator->getEventObject(); + + // Recurrence-exceptions are handled separately, so just ignore them here + if (\in_array($event, $recurrenceExceptions, true)) { + $iterator->next(); + continue; + } + + if ($this->getEffectiveRecurrenceIdOfVEvent($event) === $recurrenceId) { + return $event; + } + + $iterator->next(); + } + } + + return null; + } + + /** + * @param VEvent $vevent + * @return string + */ + private function getStatusOfEvent(VEvent $vevent):string { + if ($vevent->STATUS) { + return (string) $vevent->STATUS; + } + + // Doesn't say so in the standard, + // but we consider events without a status + // to be confirmed + return 'CONFIRMED'; + } + + /** + * @param VObject\Component\VEvent $vevent + * @return bool + */ + private function wasEventCancelled(VObject\Component\VEvent $vevent):bool { + return $this->getStatusOfEvent($vevent) === 'CANCELLED'; + } + + /** + * @param string $calendarData + * @return VObject\Component\VCalendar|null + */ + private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar { + try { + return VObject\Reader::read($calendarData, + VObject\Reader::OPTION_FORGIVING); + } catch(ParseException $ex) { + return null; + } + } + + /** + * @param string $principalUri + * @return IUser|null + */ + private function getUserFromPrincipalURI(string $principalUri):?IUser { + if (!$principalUri) { + return null; + } + + if (strcasecmp($principalUri, 'principals/users/') !== 0) { + return null; + } + + $userId = substr($principalUri, 17); + return $this->userManager->get($userId); + } + + /** + * @param VObject\Component\VCalendar $vcalendar + * @return VObject\Component\VEvent[] + */ + private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array { + $vevents = []; + + foreach($vcalendar->children() as $child) { + if (!($child instanceof VObject\Component)) { + continue; + } + + if ($child->name !== 'VEVENT') { + continue; + } + + $vevents[] = $child; + } + + return $vevents; + } + + /** + * @param array $vevents + * @return VObject\Component\VEvent[] + */ + private function getRecurrenceExceptionFromListOfVEvents(array $vevents):array { + return array_values(array_filter($vevents, function(VEvent $vevent) { + return $vevent->{'RECURRENCE-ID'} !== null; + })); + } + + /** + * @param array $vevents + * @return VEvent|null + */ + private function getMasterItemFromListOfVEvents(array $vevents):?VEvent { + $elements = array_values(array_filter($vevents, function(VEvent $vevent) { + return $vevent->{'RECURRENCE-ID'} === null; + })); + + if (count($elements) === 0) { + return null; + } + if (count($elements) > 1) { + throw new \TypeError('Multiple master objects'); + } + + return $elements[0]; + } + + /** + * @param VAlarm $valarm + * @return bool + */ + private function isAlarmRelative(VAlarm $valarm):bool { + $trigger = $valarm->TRIGGER; + return $trigger instanceof VObject\Property\ICalendar\Duration; + } + + /** + * @param VEvent $vevent + * @return int + */ + private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int { + if (isset($vevent->{'RECURRENCE-ID'})) { + return $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp(); + } + + return $vevent->DTSTART->getDateTime()->getTimestamp(); + } + + /** + * @param VEvent $vevent + * @return bool + */ + private function isRecurring(VEvent $vevent):bool { + return isset($vevent->RRULE) || isset($vevent->RDATE); } } diff --git a/apps/dav/lib/Command/SendEventReminders.php b/apps/dav/lib/Command/SendEventReminders.php index 02d8a28726..93477cb0f7 100644 --- a/apps/dav/lib/Command/SendEventReminders.php +++ b/apps/dav/lib/Command/SendEventReminders.php @@ -27,6 +27,11 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +/** + * Class SendEventReminders + * + * @package OCA\DAV\Command + */ class SendEventReminders extends Command { /** @var ReminderService */ @@ -35,10 +40,13 @@ class SendEventReminders extends Command { /** @var IConfig */ protected $config; - public function __construct(string $name = null, - ReminderService $reminderService, + /** + * @param ReminderService $reminderService + * @param IConfig $config + */ + public function __construct(ReminderService $reminderService, IConfig $config) { - parent::__construct($name); + parent::__construct(); $this->reminderService = $reminderService; $this->config = $config; } diff --git a/apps/dav/lib/Migration/Version1004Date20170825134824.php b/apps/dav/lib/Migration/Version1004Date20170825134824.php index 26855c2e23..f3165a0fe3 100644 --- a/apps/dav/lib/Migration/Version1004Date20170825134824.php +++ b/apps/dav/lib/Migration/Version1004Date20170825134824.php @@ -324,8 +324,7 @@ class Version1004Date20170825134824 extends SimpleMigrationStep { 'length' => 1, ]); $table->addColumn('stripattachments', 'smallint', [ - 'notnull' => false, - 'length' => 1, + ]); $table->addColumn('lastmodified', 'integer', [ 'notnull' => false, diff --git a/apps/dav/lib/Migration/Version1007Date20181005133326.php b/apps/dav/lib/Migration/Version1007Date20181005133326.php deleted file mode 100644 index 1e4cce950a..0000000000 --- a/apps/dav/lib/Migration/Version1007Date20181005133326.php +++ /dev/null @@ -1,82 +0,0 @@ -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/Migration/Version1012Date20190808122342.php b/apps/dav/lib/Migration/Version1012Date20190808122342.php new file mode 100644 index 0000000000..4aa768e705 --- /dev/null +++ b/apps/dav/lib/Migration/Version1012Date20190808122342.php @@ -0,0 +1,116 @@ + + * + * @author Georg Ehrke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\DAV\Migration; + +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +/** + * Auto-generated migration step: Please modify to your needs! + */ +class Version1012Date20190808122342 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + * @since 13.0.0 + */ + public function changeSchema(IOutput $output, + \Closure $schemaClosure, + array $options):?ISchemaWrapper { + /** @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('calendar_id', Type::BIGINT, [ + 'notnull' => true, + 'length' => 11, + ]); + $table->addColumn('object_id', Type::BIGINT, [ + 'notnull' => true, + 'length' => 11, + ]); + $table->addColumn('is_recurring', Type::SMALLINT, [ + 'notnull' => true, + 'length' => 1, + ]); + $table->addColumn('uid', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('recurrence_id', Type::BIGINT, [ + 'notnull' => false, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('is_recurrence_exception', Type::SMALLINT, [ + 'notnull' => true, + 'length' => 1, + ]); + $table->addColumn('event_hash', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('alarm_hash', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('type', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('is_relative', Type::SMALLINT, [ + 'notnull' => true, + 'length' => 1, + ]); + $table->addColumn('notification_date', Type::BIGINT, [ + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('is_repeat_based', Type::SMALLINT, [ + 'notnull' => true, + 'length' => 1, + ]); + + $table->setPrimaryKey(['id']); + $table->addIndex(['object_id'], 'calendar_reminder_objid'); + $table->addIndex(['uid', 'recurrence_id'], 'calendar_reminder_uidrec'); + + return $schema; + } + } +} From 2d6473e79b7d9e2e7435020084061d79d707067c Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Sun, 11 Aug 2019 17:54:14 +0200 Subject: [PATCH 09/10] Add repair step to register reminder index for existing events Signed-off-by: Georg Ehrke --- apps/dav/appinfo/info.xml | 1 + .../composer/composer/autoload_classmap.php | 2 + .../dav/composer/composer/autoload_static.php | 2 + .../BuildReminderIndexBackgroundJob.php | 134 ++++++++++++++++++ .../NotificationProvider/EmailProvider.php | 9 +- ...egisterBuildReminderIndexBackgroundJob.php | 96 +++++++++++++ 6 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php create mode 100644 apps/dav/lib/Migration/RegisterBuildReminderIndexBackgroundJob.php diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 81b93de055..6b593b97b7 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -33,6 +33,7 @@ OCA\DAV\Migration\CalDAVRemoveEmptyValue OCA\DAV\Migration\BuildCalendarSearchIndex OCA\DAV\Migration\RefreshWebcalJobRegistrar + OCA\DAV\Migration\RegisterBuildReminderIndexBackgroundJob OCA\DAV\Migration\RemoveOrphanEventsAndContacts OCA\DAV\Migration\RemoveClassifiedEventActivity diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index a1c2d671b8..1084430719 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -11,6 +11,7 @@ return array( 'OCA\\DAV\\Avatars\\AvatarHome' => $baseDir . '/../lib/Avatars/AvatarHome.php', 'OCA\\DAV\\Avatars\\AvatarNode' => $baseDir . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php', + 'OCA\\DAV\\BackgroundJob\\BuildReminderIndexBackgroundJob' => $baseDir . '/../lib/BackgroundJob/BuildReminderIndexBackgroundJob.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', @@ -179,6 +180,7 @@ return array( 'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => $baseDir . '/../lib/Migration/FixBirthdayCalendarComponent.php', 'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => $baseDir . '/../lib/Migration/RefreshWebcalJobRegistrar.php', 'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => $baseDir . '/../lib/Migration/RegenerateBirthdayCalendars.php', + 'OCA\\DAV\\Migration\\RegisterBuildReminderIndexBackgroundJob' => $baseDir . '/../lib/Migration/RegisterBuildReminderIndexBackgroundJob.php', 'OCA\\DAV\\Migration\\RemoveClassifiedEventActivity' => $baseDir . '/../lib/Migration/RemoveClassifiedEventActivity.php', 'OCA\\DAV\\Migration\\RemoveOrphanEventsAndContacts' => $baseDir . '/../lib/Migration/RemoveOrphanEventsAndContacts.php', 'OCA\\DAV\\Migration\\Version1004Date20170825134824' => $baseDir . '/../lib/Migration/Version1004Date20170825134824.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 39488419f8..b3f5688166 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -26,6 +26,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Avatars\\AvatarHome' => __DIR__ . '/..' . '/../lib/Avatars/AvatarHome.php', 'OCA\\DAV\\Avatars\\AvatarNode' => __DIR__ . '/..' . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php', + 'OCA\\DAV\\BackgroundJob\\BuildReminderIndexBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/BuildReminderIndexBackgroundJob.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', @@ -194,6 +195,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => __DIR__ . '/..' . '/../lib/Migration/FixBirthdayCalendarComponent.php', 'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => __DIR__ . '/..' . '/../lib/Migration/RefreshWebcalJobRegistrar.php', 'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/Migration/RegenerateBirthdayCalendars.php', + 'OCA\\DAV\\Migration\\RegisterBuildReminderIndexBackgroundJob' => __DIR__ . '/..' . '/../lib/Migration/RegisterBuildReminderIndexBackgroundJob.php', 'OCA\\DAV\\Migration\\RemoveClassifiedEventActivity' => __DIR__ . '/..' . '/../lib/Migration/RemoveClassifiedEventActivity.php', 'OCA\\DAV\\Migration\\RemoveOrphanEventsAndContacts' => __DIR__ . '/..' . '/../lib/Migration/RemoveOrphanEventsAndContacts.php', 'OCA\\DAV\\Migration\\Version1004Date20170825134824' => __DIR__ . '/..' . '/../lib/Migration/Version1004Date20170825134824.php', diff --git a/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php b/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php new file mode 100644 index 0000000000..cf55f42440 --- /dev/null +++ b/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php @@ -0,0 +1,134 @@ + + * + * @author Georg Ehrke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\DAV\BackgroundJob; + +use OC\BackgroundJob\QueuedJob; +use OCA\DAV\CalDAV\Reminder\ReminderService; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\IJobList; +use OCP\IDBConnection; +use OCP\ILogger; + +/** + * Class BuildReminderIndexBackgroundJob + * + * @package OCA\DAV\BackgroundJob + */ +class BuildReminderIndexBackgroundJob extends QueuedJob { + + /** @var IDBConnection */ + private $db; + + /** @var ReminderService */ + private $reminderService; + + /** @var ILogger */ + private $logger; + + /** @var IJobList */ + private $jobList; + + /** @var ITimeFactory */ + private $timeFactory; + + /** + * BuildReminderIndexBackgroundJob constructor. + * + * @param IDBConnection $db + * @param ReminderService $reminderService + * @param ILogger $logger + * @param IJobList $jobList + * @param ITimeFactory $timeFactory + */ + public function __construct(IDBConnection $db, + ReminderService $reminderService, + ILogger $logger, + IJobList $jobList, + ITimeFactory $timeFactory) { + $this->db = $db; + $this->reminderService = $reminderService; + $this->logger = $logger; + $this->jobList = $jobList; + $this->timeFactory = $timeFactory; + } + + /** + * @param $arguments + */ + public function run($arguments) { + $offset = (int) $arguments['offset']; + $stopAt = (int) $arguments['stopAt']; + + $this->logger->info('Building calendar reminder index (' . $offset .'/' . $stopAt . ')'); + + $offset = $this->buildIndex($offset, $stopAt); + + if ($offset >= $stopAt) { + $this->logger->info('Building calendar reminder index done'); + } else { + $this->jobList->add(self::class, [ + 'offset' => $offset, + 'stopAt' => $stopAt + ]); + $this->logger->info('Scheduled a new BuildReminderIndexBackgroundJob with offset ' . $offset); + } + } + + /** + * @param int $offset + * @param int $stopAt + * @return int + */ + private function buildIndex(int $offset, int $stopAt):int { + $startTime = $this->timeFactory->getTime(); + + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from('calendarobjects') + ->where($query->expr()->lte('id', $query->createNamedParameter($stopAt))) + ->andWhere($query->expr()->gt('id', $query->createNamedParameter($offset))) + ->orderBy('id', 'ASC'); + + $stmt = $query->execute(); + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $offset = $row['id']; + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + $row['component'] = $row['componenttype']; + + try { + $this->reminderService->onTouchCalendarObject('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', $row); + } catch(\Exception $ex) { + $this->logger->logException($ex); + } + + if (($this->timeFactory->getTime() - $startTime) > 15) { + return $offset; + } + } + + return $stopAt; + } +} \ No newline at end of file diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php index 2a7eb2a403..cf79fad7ab 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php @@ -108,7 +108,8 @@ class EmailProvider extends AbstractProvider { if ($organizer) { $message->setReplyTo($organizer); } - $message->setBcc($emailAddresses); + $message->setTo([]) + ->setBcc($emailAddresses); $template = $this->mailer->createEMailTemplate('dav.calendarReminder'); $template->addHeader(); @@ -185,7 +186,7 @@ class EmailProvider extends AbstractProvider { return null; } - $organizer = $vevent->ORGANZIER; + $organizer = $vevent->ORGANIZER; if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) { return null; } @@ -309,7 +310,7 @@ class EmailProvider extends AbstractProvider { } } - if (isset($vevent->ORGANIZER)) { + if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) { $emailAddresses[$this->getEMailAddressOfAttendee($vevent->ORGANIZER)] = []; } @@ -349,7 +350,7 @@ class EmailProvider extends AbstractProvider { * @return bool */ private function hasAttendeeMailURI(VObject\Property $attendee):bool { - return strcasecmp($attendee->getValue(), 'mailto:') === 0; + return stripos($attendee->getValue(), 'mailto:') === 0; } /** diff --git a/apps/dav/lib/Migration/RegisterBuildReminderIndexBackgroundJob.php b/apps/dav/lib/Migration/RegisterBuildReminderIndexBackgroundJob.php new file mode 100644 index 0000000000..c7a1cf63ab --- /dev/null +++ b/apps/dav/lib/Migration/RegisterBuildReminderIndexBackgroundJob.php @@ -0,0 +1,96 @@ + + * + * @author Georg Ehrke + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\DAV\Migration; + +use OCA\DAV\BackgroundJob\BuildReminderIndexBackgroundJob; +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Class RegisterBuildReminderIndexBackgroundJob + * + * @package OCA\DAV\Migration + */ +class RegisterBuildReminderIndexBackgroundJob implements IRepairStep { + + /** @var IDBConnection */ + private $db; + + /** @var IJobList */ + private $jobList; + + /** @var IConfig */ + private $config; + + /** @var string */ + private const CONFIG_KEY = 'buildCalendarReminderIndex'; + + /** + * @param IDBConnection $db + * @param IJobList $jobList + * @param IConfig $config + */ + public function __construct(IDBConnection $db, + IJobList $jobList, + IConfig $config) { + $this->db = $db; + $this->jobList = $jobList; + $this->config = $config; + } + + /** + * @return string + */ + public function getName() { + return 'Registering building of calendar reminder index as background job'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + // only run once + if ($this->config->getAppValue('dav', self::CONFIG_KEY) === 'yes') { + $output->info('Repair step already executed'); + return; + } + + $query = $this->db->getQueryBuilder(); + $query->select($query->createFunction('MAX(' . $query->getColumnName('id') . ')')) + ->from('calendarobjects'); + $maxId = (int)$query->execute()->fetchColumn(); + + $output->info('Add background job'); + $this->jobList->add(BuildReminderIndexBackgroundJob::class, [ + 'offset' => 0, + 'stopAt' => $maxId + ]); + + // if all were done, no need to redo the repair during next upgrade + $this->config->setAppValue('dav', self::CONFIG_KEY, 'yes'); + } +} \ No newline at end of file From 4d28a4544e1f85046e139146a1be9933e9ff6ae3 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 12 Aug 2019 13:20:03 +0200 Subject: [PATCH 10/10] Final fixes Signed-off-by: Georg Ehrke --- 3rdparty | 2 +- .../BuildReminderIndexBackgroundJob.php | 2 +- apps/dav/lib/CalDAV/Reminder/Backend.php | 2 +- .../NotificationProvider/AbstractProvider.php | 39 +- .../NotificationProvider/EmailProvider.php | 77 +-- .../NotificationProvider/PushProvider.php | 14 +- .../Reminder/NotificationProviderManager.php | 36 +- apps/dav/lib/CalDAV/Reminder/Notifier.php | 12 +- .../lib/CalDAV/Reminder/ReminderService.php | 38 +- .../Version1004Date20170825134824.php | 3 +- .../BackgroundJob/EventReminderJobTest.php | 35 +- .../unit/CalDAV/Reminder/BackendTest.php | 590 +++++++++------- .../AbstractNotificationProviderTest.php | 50 +- .../AudioProviderTest.php | 33 + .../EmailProviderTest.php | 606 +++++++++++++---- .../NotificationProvider/PushProviderTest.php | 161 +++-- .../NotificationProviderManagerTest.php | 56 +- .../unit/CalDAV/Reminder/NotifierTest.php | 64 +- .../CalDAV/Reminder/ReminderServiceTest.php | 639 +++++++++++++----- 19 files changed, 1625 insertions(+), 834 deletions(-) rename apps/dav/tests/unit/CalDAV/Reminder/{ => NotificationProvider}/AbstractNotificationProviderTest.php (54%) create mode 100644 apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AudioProviderTest.php diff --git a/3rdparty b/3rdparty index ef289bc27e..49ccfbb286 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit ef289bc27eae0cdfc3f74f419ace8dda8dd84ef0 +Subproject commit 49ccfbb28661b9ef7743c1725cd2571259215929 diff --git a/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php b/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php index cf55f42440..15c52cd852 100644 --- a/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php +++ b/apps/dav/lib/BackgroundJob/BuildReminderIndexBackgroundJob.php @@ -23,7 +23,7 @@ declare(strict_types=1); */ namespace OCA\DAV\BackgroundJob; -use OC\BackgroundJob\QueuedJob; +use OCP\BackgroundJob\QueuedJob; use OCA\DAV\CalDAV\Reminder\ReminderService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; diff --git a/apps/dav/lib/CalDAV/Reminder/Backend.php b/apps/dav/lib/CalDAV/Reminder/Backend.php index be65c35da0..b3cc013fb3 100644 --- a/apps/dav/lib/CalDAV/Reminder/Backend.php +++ b/apps/dav/lib/CalDAV/Reminder/Backend.php @@ -63,7 +63,7 @@ class Backend { $query = $this->db->getQueryBuilder(); $query->select(['cr.*', 'co.calendardata', 'c.displayname', 'c.principaluri']) ->from('calendar_reminders', 'cr') -// ->where($query->expr()->lte('cr.notification_date', $query->createNamedParameter($this->timeFactory->getTime()))) + ->where($query->expr()->lte('cr.notification_date', $query->createNamedParameter($this->timeFactory->getTime()))) ->leftJoin('cr', 'calendarobjects', 'co', $query->expr()->eq('cr.object_id', 'co.id')) ->leftJoin('cr', 'calendars', 'c', $query->expr()->eq('cr.calendar_id', 'c.id')); $stmt = $query->execute(); diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php index 6b2364c802..6e3a8eadde 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php @@ -25,8 +25,6 @@ declare(strict_types=1); */ namespace OCA\DAV\CalDAV\Reminder\NotificationProvider; -use \DateTime; -use \DateTimeImmutable; use OCA\DAV\CalDAV\Reminder\INotificationProvider; use OCP\IConfig; use OCP\IL10N; @@ -36,7 +34,6 @@ use OCP\L10N\IFactory as L10NFactory; use OCP\IUser; use Sabre\VObject\Component\VEvent; use Sabre\VObject\DateTimeParser; -use Sabre\VObject\Parameter; use Sabre\VObject\Property; /** @@ -49,22 +46,22 @@ abstract class AbstractProvider implements INotificationProvider { /** @var string */ public const NOTIFICATION_TYPE = ''; - /** @var ILogger */ - protected $logger; + /** @var ILogger */ + protected $logger; - /** @var L10NFactory */ - private $l10nFactory; + /** @var L10NFactory */ + private $l10nFactory; - /** @var IL10N[] */ + /** @var IL10N[] */ private $l10ns; /** @var string */ private $fallbackLanguage; - /** @var IURLGenerator */ - protected $urlGenerator; + /** @var IURLGenerator */ + protected $urlGenerator; - /** @var IConfig */ + /** @var IConfig */ protected $config; /** @@ -79,9 +76,9 @@ abstract class AbstractProvider implements INotificationProvider { IConfig $config) { $this->logger = $logger; $this->l10nFactory = $l10nFactory; - $this->urlGenerator = $urlGenerator; + $this->urlGenerator = $urlGenerator; $this->config = $config; - } + } /** * Send notification @@ -91,22 +88,22 @@ abstract class AbstractProvider implements INotificationProvider { * @param IUser[] $users * @return void */ - abstract public function send(VEvent $vevent, + abstract public function send(VEvent $vevent, string $calendarDisplayName, array $users=[]): void; /** * @return string */ - protected function getFallbackLanguage():string { - if ($this->fallbackLanguage) { - return $this->fallbackLanguage; + protected function getFallbackLanguage():string { + if ($this->fallbackLanguage) { + return $this->fallbackLanguage; } - $fallbackLanguage = $this->l10nFactory->findLanguage(); - $this->fallbackLanguage = $fallbackLanguage; + $fallbackLanguage = $this->l10nFactory->findLanguage(); + $this->fallbackLanguage = $fallbackLanguage; - return $fallbackLanguage; + return $fallbackLanguage; } /** @@ -114,7 +111,7 @@ abstract class AbstractProvider implements INotificationProvider { * @return bool */ protected function hasL10NForLang(string $lang):bool { - return $this->l10nFactory->languageExists('dav', $lang); + return $this->l10nFactory->languageExists('dav', $lang); } /** diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php index cf79fad7ab..f5932a87b3 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php @@ -25,8 +25,7 @@ declare(strict_types=1); */ namespace OCA\DAV\CalDAV\Reminder\NotificationProvider; -use DateTime; -use DateTimeImmutable; +use \DateTime; use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; @@ -97,36 +96,35 @@ class EmailProvider extends AbstractProvider { $organizer = $this->getOrganizerEMailAndNameFromEvent($vevent); foreach($sortedByLanguage as $lang => $emailAddresses) { - if ($this->hasL10NForLang($lang)) { + if (!$this->hasL10NForLang($lang)) { $lang = $fallbackLanguage; } $l10n = $this->getL10NForLang($lang); $fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply'); - $message = $this->mailer->createMessage(); - $message->setFrom([$fromEMail]); - if ($organizer) { - $message->setReplyTo($organizer); - } - $message->setTo([]) - ->setBcc($emailAddresses); - $template = $this->mailer->createEMailTemplate('dav.calendarReminder'); $template->addHeader(); - $this->addSubjectAndHeading($template, $l10n, $vevent); $this->addBulletList($template, $l10n, $calendarDisplayName, $vevent); - $template->addFooter(); - $message->useTemplate($template); - try { - $failed = $this->mailer->send($message); - if ($failed) { - $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]); + foreach ($emailAddresses as $emailAddress) { + $message = $this->mailer->createMessage(); + $message->setFrom([$fromEMail]); + if ($organizer) { + $message->setReplyTo($organizer); + } + $message->setTo([$emailAddress]); + $message->useTemplate($template); + + 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']); } - } catch (\Exception $ex) { - $this->logger->logException($ex, ['app' => 'dav']); } } } @@ -165,9 +163,9 @@ class EmailProvider extends AbstractProvider { $template->addBodyListItem((string) $vevent->DESCRIPTION, $l10n->t('Description:'), $this->getAbsoluteImagePath('actions/more.svg')); } - } + } - /** + /** * @param string $path * @return string */ @@ -201,35 +199,6 @@ class EmailProvider extends AbstractProvider { return [$organizerEMail]; } - /** - * @param array $sortedByLanguage - * @param IUser[] $users - * @param string $defaultLanguage - */ - private function sortUsersByLanguage(array &$sortedByLanguage, - array $users, - string $defaultLanguage):void { - /** - * @var array $sortedByLanguage - * [ - * 'de' => ['a@b.com', 'c@d.com'], - * ... - * ] - */ - foreach($users as $user) { - /** @var IUser $user */ - $emailAddress = $user->getEMailAddress(); - $lang = $this->config->getUserValue($user->getUID(), - 'core', 'lang', $defaultLanguage); - - if (!isset($sortedByLanguage[$lang])) { - $sortedByLanguage[$lang] = []; - } - - $sortedByLanguage[$lang][] = $emailAddress; - } - } - /** * @param array $emails * @param string $defaultLanguage @@ -386,7 +355,7 @@ class EmailProvider extends AbstractProvider { } } - return array_unique($emailAddresses); + return $emailAddresses; } /** @@ -415,7 +384,9 @@ class EmailProvider extends AbstractProvider { $diff = $dtstartDt->diff($dtendDt); + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ $dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM)); + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ $dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM)); if ($isAllDay) { @@ -432,7 +403,9 @@ class EmailProvider extends AbstractProvider { $startTimezone = $endTimezone = null; if (!$vevent->DTSTART->isFloating()) { + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ $startTimezone = $vevent->DTSTART->getDateTime()->getTimezone()->getName(); + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ $endTimezone = $this->getDTEndFromEvent($vevent)->getDateTime()->getTimezone()->getName(); } diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php index 2e580fd78a..3872b67e59 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php @@ -47,7 +47,7 @@ class PushProvider extends AbstractProvider { /** @var string */ public const NOTIFICATION_TYPE = 'DISPLAY'; - /** @var IManager */ + /** @var IManager */ private $manager; /** @var ITimeFactory */ @@ -70,7 +70,7 @@ class PushProvider extends AbstractProvider { parent::__construct($logger, $l10nFactory, $urlGenerator, $config); $this->manager = $manager; $this->timeFactory = $timeFactory; - } + } /** * Send push notification to all users. @@ -80,13 +80,13 @@ class PushProvider extends AbstractProvider { * @param IUser[] $users * @throws \Exception */ - public function send(VEvent $vevent, + public function send(VEvent $vevent, string $calendarDisplayName=null, array $users=[]):void { $eventDetails = $this->extractEventDetails($vevent); $eventDetails['calendar_displayname'] = $calendarDisplayName; - foreach($users as $user) { + foreach($users as $user) { /** @var INotification $notification */ $notification = $this->manager->createNotification(); $notification->setApp(Application::APP_ID) @@ -101,7 +101,7 @@ class PushProvider extends AbstractProvider { $this->manager->notify($notification); } - } + } /** * @var VEvent $vevent @@ -124,11 +124,15 @@ class PushProvider extends AbstractProvider { ? ((string) $vevent->LOCATION) : null, 'all_day' => $start instanceof Property\ICalendar\Date, + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ 'start_atom' => $start->getDateTime()->format(\DateTime::ATOM), 'start_is_floating' => $start->isFloating(), + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ 'start_timezone' => $start->getDateTime()->getTimezone()->getName(), + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ 'end_atom' => $end->getDateTime()->format(\DateTime::ATOM), 'end_is_floating' => $end->isFloating(), + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ 'end_timezone' => $end->getDateTime()->getTimezone()->getName(), ]; } diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php index 3d54970562..c9bcf2dd06 100644 --- a/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php +++ b/apps/dav/lib/CalDAV/Reminder/NotificationProviderManager.php @@ -31,8 +31,8 @@ namespace OCA\DAV\CalDAV\Reminder; */ class NotificationProviderManager { - /** @var INotificationProvider[] */ - private $providers = []; + /** @var INotificationProvider[] */ + private $providers = []; /** * Checks whether a provider for a given ACTION exists @@ -45,23 +45,23 @@ class NotificationProviderManager { && isset($this->providers[$type])); } - /** + /** * Get provider for a given ACTION * - * @param string $type - * @return INotificationProvider - * @throws NotificationProvider\ProviderNotAvailableException - * @throws NotificationTypeDoesNotExistException - */ - public function getProvider(string $type):INotificationProvider { - if (in_array($type, ReminderService::REMINDER_TYPES, true)) { - if (isset($this->providers[$type])) { - return $this->providers[$type]; - } - throw new NotificationProvider\ProviderNotAvailableException($type); - } - throw new NotificationTypeDoesNotExistException($type); - } + * @param string $type + * @return INotificationProvider + * @throws NotificationProvider\ProviderNotAvailableException + * @throws NotificationTypeDoesNotExistException + */ + public function getProvider(string $type):INotificationProvider { + if (in_array($type, ReminderService::REMINDER_TYPES, true)) { + if (isset($this->providers[$type])) { + return $this->providers[$type]; + } + throw new NotificationProvider\ProviderNotAvailableException($type); + } + throw new NotificationTypeDoesNotExistException($type); + } /** * Registers a new provider @@ -69,7 +69,7 @@ class NotificationProviderManager { * @param string $providerClassName * @throws \OCP\AppFramework\QueryException */ - public function registerProvider(string $providerClassName):void { + public function registerProvider(string $providerClassName):void { $provider = \OC::$server->query($providerClassName); if (!$provider instanceof INotificationProvider) { diff --git a/apps/dav/lib/CalDAV/Reminder/Notifier.php b/apps/dav/lib/CalDAV/Reminder/Notifier.php index 4bad984178..2c5f05f62a 100644 --- a/apps/dav/lib/CalDAV/Reminder/Notifier.php +++ b/apps/dav/lib/CalDAV/Reminder/Notifier.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace OCA\DAV\CalDAV\Reminder; -use DateTime; +use \DateTime; use OCA\DAV\AppInfo\Application; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IL10N; @@ -85,10 +85,6 @@ class Notifier implements INotifier { * @since 17.0.0 */ public function getName():string { - if ($this->l10n) { - return $this->l10n->t('Calendar'); - } - return $this->l10nFactory->get('dav')->t('Calendar'); } @@ -143,7 +139,7 @@ class Notifier implements INotifier { private function prepareNotificationSubject(INotification $notification): void { $parameters = $notification->getSubjectParameters(); - $startTime = \DateTime::createFromFormat(\DateTimeInterface::ATOM, $parameters['start_atom']); + $startTime = \DateTime::createFromFormat(\DateTime::ATOM, $parameters['start_atom']); $now = $this->timeFactory->getDateTime(); $title = $this->getTitleFromParameters($parameters); @@ -220,8 +216,8 @@ class Notifier implements INotifier { * @throws \Exception */ private function generateDateString(array $parameters):string { - $startDateTime = DateTime::createFromFormat(DATE_ATOM, $parameters['start_atom']); - $endDateTime = DateTime::createFromFormat(DATE_ATOM, $parameters['end_atom']); + $startDateTime = DateTime::createFromFormat(\DateTime::ATOM, $parameters['start_atom']); + $endDateTime = DateTime::createFromFormat(\DateTime::ATOM, $parameters['end_atom']); $isAllDay = $parameters['all_day']; $diff = $startDateTime->diff($endDateTime); diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php index ad428eef74..f36ddd157c 100644 --- a/apps/dav/lib/CalDAV/Reminder/ReminderService.php +++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php @@ -24,7 +24,7 @@ declare(strict_types=1); */ namespace OCA\DAV\CalDAV\Reminder; -use DateTimeImmutable; +use \DateTimeImmutable; use OCA\DAV\CalDAV\CalDavBackend; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IGroup; @@ -40,11 +40,11 @@ use Sabre\VObject\Recur\NoInstancesException; class ReminderService { - /** @var Backend */ - private $backend; + /** @var Backend */ + private $backend; - /** @var NotificationProviderManager */ - private $notificationProviderManager; + /** @var NotificationProviderManager */ + private $notificationProviderManager; /** @var IUserManager */ private $userManager; @@ -83,19 +83,19 @@ class ReminderService { * @param CalDavBackend $caldavBackend * @param ITimeFactory $timeFactory */ - public function __construct(Backend $backend, - NotificationProviderManager $notificationProviderManager, + public function __construct(Backend $backend, + NotificationProviderManager $notificationProviderManager, IUserManager $userManager, IGroupManager $groupManager, CalDavBackend $caldavBackend, ITimeFactory $timeFactory) { - $this->backend = $backend; - $this->notificationProviderManager = $notificationProviderManager; + $this->backend = $backend; + $this->notificationProviderManager = $notificationProviderManager; $this->userManager = $userManager; $this->groupManager = $groupManager; $this->caldavBackend = $caldavBackend; $this->timeFactory = $timeFactory; - } + } /** * Process reminders to activate @@ -103,12 +103,12 @@ class ReminderService { * @throws NotificationProvider\ProviderNotAvailableException * @throws NotificationTypeDoesNotExistException */ - public function processReminders():void { - $reminders = $this->backend->getRemindersToProcess(); + public function processReminders():void { + $reminders = $this->backend->getRemindersToProcess(); - foreach($reminders as $reminder) { - $vcalendar = $this->parseCalendarData($reminder['calendardata']); - if (!$vcalendar) { + foreach($reminders as $reminder) { + $vcalendar = $this->parseCalendarData($reminder['calendardata']); + if (!$vcalendar) { $this->backend->removeReminder($reminder['id']); continue; } @@ -140,7 +140,7 @@ class ReminderService { $this->deleteOrProcessNext($reminder, $vevent); } - } + } /** * @param string $action @@ -175,7 +175,7 @@ class ReminderService { /** * @param array $objectData */ - private function onCalendarObjectCreate(array $objectData):void { + private function onCalendarObjectCreate(array $objectData):void { /** @var VObject\Component\VCalendar $vcalendar */ $vcalendar = $this->parseCalendarData($objectData['calendardata']); if (!$vcalendar) { @@ -343,7 +343,7 @@ class ReminderService { 'is_repeat_based' => false, ]; - $repeat = $valarm->REPEAT ? (int) $valarm->REPEAT : 0; + $repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0; for($i = 0; $i < $repeat; $i++) { if ($valarm->DURATION === null) { continue; @@ -671,7 +671,7 @@ class ReminderService { return null; } - if (strcasecmp($principalUri, 'principals/users/') !== 0) { + if (stripos($principalUri, 'principals/users/') !== 0) { return null; } diff --git a/apps/dav/lib/Migration/Version1004Date20170825134824.php b/apps/dav/lib/Migration/Version1004Date20170825134824.php index f3165a0fe3..26855c2e23 100644 --- a/apps/dav/lib/Migration/Version1004Date20170825134824.php +++ b/apps/dav/lib/Migration/Version1004Date20170825134824.php @@ -324,7 +324,8 @@ class Version1004Date20170825134824 extends SimpleMigrationStep { 'length' => 1, ]); $table->addColumn('stripattachments', 'smallint', [ - + 'notnull' => false, + 'length' => 1, ]); $table->addColumn('lastmodified', 'integer', [ 'notnull' => false, diff --git a/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php index 960dd481dd..c3b4b7e54c 100644 --- a/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php @@ -51,17 +51,42 @@ class EventReminderJobTest extends TestCase { public function data(): array { - return [[true], [false]]; + return [ + [true, true, true], + [true, false, false], + [false, true, false], + [false, false, false], + ]; } /** * @dataProvider data + * * @param bool $sendEventReminders + * @param bool $sendEventRemindersMode + * @param bool $expectCall */ - 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'); + public function testRun(bool $sendEventReminders, bool $sendEventRemindersMode, bool $expectCall): void { + $this->config->expects($this->at(0)) + ->method('getAppValue') + ->with('dav', 'sendEventReminders', 'yes') + ->willReturn($sendEventReminders ? 'yes' : 'no'); + + if ($sendEventReminders) { + $this->config->expects($this->at(1)) + ->method('getAppValue') + ->with('dav', 'sendEventRemindersMode', 'backgroundjob') + ->willReturn($sendEventRemindersMode ? 'backgroundjob' : 'cron'); + + } + + if ($expectCall) { + $this->reminderService->expects($this->once()) + ->method('processReminders'); + } else { + $this->reminderService->expects($this->never()) + ->method('processReminders'); + } $this->backgroundJob->run([]); } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php b/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php index 1cd979dc5d..71453ac73a 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php @@ -1,8 +1,11 @@ + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -21,7 +24,6 @@ */ 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; @@ -29,285 +31,367 @@ 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; + /** + * Reminder Backend + * + * @var ReminderBackend|\PHPUnit\Framework\MockObject\MockObject + */ + private $reminderBackend; /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; + private $timeFactory; - public function setUp() { + public function setUp() { parent::setUp(); - $this->dbConnection = $this->createMock(IDBConnection::class); + $query = self::$realDatabase->getQueryBuilder(); + $query->delete('calendar_reminders')->execute(); + $query->delete('calendarobjects')->execute(); + $query->delete('calendars')->execute(); + $this->timeFactory = $this->createMock(ITimeFactory::class); - $this->reminderBackend = new ReminderBackend($this->dbConnection, $this->timeFactory); - } + $this->reminderBackend = new ReminderBackend(self::$realDatabase, $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'); + $this->createRemindersTestSet(); } - 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); + protected function tearDown() { + $query = self::$realDatabase->getQueryBuilder(); + $query->delete('calendar_reminders')->execute(); + $query->delete('calendarobjects')->execute(); + $query->delete('calendars')->execute(); } - 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)); + public function testCleanRemindersForEvent(): void { + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); - $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'], - ])); + $this->assertCount(4, $rows); - $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->cleanRemindersForEvent(1); - $this->reminderBackend->removeReminder(16); + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); + + $this->assertCount(2, $rows); } - 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' - ]]; + public function testCleanRemindersForCalendar(): void { + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); - $this->timeFactory->expects($this->exactly(2)) + $this->assertCount(4, $rows); + + $this->reminderBackend->cleanRemindersForCalendar(1); + + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); + + $this->assertCount(1, $rows); + } + + public function testRemoveReminder(): void { + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); + + $this->assertCount(4, $rows); + + $this->reminderBackend->removeReminder((int) $rows[3]['id']); + + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); + + $this->assertCount(3, $rows); + } + + public function testGetRemindersToProcess(): void { + $this->timeFactory->expects($this->exactly(1)) ->method('getTime') ->with() - ->willReturn(1337); + ->willReturn(123457); - /** @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); + $rows = $this->reminderBackend->getRemindersToProcess(); - $this->dbConnection->expects($this->once()) - ->method('getQueryBuilder') - ->with() - ->willReturn($queryBuilder); - $queryBuilder->method('expr') - ->willReturn($expr); + $this->assertCount(2, $rows); + unset($rows[0]['id']); + unset($rows[1]['id']); - $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); + $this->assertEquals($rows[0], [ + 'calendar_id' => 1, + 'object_id' => 1, + 'uid' => 'asd', + 'is_recurring' => false, + 'recurrence_id' => 123458, + 'is_recurrence_exception' => false, + 'event_hash' => 'asd123', + 'alarm_hash' => 'asd567', + 'type' => 'EMAIL', + 'is_relative' => true, + 'notification_date' => 123456, + 'is_repeat_based' => false, + 'calendardata' => 'Calendar data 123', + 'displayname' => 'Displayname 123', + 'principaluri' => 'principals/users/user001', + ]); + $this->assertEquals($rows[1], [ + 'calendar_id' => 1, + 'object_id' => 1, + 'uid' => 'asd', + 'is_recurring' => false, + 'recurrence_id' => 123458, + 'is_recurrence_exception' => false, + 'event_hash' => 'asd123', + 'alarm_hash' => 'asd567', + 'type' => 'AUDIO', + 'is_relative' => true, + 'notification_date' => 123456, + 'is_repeat_based' => false, + 'calendardata' => 'Calendar data 123', + 'displayname' => 'Displayname 123', + 'principaluri' => 'principals/users/user001', + ]); } - 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); + public function testGetAllScheduledRemindersForEvent(): void { + $rows = $this->reminderBackend->getAllScheduledRemindersForEvent(1); - $this->dbConnection->expects($this->once()) - ->method('getQueryBuilder') - ->with() - ->will($this->returnValue($queryBuilder)); - $queryBuilder->method('expr') - ->will($this->returnValue($expr)); + $this->assertCount(2, $rows); + unset($rows[0]['id']); + unset($rows[1]['id']); - $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'], - ])); + $this->assertEquals($rows[0], [ + 'calendar_id' => 1, + 'object_id' => 1, + 'uid' => 'asd', + 'is_recurring' => false, + 'recurrence_id' => 123458, + 'is_recurrence_exception' => false, + 'event_hash' => 'asd123', + 'alarm_hash' => 'asd567', + 'type' => 'EMAIL', + 'is_relative' => true, + 'notification_date' => 123456, + 'is_repeat_based' => false, + ]); + $this->assertEquals($rows[1], [ + 'calendar_id' => 1, + 'object_id' => 1, + 'uid' => 'asd', + 'is_recurring' => false, + 'recurrence_id' => 123458, + 'is_recurrence_exception' => false, + 'event_hash' => 'asd123', + 'alarm_hash' => 'asd567', + 'type' => 'AUDIO', + 'is_relative' => true, + 'notification_date' => 123456, + 'is_repeat_based' => false, + ]); + } - $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', + public function testInsertReminder(): void { + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); + + $this->assertCount(4, $rows); + + $this->reminderBackend->insertReminder(42, 1337, 'uid99', true, 12345678, + true, 'hash99', 'hash42', 'AUDIO', false, 12345670, false); + + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); + + $this->assertCount(5, $rows); + + unset($rows[4]['id']); + + $this->assertEquals($rows[4], [ + 'calendar_id' => '42', + 'object_id' => '1337', + 'is_recurring' => '1', + 'uid' => 'uid99', + 'recurrence_id' => '12345678', + 'is_recurrence_exception' => '1', + 'event_hash' => 'hash99', + 'alarm_hash' => 'hash42', + 'type' => 'AUDIO', + 'is_relative' => '0', + 'notification_date' => '12345670', + 'is_repeat_based' => '0', + ]); + } + + public function testUpdateReminder() { + $query = self::$realDatabase->getQueryBuilder(); + $rows = $query->select('*') + ->from('calendar_reminders') + ->execute() + ->fetchAll(); + + $this->assertCount(4, $rows); + + $this->assertEquals($rows[3]['notification_date'], 123600); + + $reminderId = (int) $rows[3]['id']; + $newNotificationDate = 123700; + + $this->reminderBackend->updateReminder($reminderId, $newNotificationDate); + + $query = self::$realDatabase->getQueryBuilder(); + $row = $query->select('notification_date') + ->from('calendar_reminders') + ->where($query->expr()->eq('id', $query->createNamedParameter($reminderId))) + ->execute() + ->fetch(); + + $this->assertEquals((int) $row['notification_date'], 123700); + } + + + private function createRemindersTestSet(): void { + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendars') + ->values([ + 'id' => $query->createNamedParameter(1), + 'principaluri' => $query->createNamedParameter('principals/users/user001'), + 'displayname' => $query->createNamedParameter('Displayname 123'), ]) - ->willReturn($queryBuilder); - $queryBuilder->expects($this->at(8)) - ->method('execute') - ->with() - ->willReturn($stmt); + ->execute(); - $actual = $this->reminderBackend->insertReminder('user1', '1', 'object.ics', 'EMAIL', 1227, 1337); - } + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendars') + ->values([ + 'id' => $query->createNamedParameter(99), + 'principaluri' => $query->createNamedParameter('principals/users/user002'), + 'displayname' => $query->createNamedParameter('Displayname 99'), + ]) + ->execute(); + + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendarobjects') + ->values([ + 'id' => $query->createNamedParameter(1), + 'calendardata' => $query->createNamedParameter('Calendar data 123'), + 'calendarid' => $query->createNamedParameter(1), + 'size' => $query->createNamedParameter(42), + ]) + ->execute(); + + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendarobjects') + ->values([ + 'id' => $query->createNamedParameter(2), + 'calendardata' => $query->createNamedParameter('Calendar data 456'), + 'calendarid' => $query->createNamedParameter(1), + 'size' => $query->createNamedParameter(42), + ]) + ->execute(); + + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendarobjects') + ->values([ + 'id' => $query->createNamedParameter(10), + 'calendardata' => $query->createNamedParameter('Calendar data 789'), + 'calendarid' => $query->createNamedParameter(99), + 'size' => $query->createNamedParameter(42), + ]) + ->execute(); + + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendar_reminders') + ->values([ + 'calendar_id' => $query->createNamedParameter(1), + 'object_id' => $query->createNamedParameter(1), + 'uid' => $query->createNamedParameter('asd'), + 'is_recurring' => $query->createNamedParameter(0), + 'recurrence_id' => $query->createNamedParameter(123458), + 'is_recurrence_exception' => $query->createNamedParameter(0), + 'event_hash' => $query->createNamedParameter('asd123'), + 'alarm_hash' => $query->createNamedParameter('asd567'), + 'type' => $query->createNamedParameter('EMAIL'), + 'is_relative' => $query->createNamedParameter(1), + 'notification_date' => $query->createNamedParameter(123456), + 'is_repeat_based' => $query->createNamedParameter(0), + ]) + ->execute(); + + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendar_reminders') + ->values([ + 'calendar_id' => $query->createNamedParameter(1), + 'object_id' => $query->createNamedParameter(1), + 'uid' => $query->createNamedParameter('asd'), + 'is_recurring' => $query->createNamedParameter(0), + 'recurrence_id' => $query->createNamedParameter(123458), + 'is_recurrence_exception' => $query->createNamedParameter(0), + 'event_hash' => $query->createNamedParameter('asd123'), + 'alarm_hash' => $query->createNamedParameter('asd567'), + 'type' => $query->createNamedParameter('AUDIO'), + 'is_relative' => $query->createNamedParameter(1), + 'notification_date' => $query->createNamedParameter(123456), + 'is_repeat_based' => $query->createNamedParameter(0), + ]) + ->execute(); + + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendar_reminders') + ->values([ + 'calendar_id' => $query->createNamedParameter(1), + 'object_id' => $query->createNamedParameter(2), + 'uid' => $query->createNamedParameter('asd'), + 'is_recurring' => $query->createNamedParameter(0), + 'recurrence_id' => $query->createNamedParameter(123900), + 'is_recurrence_exception' => $query->createNamedParameter(0), + 'event_hash' => $query->createNamedParameter('asd123'), + 'alarm_hash' => $query->createNamedParameter('asd567'), + 'type' => $query->createNamedParameter('EMAIL'), + 'is_relative' => $query->createNamedParameter(1), + 'notification_date' => $query->createNamedParameter(123499), + 'is_repeat_based' => $query->createNamedParameter(0), + ]) + ->execute(); + + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendar_reminders') + ->values([ + 'calendar_id' => $query->createNamedParameter(99), + 'object_id' => $query->createNamedParameter(10), + 'uid' => $query->createNamedParameter('asd'), + 'is_recurring' => $query->createNamedParameter(0), + 'recurrence_id' => $query->createNamedParameter(123900), + 'is_recurrence_exception' => $query->createNamedParameter(0), + 'event_hash' => $query->createNamedParameter('asd123'), + 'alarm_hash' => $query->createNamedParameter('asd567'), + 'type' => $query->createNamedParameter('DISPLAY'), + 'is_relative' => $query->createNamedParameter(1), + 'notification_date' => $query->createNamedParameter(123600), + 'is_repeat_based' => $query->createNamedParameter(0), + ]) + ->execute(); + } } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AbstractNotificationProviderTest.php similarity index 54% rename from apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php rename to apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AbstractNotificationProviderTest.php index ba2e54af33..5a8d328ef8 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/AbstractNotificationProviderTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AbstractNotificationProviderTest.php @@ -1,4 +1,5 @@ * */ -namespace OCA\DAV\Tests\unit\CalDAV\Reminder; +namespace OCA\DAV\Tests\unit\CalDAV\Reminder\NotificationProvider; -use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider; +use OCA\DAV\CalDAV\Reminder\NotificationProvider\AbstractProvider; use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; @@ -33,23 +34,23 @@ use Sabre\VObject\Component\VCalendar; abstract class AbstractNotificationProviderTest extends TestCase { - /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ - protected $logger; + /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ + protected $logger; - /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ - protected $l10nFactory; + /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10nFactory; - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - protected $l10n; + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10n; - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - protected $urlGenerator; + /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ + protected $urlGenerator; - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - protected $config; + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; - /** @var AbstractNotificationProvider|\PHPUnit\Framework\MockObject\MockObject */ - protected $provider; + /** @var AbstractProvider|\PHPUnit\Framework\MockObject\MockObject */ + protected $provider; /** * @var VCalendar @@ -69,19 +70,20 @@ abstract class AbstractNotificationProviderTest extends TestCase { 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->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 = new VCalendar(); $this->vcalendar->add('VEVENT', [ 'SUMMARY' => 'Fellowship meeting', - 'DTSTART' => new \DateTime('2017-01-01 00:00:00') // 1483228800 - ]); + 'DTSTART' => new \DateTime('2017-01-01 00:00:00+00:00'), // 1483228800, + 'UID' => 'uid1234', + ]); $this->calendarDisplayName = 'Personal'; - $this->user = $this->createMock(IUser::class); - } + $this->user = $this->createMock(IUser::class); + } } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AudioProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AudioProviderTest.php new file mode 100644 index 0000000000..9938b2f732 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AudioProviderTest.php @@ -0,0 +1,33 @@ + + * + * @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 + * + */ +namespace OCA\DAV\Tests\unit\CalDAV\Reminder\NotificationProvider; + +use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider; + +class AudioProviderTest extends PushProviderTest { + + public function testNotificationType():void { + $this->assertEquals(AudioProvider::NOTIFICATION_TYPE, 'AUDIO'); + } + +} \ No newline at end of file diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php index 34a61b34fc..9bf2957e9f 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php @@ -1,8 +1,11 @@ + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -22,7 +25,6 @@ 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; @@ -33,194 +35,512 @@ use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Mail\IAttachment; use OCP\Mail\IMessage; +use Sabre\VObject\Component\VCalendar; use Test\TestCase; -use OCA\DAV\Tests\unit\CalDAV\Reminder\AbstractNotificationProviderTest; class EmailProviderTest extends AbstractNotificationProviderTest { - const USER_EMAIL = 'frodo@hobb.it'; + const USER_EMAIL = 'frodo@hobb.it'; - /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ - protected $logger; + /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ + protected $logger; - /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ - protected $l10nFactory; + /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10nFactory; - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - protected $l10n; + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + protected $l10n; - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - protected $urlGenerator; + /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ + protected $urlGenerator; - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - protected $config; + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; - /** @var IMailer|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IMailer|\PHPUnit\Framework\MockObject\MockObject */ private $mailer; - public function setUp() { - parent::setUp(); + public function setUp() { + parent::setUp(); - $this->mailer = $this->createMock(IMailer::class); + $this->mailer = $this->createMock(IMailer::class); - $this->provider = new EmailProvider( - $this->config, - $this->mailer, - $this->logger, - $this->l10nFactory, - $this->urlGenerator - ); - } + $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); + public function testSendWithoutAttendees():void { + $user1 = $this->createMock(IUser::class); + $user1->method('getUID') + ->willReturn('uid1'); + $user1->method('getEMailAddress') + ->willReturn('uid1@example.com'); + $user2 = $this->createMock(IUser::class); + $user2->method('getUID') + ->willReturn('uid2'); + $user2->method('getEMailAddress') + ->willReturn('uid2@example.com'); + $user3 = $this->createMock(IUser::class); + $user3->method('getUID') + ->willReturn('uid3'); + $user3->method('getEMailAddress') + ->willReturn('uid3@example.com'); + $user4 = $this->createMock(IUser::class); + $user4->method('getUID') + ->willReturn('uid4'); + $user4->method('getEMailAddress') + ->willReturn(null); - $this->mailer - ->expects($this->never()) - ->method('send'); + $users = [$user1, $user2, $user3, $user4]; - $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); - } + $this->config->expects($this->at(0)) + ->method('getUserValue') + ->with('uid1', 'core', 'lang', null) + ->willReturn(null); + $this->config->expects($this->at(1)) + ->method('getUserValue') + ->with('uid2', 'core', 'lang', null) + ->willReturn('de'); + $this->config->expects($this->at(2)) + ->method('getUserValue') + ->with('uid3', 'core', 'lang', null) + ->willReturn('de'); - public function testSendWithFailedRecipients(): void - { - $this->user->expects($this->exactly(2)) - ->method('getEMailAddress') - ->with() - ->willReturn(self::USER_EMAIL); + $enL10N = $this->createMock(IL10N::class); + $enL10N->method('t') + ->will($this->returnArgument(0)); + $enL10N->method('l') + ->will($this->returnArgument(0)); - $this->mailer - ->expects($this->once()) - ->method('send') - ->willReturn([self::USER_EMAIL]) - ; + $deL10N = $this->createMock(IL10N::class); + $deL10N->method('t') + ->will($this->returnArgument(0)); + $deL10N->method('l') + ->will($this->returnArgument(0)); - $this->logger - ->expects($this->once()) - ->method('error'); + $this->l10nFactory->expects($this->at(0)) + ->method('findLanguage') + ->with() + ->willReturn('en'); - $l10n = $this->createMock(IL10N::class); - $this->l10nFactory - ->method('get') - ->willReturn($l10n); + $this->l10nFactory->expects($this->at(1)) + ->method('languageExists') + ->with('dav', 'en') + ->willReturn(true); - $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); - } + $this->l10nFactory->expects($this->at(2)) + ->method('get') + ->with('dav', 'en') + ->willReturn($enL10N); - public function testSendWithMailerFailure(): void - { - $this->user->expects($this->exactly(2)) - ->method('getEMailAddress') - ->with() - ->willReturn(self::USER_EMAIL); + $this->l10nFactory->expects($this->at(3)) + ->method('languageExists') + ->with('dav', 'de') + ->willReturn(true); - $ex = new \Exception(); + $this->l10nFactory->expects($this->at(4)) + ->method('get') + ->with('dav', 'de') + ->willReturn($deL10N); - $this->mailer - ->expects($this->once()) - ->method('send') - ->will($this->throwException($ex)) - ; + $template1 = $this->getTemplateMock(); + $message11 = $this->getMessageMock('uid1@example.com', $template1); + $template2 = $this->getTemplateMock(); + $message21 = $this->getMessageMock('uid2@example.com', $template2); + $message22 = $this->getMessageMock('uid3@example.com', $template2); - $this->logger - ->expects($this->once()) - ->method('logException') - ->with($ex, ['app' => 'dav']); + $this->mailer->expects($this->at(0)) + ->method('createEMailTemplate') + ->with('dav.calendarReminder') + ->willReturn($template1); - $l10n = $this->createMock(IL10N::class); - $this->l10nFactory - ->method('get') - ->willReturn($l10n); + $this->mailer->expects($this->at(1)) + ->method('createMessage') + ->with() + ->willReturn($message11); + $this->mailer->expects($this->at(2)) + ->method('send') + ->with($message11) + ->willReturn([]); - $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); - } + $this->mailer->expects($this->at(3)) + ->method('createEMailTemplate') + ->with('dav.calendarReminder') + ->willReturn($template2); - public function testSend(): void - { - $this->user->expects($this->exactly(2)) - ->method('getEMailAddress') - ->with() - ->willReturn(self::USER_EMAIL); + $this->mailer->expects($this->at(4)) + ->method('createMessage') + ->with() + ->willReturn($message21); + $this->mailer->expects($this->at(5)) + ->method('send') + ->with($message21) + ->willReturn([]); + $this->mailer->expects($this->at(6)) + ->method('createMessage') + ->with() + ->willReturn($message22); + $this->mailer->expects($this->at(7)) + ->method('send') + ->with($message22) + ->willReturn([]); - $this->user->expects($this->once()) - ->method('getDisplayName') - ->with() - ->willReturn('Frodo'); + $this->setupURLGeneratorMock(2); - $this->urlGenerator - ->expects($this->exactly(2)) - ->method('getAbsoluteURL'); + $vcalendar = $this->getNoAttendeeVCalendar(); + $this->provider->send($vcalendar->VEVENT, $this->calendarDisplayName, $users); + } - $this->urlGenerator - ->expects($this->exactly(2)) - ->method('imagePath'); + public function testSendWithAttendees(): void { + $user1 = $this->createMock(IUser::class); + $user1->method('getUID') + ->willReturn('uid1'); + $user1->method('getEMailAddress') + ->willReturn('uid1@example.com'); + $user2 = $this->createMock(IUser::class); + $user2->method('getUID') + ->willReturn('uid2'); + $user2->method('getEMailAddress') + ->willReturn('uid2@example.com'); + $user3 = $this->createMock(IUser::class); + $user3->method('getUID') + ->willReturn('uid3'); + $user3->method('getEMailAddress') + ->willReturn('uid3@example.com'); + $user4 = $this->createMock(IUser::class); + $user4->method('getUID') + ->willReturn('uid4'); + $user4->method('getEMailAddress') + ->willReturn(null); - $mailMessage = $this->createMock(IMessage::class); - $mailMessage->expects($this->once()) - ->method('setFrom') - ->with([\OCP\Util::getDefaultEmailAddress('invitations-noreply') => 'Nextcloud']) - ->willReturn($mailMessage); + $users = [$user1, $user2, $user3, $user4]; - $mailMessage->expects($this->once()) - ->method('setTo') - ->with([self::USER_EMAIL => 'Frodo']) - ->willReturn($mailMessage); + $this->config->expects($this->at(0)) + ->method('getUserValue') + ->with('uid1', 'core', 'lang', null) + ->willReturn(null); + $this->config->expects($this->at(1)) + ->method('getUserValue') + ->with('uid2', 'core', 'lang', null) + ->willReturn('de'); + $this->config->expects($this->at(2)) + ->method('getUserValue') + ->with('uid3', 'core', 'lang', null) + ->willReturn('de'); - $mailMessage - ->expects($this->never()) - ->method('setReplyTo') - ->willReturn($mailMessage); + $enL10N = $this->createMock(IL10N::class); + $enL10N->method('t') + ->will($this->returnArgument(0)); + $enL10N->method('l') + ->will($this->returnArgument(0)); - $emailTemplate = $this->createMock(IEMailTemplate::class); - $this->mailer - ->expects($this->once()) - ->method('createEMailTemplate') - ->willReturn($emailTemplate); + $deL10N = $this->createMock(IL10N::class); + $deL10N->method('t') + ->will($this->returnArgument(0)); + $deL10N->method('l') + ->will($this->returnArgument(0)); - $emailTemplate->expects($this->once()) + $this->l10nFactory->expects($this->at(0)) + ->method('findLanguage') + ->with() + ->willReturn('en'); + + $this->l10nFactory->expects($this->at(1)) + ->method('languageExists') + ->with('dav', 'de') + ->willReturn(true); + + $this->l10nFactory->expects($this->at(2)) + ->method('get') + ->with('dav', 'de') + ->willReturn($enL10N); + + $this->l10nFactory->expects($this->at(3)) + ->method('languageExists') + ->with('dav', 'en') + ->willReturn(true); + + $this->l10nFactory->expects($this->at(4)) + ->method('get') + ->with('dav', 'en') + ->willReturn($deL10N); + + $template1 = $this->getTemplateMock(); + $message11 = $this->getMessageMock('foo1@example.org', $template1); + $message12 = $this->getMessageMock('uid2@example.com', $template1); + $message13 = $this->getMessageMock('uid3@example.com', $template1); + $template2 = $this->getTemplateMock(); + $message21 = $this->getMessageMock('foo3@example.org', $template2); + $message22 = $this->getMessageMock('foo4@example.org', $template2); + $message23 = $this->getMessageMock('uid1@example.com', $template2); + + $this->mailer->expects($this->at(0)) + ->method('createEMailTemplate') + ->with('dav.calendarReminder') + ->willReturn($template1); + + $this->mailer->expects($this->at(1)) + ->method('createMessage') + ->with() + ->willReturn($message11); + $this->mailer->expects($this->at(2)) + ->method('send') + ->with($message11) + ->willReturn([]); + $this->mailer->expects($this->at(3)) + ->method('createMessage') + ->with() + ->willReturn($message12); + $this->mailer->expects($this->at(4)) + ->method('send') + ->with($message12) + ->willReturn([]); + $this->mailer->expects($this->at(5)) + ->method('createMessage') + ->with() + ->willReturn($message13); + $this->mailer->expects($this->at(6)) + ->method('send') + ->with($message13) + ->willReturn([]); + + $this->mailer->expects($this->at(7)) + ->method('createEMailTemplate') + ->with('dav.calendarReminder') + ->willReturn($template2); + + $this->mailer->expects($this->at(8)) + ->method('createMessage') + ->with() + ->willReturn($message21); + $this->mailer->expects($this->at(9)) + ->method('send') + ->with($message21) + ->willReturn([]); + $this->mailer->expects($this->at(10)) + ->method('createMessage') + ->with() + ->willReturn($message22); + $this->mailer->expects($this->at(11)) + ->method('send') + ->with($message22) + ->willReturn([]); + $this->mailer->expects($this->at(12)) + ->method('createMessage') + ->with() + ->willReturn($message23); + $this->mailer->expects($this->at(13)) + ->method('send') + ->with($message23) + ->willReturn([]); + + $this->setupURLGeneratorMock(2); + + $vcalendar = $this->getAttendeeVCalendar(); + $this->provider->send($vcalendar->VEVENT, $this->calendarDisplayName, $users); + } + + /** + * @return IEMailTemplate + */ + private function getTemplateMock():IEMailTemplate { + $template = $this->createMock(IEMailTemplate::class); + + $template->expects($this->at(0)) + ->method('addHeader') + ->with() + ->willReturn($template); + + $template->expects($this->at(1)) ->method('setSubject') - ->with('Notification: Fellowship meeting'); + ->with() + ->willReturn($template); - $emailTemplate->expects($this->once()) - ->method('addHeader'); + $template->expects($this->at(2)) + ->method('addHeading') + ->with() + ->willReturn($template); - $emailTemplate->expects($this->once()) - ->method('addHeading'); + $template->expects($this->at(3)) + ->method('addBodyListItem') + ->with() + ->willReturn($template); - $emailTemplate->expects($this->exactly(2)) - ->method('addBodyListItem'); + $template->expects($this->at(4)) + ->method('addBodyListItem') + ->with() + ->willReturn($template); - $emailTemplate->expects($this->once()) - ->method('addFooter'); + $template->expects($this->at(5)) + ->method('addBodyListItem') + ->with() + ->willReturn($template); - $mailMessage->expects($this->once()) - ->method('useTemplate') - ->with($emailTemplate); + $template->expects($this->at(6)) + ->method('addBodyListItem') + ->with() + ->willReturn($template); - $this->mailer - ->expects($this->once()) - ->method('createMessage') - ->willReturn($mailMessage); + $template->expects($this->at(7)) + ->method('addFooter') + ->with() + ->willReturn($template); - $emailAttachment = $this->createMock(IAttachment::class); - $this->mailer - ->expects($this->once()) - ->method('createAttachment') - ->willReturn($emailAttachment); + return $template; + } - $this->mailer - ->expects($this->once()) - ->method('send'); + /** + * @param array $toMail + * @param IEMailTemplate $templateMock + * @param array $replyTo + * @return IMessage + */ + private function getMessageMock(string $toMail, IEMailTemplate $templateMock, array $replyTo=null):IMessage { + $message = $this->createMock(IMessage::class); + $i = 0; - $l10n = $this->createMock(IL10N::class); - $this->l10nFactory - ->method('get') - ->willReturn($l10n); + $message->expects($this->at($i++)) + ->method('setFrom') + ->with([\OCP\Util::getDefaultEmailAddress('reminders-noreply')]) + ->willReturn($message); - $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); - } + if ($replyTo) { + $message->expects($this->at($i++)) + ->method('setReplyTo') + ->with($replyTo) + ->willReturn($message); + } + + $message->expects($this->at($i++)) + ->method('setTo') + ->with([$toMail]) + ->willReturn($message); + + $message->expects($this->at($i++)) + ->method('useTemplate') + ->with($templateMock) + ->willReturn($message); + + return $message; + } + + private function getNoAttendeeVCalendar():VCalendar { + $vcalendar = new VCalendar(); + $vcalendar->add('VEVENT', [ + 'SUMMARY' => 'Fellowship meeting', + 'DTSTART' => new \DateTime('2017-01-01 00:00:00+00:00'), // 1483228800, + 'UID' => 'uid1234', + 'LOCATION' => 'Location 123', + 'DESCRIPTION' => 'DESCRIPTION 456', + ]); + + return $vcalendar; + } + + private function getAttendeeVCalendar():VCalendar { + $vcalendar = new VCalendar(); + $vcalendar->add('VEVENT', [ + 'SUMMARY' => 'Fellowship meeting', + 'DTSTART' => new \DateTime('2017-01-01 00:00:00+00:00'), // 1483228800, + 'UID' => 'uid1234', + 'LOCATION' => 'Location 123', + 'DESCRIPTION' => 'DESCRIPTION 456', + ]); + + $vcalendar->VEVENT->add( + 'ATTENDEE', + 'mailto:foo1@example.org', + [ + 'LANG' => 'de', + 'PARTSTAT' => 'NEEDS-ACTION', + ] + ); + + $vcalendar->VEVENT->add( + 'ATTENDEE', + 'mailto:foo2@example.org', + [ + 'LANG' => 'de', + 'PARTSTAT' => 'DECLINED', + ] + ); + + $vcalendar->VEVENT->add( + 'ATTENDEE', + 'mailto:foo3@example.org', + [ + 'LANG' => 'en', + 'PARTSTAT' => 'CONFIRMED', + ] + ); + + $vcalendar->VEVENT->add( + 'ATTENDEE', + 'mailto:foo4@example.org' + ); + + $vcalendar->VEVENT->add( + 'ATTENDEE', + 'tomail:foo5@example.org' + ); + + return $vcalendar; + } + + private function setupURLGeneratorMock(int $times=1):void { + for ($i = 0; $i < $times; $i++) { + $this->urlGenerator + ->expects($this->at(8 * $i)) + ->method('imagePath') + ->with('core', 'actions/info.svg') + ->willReturn('imagePath1'); + + $this->urlGenerator + ->expects($this->at(8 * $i + 1)) + ->method('getAbsoluteURL') + ->with('imagePath1') + ->willReturn('AbsURL1'); + + $this->urlGenerator + ->expects($this->at(8 * $i + 2)) + ->method('imagePath') + ->with('core', 'places/calendar.svg') + ->willReturn('imagePath2'); + + $this->urlGenerator + ->expects($this->at(8 * $i + 3)) + ->method('getAbsoluteURL') + ->with('imagePath2') + ->willReturn('AbsURL2'); + + $this->urlGenerator + ->expects($this->at(8 * $i + 4)) + ->method('imagePath') + ->with('core', 'actions/address.svg') + ->willReturn('imagePath3'); + + $this->urlGenerator + ->expects($this->at(8 * $i + 5)) + ->method('getAbsoluteURL') + ->with('imagePath3') + ->willReturn('AbsURL3'); + + $this->urlGenerator + ->expects($this->at(8 * $i + 6)) + ->method('imagePath') + ->with('core', 'actions/more.svg') + ->willReturn('imagePath4'); + + $this->urlGenerator + ->expects($this->at(8 * $i + 7)) + ->method('getAbsoluteURL') + ->with('imagePath4') + ->willReturn('AbsURL4'); + } + } } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php index e10afb44d2..bbf71837b0 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php @@ -1,8 +1,11 @@ + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -23,7 +26,6 @@ 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; @@ -34,7 +36,6 @@ 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 { @@ -75,65 +76,111 @@ class PushProviderTest extends AbstractNotificationProviderTest { ); } - public function testSend(): void - { - $notification = $this->createMock(INotification::class); - $notification - ->expects($this->once()) - ->method('setApp') - ->with(Application::APP_ID) - ->willReturn($notification); + public function testNotificationType():void { + $this->assertEquals(PushProvider::NOTIFICATION_TYPE, 'DISPLAY'); + } - $notification - ->expects($this->once()) - ->method('setUser') - ->willReturn($notification) - ; + public function testSend(): void { + $user1 = $this->createMock(IUser::class); + $user1->method('getUID') + ->willReturn('uid1'); + $user2 = $this->createMock(IUser::class); + $user2->method('getUID') + ->willReturn('uid2'); + $user3 = $this->createMock(IUser::class); + $user3->method('getUID') + ->willReturn('uid3'); - $notification - ->expects($this->once()) - ->method('setDateTime') - ->willReturn($notification) - ; + $users = [$user1, $user2, $user3]; - $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') + $dateTime = new \DateTime('@946684800'); + $this->timeFactory->method('getDateTime') ->with() - ->willReturn(new \DateTime()); + ->willReturn($dateTime); - $this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user); + $notification1 = $this->createNotificationMock('uid1', $dateTime); + $notification2 = $this->createNotificationMock('uid2', $dateTime); + $notification3 = $this->createNotificationMock('uid3', $dateTime); + + $this->manager->expects($this->at(0)) + ->method('createNotification') + ->with() + ->willReturn($notification1); + $this->manager->expects($this->at(2)) + ->method('createNotification') + ->with() + ->willReturn($notification2); + $this->manager->expects($this->at(4)) + ->method('createNotification') + ->with() + ->willReturn($notification3); + + $this->manager->expects($this->at(1)) + ->method('notify') + ->with($notification1); + $this->manager->expects($this->at(3)) + ->method('notify') + ->with($notification2); + $this->manager->expects($this->at(5)) + ->method('notify') + ->with($notification3); + + $this->provider->send($this->vcalendar->VEVENT, $this->calendarDisplayName, $users); } + + /** + * @param string $uid + * @param \DateTime $dt + */ + private function createNotificationMock(string $uid, \DateTime $dt):INotification { + $notification = $this->createMock(INotification::class); + $notification + ->expects($this->once()) + ->method('setApp') + ->with('dav') + ->willReturn($notification); + + $notification->expects($this->once()) + ->method('setUser') + ->with($uid) + ->willReturn($notification); + + $notification->expects($this->once()) + ->method('setDateTime') + ->with($dt) + ->willReturn($notification); + + $notification->expects($this->once()) + ->method('setObject') + ->with('dav', 'uid1234') + ->willReturn($notification); + + $notification->expects($this->once()) + ->method('setSubject') + ->with('calendar_reminder', [ + 'title' => 'Fellowship meeting', + 'start_atom' => '2017-01-01T00:00:00+00:00', + ]) + ->willReturn($notification); + + $notification + ->expects($this->once()) + ->method('setMessage') + ->with('calendar_reminder', [ + 'title' => 'Fellowship meeting', + 'start_atom' => '2017-01-01T00:00:00+00:00', + 'description' => null, + 'location' => null, + 'all_day' => false, + 'start_is_floating' => false, + 'start_timezone' => 'UTC', + 'end_atom' => '2017-01-01T00:00:00+00:00', + 'end_is_floating' => false, + 'end_timezone' => 'UTC', + 'calendar_displayname' => 'Personal', + ]) + ->willReturn($notification); + + return $notification; + } } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php index d962b631c1..b4c62eacd7 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php @@ -1,8 +1,11 @@ + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -31,8 +34,8 @@ use Test\TestCase; class NotificationProviderManagerTest extends TestCase { - /** @var NotificationProviderManager|\PHPUnit\Framework\MockObject\MockObject */ - private $providerManager; + /** @var NotificationProviderManager|\PHPUnit\Framework\MockObject\MockObject */ + private $providerManager; /** * @throws \OCP\AppFramework\QueryException @@ -40,9 +43,9 @@ class NotificationProviderManagerTest extends TestCase { public function setUp() { parent::setUp(); - $this->providerManager = new NotificationProviderManager(); - $this->providerManager->registerProvider(EmailProvider::class); - } + $this->providerManager = new NotificationProviderManager(); + $this->providerManager->registerProvider(EmailProvider::class); + } /** * @expectedException OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException @@ -50,10 +53,9 @@ class NotificationProviderManagerTest extends TestCase { * @throws ProviderNotAvailableException * @throws NotificationTypeDoesNotExistException */ - public function testGetProviderForUnknownType(): void - { - $this->providerManager->getProvider('NOT EXISTENT'); - } + public function testGetProviderForUnknownType(): void{ + $this->providerManager->getProvider('NOT EXISTENT'); + } /** * @expectedException OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException @@ -61,28 +63,16 @@ class NotificationProviderManagerTest extends TestCase { * @throws NotificationTypeDoesNotExistException * @throws ProviderNotAvailableException */ - public function testGetProviderForUnRegisteredType(): void - { - $this->providerManager->getProvider('AUDIO'); - } + 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); - } + 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 - { + public function testRegisterProvider(): void{ $this->providerManager->registerProvider(PushProvider::class); $provider = $this->providerManager->getProvider('DISPLAY'); $this->assertInstanceOf(PushProvider::class, $provider); @@ -93,8 +83,12 @@ class NotificationProviderManagerTest extends TestCase { * @expectedException \InvalidArgumentException * @throws \OCP\AppFramework\QueryException */ - public function testRegisterBadProvider(): void - { + public function testRegisterBadProvider(): void{ $this->providerManager->registerProvider(Capabilities::class); } + + public function testHasProvider(): void { + $this->assertTrue($this->providerManager->hasProvider('EMAIL')); + $this->assertFalse($this->providerManager->hasProvider('EMAIL123')); + } } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php index b9695b33fd..8d38617ad2 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php @@ -1,8 +1,11 @@ + * @copyright Copyright (c) 2019, Thomas Citharel + * @copyright Copyright (c) 2019, Georg Ehrke * * @author Thomas Citharel + * @author Georg Ehrke * * @license AGPL-3.0 * @@ -24,6 +27,7 @@ namespace OCA\DAV\Tests\unit\CalDAV\Reminder; use OCA\DAV\AppInfo\Application; use OCA\DAV\CalDAV\Reminder\Notifier; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\IL10N; use OCP\IURLGenerator; use OCP\L10N\IFactory; @@ -36,22 +40,33 @@ class NotifierTest extends TestCase { /** @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 $l10n; + + /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ + protected $timeFactory; protected function setUp() { parent::setUp(); $this->urlGenerator = $this->createMock(IURLGenerator::class); - $this->l = $this->createMock(IL10N::class); - $this->l->expects($this->any()) + $this->l10n = $this->createMock(IL10N::class); + $this->l10n->expects($this->any()) ->method('t') ->willReturnCallback(function($string, $args) { return vsprintf($string, $args); }); - $this->l->expects($this->any()) + $this->l10n->expects($this->any()) + ->method('l') + ->willReturnCallback(function($string, $args) { + /** \DateTime $args */ + return $args->format(\DateTime::ATOM); + }); + $this->l10n->expects($this->any()) ->method('n') ->willReturnCallback(function($textSingular, $textPlural, $count, $args) { $text = $count === 1 ? $textSingular : $textPlural; @@ -61,14 +76,28 @@ class NotifierTest extends TestCase { $this->factory = $this->createMock(IFactory::class); $this->factory->expects($this->any()) ->method('get') - ->willReturn($this->l); + ->willReturn($this->l10n); + + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->timeFactory + ->method('getDateTime') + ->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2005-08-15T14:00:00+02:00')); $this->notifier = new Notifier( $this->factory, - $this->urlGenerator + $this->urlGenerator, + $this->timeFactory ); } + public function testGetId():void { + $this->assertEquals($this->notifier->getID(), 'dav'); + } + + public function testGetName():void { + $this->assertEquals($this->notifier->getName(), 'Calendar'); + } + /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Notification not from this app @@ -111,17 +140,24 @@ class NotifierTest extends TestCase { [ 'calendar_reminder', [ - 'title' => 'foo', - 'start' => time() - 60 * 60 * 24 + 'title' => 'Title of this event', + 'start_atom' => '2005-08-15T15:52:01+02:00' ], - 'foo (one day ago)', + 'Title of this event (in 1 hour, 52 minutes)', [ - 'when' => 'foo', - 'description' => 'bar', + 'title' => 'Title of this event', + 'description' => null, 'location' => 'NC Headquarters', - 'calendar' => 'Personal' + 'all_day' => false, + 'start_atom' => '2005-08-15T15:52:01+02:00', + 'start_is_floating' => false, + 'start_timezone' => 'Europe/Berlin', + 'end_atom' => '2005-08-15T17:52:01+02:00', + 'end_is_floating' => false, + 'end_timezone' => 'Europe/Berlin', + 'calendar_displayname' => 'Personal', ], - 'Calendar: Personal
Date: foo
Description: bar
Where: NC Headquarters' + "Calendar: Personal\r\nDate: 2005-08-15T15:52:01+02:00, 2005-08-15T15:52:01+02:00 - 2005-08-15T17:52:01+02:00 (Europe/Berlin)\r\nWhere: NC Headquarters" ], ]; } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php index 061c3f16e3..a9acca66a8 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php @@ -1,4 +1,5 @@ notificationProviderManager = $this->createMock(NotificationProviderManager::class); $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); - $this->userSession = $this->createMock(IUserSession::class); + $this->caldavBackend = $this->createMock(CalDavBackend::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + + $this->caldavBackend->method('getShares')->willReturn([]); + + $this->reminderService = new ReminderService($this->backend, + $this->notificationProviderManager, + $this->userManager, + $this->groupManager, + $this->caldavBackend, + $this->timeFactory); } - 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), - ] - ]; - } + public function testOnCalendarObjectDelete():void { + $this->backend->expects($this->once()) + ->method('cleanRemindersForEvent') + ->with(44); - /** - * @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 - ] + $action = '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject'; + $objectData = [ + 'id' => '44', + 'component' => 'vevent', ]; + + $this->reminderService->onTouchCalendarObject($action, $objectData); } - /** - * @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']); + public function testOnCalendarObjectCreateSingleEntry():void { + $action = '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'; + $objectData = [ + 'calendardata' => self::CALENDAR_DATA, + 'id' => '42', + 'calendarid' => '1337', + 'component' => 'vevent', + ]; - if ($action !== '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') { - $user = $this->createMock(IUser::class); - $user->expects($this->once())->method('getUID')->willReturn('user'); + $this->backend->expects($this->exactly(2)) + ->method('insertReminder') + ->withConsecutive( + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1465429500, false], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', '35b3eae8e792aa2209f0b4e1a302f105', 'DISPLAY', false, 1465344000, false] + ) + ->willReturn(1); - $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); + $this->timeFactory->expects($this->once()) + ->method('getDateTime') + ->with() + ->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2016-06-08T00:00:00+00:00')); + + $this->reminderService->onTouchCalendarObject($action, $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); + public function testOnCalendarObjectCreateSingleEntryWithRepeat(): void { + $action = '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'; + $objectData = [ + 'calendardata' => self::CALENDAR_DATA_REPEAT, + 'id' => '42', + 'calendarid' => '1337', + 'component' => 'vevent', + ]; - $reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession); - $reminderService->onTouchCalendarObject('', ['principaluri' => 'foo'], [], ['calendarid' => 1, 'uri' => 'bar']); + $this->backend->expects($this->exactly(5)) + ->method('insertReminder') + ->withConsecutive( + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429500, false], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429620, true], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429740, true], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429860, true], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429980, true] + ) + ->willReturn(1); + + $this->timeFactory->expects($this->once()) + ->method('getDateTime') + ->with() + ->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2016-06-08T00:00:00+00:00')); + + $this->reminderService->onTouchCalendarObject($action, $objectData); } - public function testOnTouchCalendarObjectWithNoCalendarURI(): void - { - $reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession); - $this->assertNull($reminderService->onTouchCalendarObject('', [], [], [])); + public function testOnCalendarObjectCreateRecurringEntry(): void { + $action = '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'; + $objectData = [ + 'calendardata' => self::CALENDAR_DATA_RECURRING, + 'id' => '42', + 'calendarid' => '1337', + 'component' => 'vevent', + ]; + + $this->backend->expects($this->exactly(2)) + ->method('insertReminder') + ->withConsecutive( + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1467243900, false], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false] + ) + ->willReturn(1); + + $this->timeFactory->expects($this->once()) + ->method('getDateTime') + ->with() + ->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2016-06-29T00:00:00+00:00')); + + $this->reminderService->onTouchCalendarObject($action, $objectData); + } + + public function testOnCalendarObjectCreateRecurringEntryWithRepeat():void { + $action = '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject'; + $objectData = [ + 'calendardata' => self::CALENDAR_DATA_RECURRING_REPEAT, + 'id' => '42', + 'calendarid' => '1337', + 'component' => 'vevent', + ]; + + $this->backend->expects($this->exactly(6)) + ->method('insertReminder') + ->withConsecutive( + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467243900, false], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244020, true], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244140, true], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244260, true], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244380, true], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false] + ) + ->willReturn(1); + + $this->timeFactory->expects($this->once()) + ->method('getDateTime') + ->with() + ->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2016-06-29T00:00:00+00:00')); + + $this->reminderService->onTouchCalendarObject($action, $objectData); + } + + public function testProcessReminders():void { + $this->backend->expects($this->at(0)) + ->method('getRemindersToProcess') + ->with() + ->willReturn([ + [ + 'id' => 1, + 'calendar_id' => 1337, + 'object_id' => 42, + 'uid' => 'wej2z68l9h', + 'is_recurring' => false, + 'recurrence_id' => 1465430400, + 'is_recurrence_exception' => false, + 'event_hash' => '5c70531aab15c92b52518ae10a2f78a4', + 'alarm_hash' => 'de919af7429d3b5c11e8b9d289b411a6', + 'type' => 'EMAIL', + 'is_relative' => true, + 'notification_date' => 1465429500, + 'is_repeat_based' => false, + 'calendardata' => self::CALENDAR_DATA, + 'displayname' => 'Displayname 123', + 'principaluri' => 'principals/users/user001', + ], + [ + 'id' => 2, + 'calendar_id' => 1337, + 'object_id' => 42, + 'uid' => 'wej2z68l9h', + 'is_recurring' => false, + 'recurrence_id' => 1465430400, + 'is_recurrence_exception' => false, + 'event_hash' => '5c70531aab15c92b52518ae10a2f78a4', + 'alarm_hash' => 'ecacbf07d413c3c78d1ac7ad8c469602', + 'type' => 'EMAIL', + 'is_relative' => true, + 'notification_date' => 1465429740, + 'is_repeat_based' => true, + 'calendardata' => self::CALENDAR_DATA_REPEAT, + 'displayname' => 'Displayname 123', + 'principaluri' => 'principals/users/user001', + ], + [ + 'id' => 3, + 'calendar_id' => 1337, + 'object_id' => 42, + 'uid' => 'wej2z68l9h', + 'is_recurring' => false, + 'recurrence_id' => 1465430400, + 'is_recurrence_exception' => false, + 'event_hash' => '5c70531aab15c92b52518ae10a2f78a4', + 'alarm_hash' => '35b3eae8e792aa2209f0b4e1a302f105', + 'type' => 'DISPLAY', + 'is_relative' => false, + 'notification_date' => 1465344000, + 'is_repeat_based' => false, + 'calendardata' => self::CALENDAR_DATA, + 'displayname' => 'Displayname 123', + 'principaluri' => 'principals/users/user001', + ], + [ + 'id' => 4, + 'calendar_id' => 1337, + 'object_id' => 42, + 'uid' => 'wej2z68l9h', + 'is_recurring' => true, + 'recurrence_id' => 1467244800, + 'is_recurrence_exception' => false, + 'event_hash' => 'fbdb2726bc0f7dfacac1d881c1453e20', + 'alarm_hash' => 'ecacbf07d413c3c78d1ac7ad8c469602', + 'type' => 'EMAIL', + 'is_relative' => true, + 'notification_date' => 1467243900, + 'is_repeat_based' => false, + 'calendardata' => self::CALENDAR_DATA_RECURRING_REPEAT, + 'displayname' => 'Displayname 123', + 'principaluri' => 'principals/users/user001', + ], + [ + 'id' => 5, + 'calendar_id' => 1337, + 'object_id' => 42, + 'uid' => 'wej2z68l9h', + 'is_recurring' => true, + 'recurrence_id' => 1467849600, + 'is_recurrence_exception' => false, + 'event_hash' => 'fbdb2726bc0f7dfacac1d881c1453e20', + 'alarm_hash' => '8996992118817f9f311ac5cc56d1cc97', + 'type' => 'EMAIL', + 'is_relative' => true, + 'notification_date' => 1467158400, + 'is_repeat_based' => false, + 'calendardata' => self::CALENDAR_DATA_RECURRING, + 'displayname' => 'Displayname 123', + 'principaluri' => 'principals/users/user001', + ] + ]); + + $this->notificationProviderManager->expects($this->at(0)) + ->method('hasProvider') + ->with('EMAIL') + ->willReturn(true); + + $provider1 = $this->createMock(INotificationProvider::class); + $this->notificationProviderManager->expects($this->at(1)) + ->method('getProvider') + ->with('EMAIL') + ->willReturn($provider1); + + $this->notificationProviderManager->expects($this->at(2)) + ->method('hasProvider') + ->with('EMAIL') + ->willReturn(true); + + $provider2 = $this->createMock(INotificationProvider::class); + $this->notificationProviderManager->expects($this->at(3)) + ->method('getProvider') + ->with('EMAIL') + ->willReturn($provider2); + + $this->notificationProviderManager->expects($this->at(4)) + ->method('hasProvider') + ->with('DISPLAY') + ->willReturn(true); + + $provider3 = $this->createMock(INotificationProvider::class); + $this->notificationProviderManager->expects($this->at(5)) + ->method('getProvider') + ->with('DISPLAY') + ->willReturn($provider3); + + $this->notificationProviderManager->expects($this->at(6)) + ->method('hasProvider') + ->with('EMAIL') + ->willReturn(true); + + $provider4 = $this->createMock(INotificationProvider::class); + $this->notificationProviderManager->expects($this->at(7)) + ->method('getProvider') + ->with('EMAIL') + ->willReturn($provider4); + + $this->notificationProviderManager->expects($this->at(8)) + ->method('hasProvider') + ->with('EMAIL') + ->willReturn(true); + + $provider5 = $this->createMock(INotificationProvider::class); + $this->notificationProviderManager->expects($this->at(9)) + ->method('getProvider') + ->with('EMAIL') + ->willReturn($provider5); + + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->exactly(5)) + ->method('get') + ->with('user001') + ->willReturn($user); + + $provider1->expects($this->once()) + ->method('send') + ->with($this->callback(function($vevent) { + if ($vevent->DTSTART->getDateTime()->format(\DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') { + return false; + } + return true; + }, 'Displayname 123', $user)); + $provider2->expects($this->once()) + ->method('send') + ->with($this->callback(function($vevent) { + if ($vevent->DTSTART->getDateTime()->format(\DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') { + return false; + } + return true; + }, 'Displayname 123', $user)); + $provider3->expects($this->once()) + ->method('send') + ->with($this->callback(function($vevent) { + if ($vevent->DTSTART->getDateTime()->format(\DateTime::ATOM) !== '2016-06-09T00:00:00+00:00') { + return false; + } + return true; + }, 'Displayname 123', $user)); + $provider4->expects($this->once()) + ->method('send') + ->with($this->callback(function($vevent) { + if ($vevent->DTSTART->getDateTime()->format(\DateTime::ATOM) !== '2016-06-30T00:00:00+00:00') { + return false; + } + return true; + }, 'Displayname 123', $user)); + $provider5->expects($this->once()) + ->method('send') + ->with($this->callback(function($vevent) { + if ($vevent->DTSTART->getDateTime()->format(\DateTime::ATOM) !== '2016-07-07T00:00:00+00:00') { + return false; + } + return true; + }, 'Displayname 123', $user)); + + $this->backend->expects($this->at(1)) + ->method('removeReminder') + ->with(1); + $this->backend->expects($this->at(2)) + ->method('removeReminder') + ->with(2); + $this->backend->expects($this->at(3)) + ->method('removeReminder') + ->with(3); + $this->backend->expects($this->at(4)) + ->method('removeReminder') + ->with(4); + $this->backend->expects($this->at(5)) + ->method('insertReminder') + ->with(1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848700, false) + ->willReturn(99); + + $this->backend->expects($this->at(6)) + ->method('insertReminder') + ->with(1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848820, true) + ->willReturn(99); + $this->backend->expects($this->at(7)) + ->method('insertReminder') + ->with(1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848940, true) + ->willReturn(99); + $this->backend->expects($this->at(8)) + ->method('insertReminder') + ->with(1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849060, true) + ->willReturn(99); + $this->backend->expects($this->at(9)) + ->method('insertReminder') + ->with(1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849180, true) + ->willReturn(99); + $this->backend->expects($this->at(10)) + ->method('removeReminder') + ->with(5); + $this->backend->expects($this->at(11)) + ->method('insertReminder') + ->with(1337, 42, 'wej2z68l9h', true, 1468454400, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467763200, false) + ->willReturn(99); + + $this->timeFactory->method('getDateTime') + ->willReturn(\DateTime::createFromFormat(\DateTime::ATOM, '2016-06-08T00:00:00+00:00')); + + $this->reminderService->processReminders(); } }