Support event reminders (email and notifications)

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
pull/3044/head
Thomas Citharel 5 years ago committed by Roeland Jago Douma
parent f452e23a7d
commit 7bddcc091d
No known key found for this signature in database
GPG Key ID: F941078878347C0C

@ -108,3 +108,6 @@ $calendarManager->register(function() use ($calendarManager, $app) {
$app->setupCalendarProvider($calendarManager, $user->getUID());
}
});
$app->registerNotifier();
$app->registerCalendarReminders();

@ -23,6 +23,7 @@
<job>OCA\DAV\BackgroundJob\CleanupDirectLinksJob</job>
<job>OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob</job>
<job>OCA\DAV\BackgroundJob\CleanupInvitationTokenJob</job>
<job>OCA\DAV\BackgroundJob\EventReminderJob</job>
</background-jobs>
<repair-steps>

@ -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',

@ -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',

@ -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');
});

@ -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);
}
}
}

@ -0,0 +1,59 @@
<?php
/**
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\BackgroundJob;
use OC\BackgroundJob\TimedJob;
use OCA\DAV\CalDAV\Reminder\ReminderService;
use OCP\IConfig;
class EventReminderJob extends TimedJob {
/** @var ReminderService */
private $reminderService;
/** @var IConfig */
private $config;
/**
* EventReminderJob constructor.
*
* @param ReminderService $reminderService
* @param IConfig $config
*/
public function __construct(ReminderService $reminderService, IConfig $config) {
$this->reminderService = $reminderService;
$this->config = $config;
/** Run every 5 minutes */
$this->setInterval(5);
}
/**
* @param $arg
* @throws \OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException
* @throws \OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException
* @throws \OC\User\NoUserException
*/
public function run($arg): void
{
if ($this->config->getAppValue('dav', 'sendEventReminders', 'yes') === 'yes') {
$this->reminderService->processReminders();
}
}
}

@ -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))

@ -0,0 +1,208 @@
<?php
/**
* @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\Reminder;
use \DateTime;
use \DateTimeImmutable;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IURLGenerator;
use OCP\L10N\IFactory as L10NFactory;
use OCP\IUser;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Parameter;
use Sabre\VObject\Property;
abstract class AbstractNotificationProvider
{
public const NOTIFICATION_TYPE = '';
/** @var ILogger */
protected $logger;
/** @var L10NFactory */
protected $l10nFactory;
/** @var IL10N */
protected $l10n;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var IConfig */
protected $config;
/**
* @param ILogger $logger
* @param L10NFactory $l10nFactory
* @param IConfig $config
* @param IUrlGenerator $urlGenerator
*/
public function __construct(ILogger $logger, L10NFactory $l10nFactory, IURLGenerator $urlGenerator, IConfig $config) {
$this->logger = $logger;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
$this->config = $config;
}
/**
* Send notification
*
* @param VCalendar $vcalendar
* @param string $calendarDisplayName
* @param IUser $user
* @return void
*/
public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void {}
/**
* @var VCalendar $vcalendar
* @var string $defaultValue
* @return array
* @throws \Exception
*/
protected function extractEventDetails(VCalendar $vcalendar, $defaultValue = ''): array
{
/** @var VEvent $vevent */
$vevent = $vcalendar->VEVENT;
/** @var Property $start */
$start = $vevent->DTSTART;
if (isset($vevent->DTEND)) {
$end = $vevent->DTEND;
} elseif (isset($vevent->DURATION)) {
$isFloating = $vevent->DTSTART->isFloating();
$end = clone $vevent->DTSTART;
$endDateTime = $end->getDateTime();
$endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
$end->setDateTime($endDateTime, $isFloating);
} elseif (!$vevent->DTSTART->hasTime()) {
$isFloating = $vevent->DTSTART->isFloating();
$end = clone $vevent->DTSTART;
$endDateTime = $end->getDateTime();
$endDateTime = $endDateTime->modify('+1 day');
$end->setDateTime($endDateTime, $isFloating);
} else {
$end = clone $vevent->DTSTART;
}
return [
'title' => (string) $vevent->SUMMARY ?: $defaultValue,
'description' => (string) $vevent->DESCRIPTION ?: $defaultValue,
'start'=> $start->getDateTime(),
'end' => $end->getDateTime(),
'when' => $this->generateWhenString($start, $end),
'url' => (string) $vevent->URL ?: $defaultValue,
'location' => (string) $vevent->LOCATION ?: $defaultValue,
'uid' => (string) $vevent->UID,
];
}
/**
* @param Property $dtstart
* @param Property $dtend
* @return string
* @throws \Exception
*/
private function generateWhenString(Property $dtstart, Property $dtend): string
{
$isAllDay = $dtstart instanceof Property\ICalendar\Date;
/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
/** @var DateTimeImmutable $dtstartDt */
$dtstartDt = $dtstart->getDateTime();
/** @var DateTimeImmutable $dtendDt */
$dtendDt = $dtend->getDateTime();
$diff = $dtstartDt->diff($dtendDt);
$dtstartDt = new DateTime($dtstartDt->format(DateTime::ATOM));
$dtendDt = new DateTime($dtendDt->format(DateTime::ATOM));
if ($isAllDay) {
// One day event
if ($diff->days === 1) {
return $this->l10n->l('date', $dtstartDt, ['width' => 'medium']);
}
//event that spans over multiple days
$localeStart = $this->l10n->l('date', $dtstartDt, ['width' => 'medium']);
$localeEnd = $this->l10n->l('date', $dtendDt, ['width' => 'medium']);
return $localeStart . ' - ' . $localeEnd;
}
/** @var Property\ICalendar\DateTime $dtstart */
/** @var Property\ICalendar\DateTime $dtend */
$isFloating = $dtstart->isFloating();
$startTimezone = $endTimezone = null;
if (!$isFloating) {
$prop = $dtstart->offsetGet('TZID');
if ($prop instanceof Parameter) {
$startTimezone = $prop->getValue();
}
$prop = $dtend->offsetGet('TZID');
if ($prop instanceof Parameter) {
$endTimezone = $prop->getValue();
}
}
$localeStart = $this->l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
$this->l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
// always show full date with timezone if timezones are different
if ($startTimezone !== $endTimezone) {
$localeEnd = $this->l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
return $localeStart . ' (' . $startTimezone . ') - ' .
$localeEnd . ' (' . $endTimezone . ')';
}
// show only end time if date is the same
if ($this->isDayEqual($dtstartDt, $dtendDt)) {
$localeEnd = $this->l10n->l('time', $dtendDt, ['width' => 'short']);
} else {
$localeEnd = $this->l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
$this->l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
}
return $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')';
}
/**
* @param DateTime $dtStart
* @param DateTime $dtEnd
* @return bool
*/
private function isDayEqual(DateTime $dtStart, DateTime $dtEnd): bool
{
return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
}
}

@ -0,0 +1,139 @@
<?php
/**
* @copyright Copyright (c) 2019 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\Reminder;
use OCP\IDBConnection;
use OCP\AppFramework\Utility\ITimeFactory;
/**
* Class Backend
*
* @package OCA\DAV\CalDAV\Reminder
*/
class Backend {
/** @var IDBConnection */
protected $db;
/** @var ITimeFactory */
private $timeFactory;
/**
* @param IDBConnection $db
* @param ITimeFactory $timeFactory
*/
public function __construct(IDBConnection $db, ITimeFactory $timeFactory) {
$this->db = $db;
$this->timeFactory = $timeFactory;
}
/**
* @param string $uid
* @param string $calendarId
* @param string $uri
* @param string $type
* @param int $notificationDate
* @param int $eventStartDate
*/
public function insertReminder(string $uid, string $calendarId, string $uri, string $type, int $notificationDate, int $eventStartDate): void
{
$query = $this->db->getQueryBuilder();
$query->insert('calendar_reminders')
->values([
'uid' => $query->createNamedParameter($uid),
'calendarid' => $query->createNamedParameter($calendarId),
'objecturi' => $query->createNamedParameter($uri),
'type' => $query->createNamedParameter($type),
'notificationdate' => $query->createNamedParameter($notificationDate),
'eventstartdate' => $query->createNamedParameter($eventStartDate),
])->execute();
}
/**
* Cleans reminders in database
*
* @param int $calendarId
* @param string $objectUri
*/
public function cleanRemindersForEvent(int $calendarId, string $objectUri): void
{
$query = $this->db->getQueryBuilder();
$query->delete('calendar_reminders')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->eq('objecturi', $query->createNamedParameter($objectUri)))
->execute();
}
/**
* Remove all reminders for a calendar
*
* @param integer $calendarId
* @return void
*/
public function cleanRemindersForCalendar(int $calendarId): void
{
$query = $this->db->getQueryBuilder();
$query->delete('calendar_reminders')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
->execute();
}
/**
* Remove a reminder by it's id
*
* @param integer $reminderId
* @return void
*/
public function removeReminder(int $reminderId): void
{
$query = $this->db->getQueryBuilder();
$query->delete('calendar_reminders')
->where($query->expr()->eq('id', $query->createNamedParameter($reminderId)))
->execute();
}
/**
* Get all reminders with a notification date before now
*
* @return array
* @throws \Exception
*/
public function getRemindersToProcess(): array
{
$query = $this->db->getQueryBuilder();
$fields = ['cr.id', 'cr.calendarid', 'cr.objecturi', 'cr.type', 'cr.notificationdate', 'cr.uid', 'co.calendardata', 'c.displayname'];
$stmt = $query->select($fields)
->from('calendar_reminders', 'cr')
->where($query->expr()->lte('cr.notificationdate', $query->createNamedParameter($this->timeFactory->getTime())))
->andWhere($query->expr()->gte('cr.eventstartdate', $query->createNamedParameter($this->timeFactory->getTime()))) # We check that DTSTART isn't before
->leftJoin('cr', 'calendars', 'c', $query->expr()->eq('cr.calendarid', 'c.id'))
->leftJoin('cr', 'calendarobjects', 'co', $query->expr()->andX($query->expr()->eq('cr.calendarid', 'c.id'), $query->expr()->eq('co.uri', 'cr.objecturi')))
->execute();
return $stmt->fetchAll();
}
}

@ -0,0 +1,157 @@
<?php
/**
* @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\Reminder\NotificationProvider;
use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IURLGenerator;
use OCP\L10N\IFactory as L10NFactory;
use OCP\Mail\IEMailTemplate;
use OCP\Mail\IMailer;
use OCP\IUser;
use Sabre\VObject\Component\VCalendar;
class EmailProvider extends AbstractNotificationProvider
{
/** @var IMailer */
private $mailer;
public const NOTIFICATION_TYPE = 'EMAIL';
/**
* @param IConfig $config
* @param IMailer $mailer
* @param ILogger $logger
* @param L10NFactory $l10nFactory
* @param IUrlGenerator $urlGenerator
*/
public function __construct(IConfig $config, IMailer $mailer, ILogger $logger,
L10NFactory $l10nFactory,
IURLGenerator $urlGenerator) {
parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
$this->mailer = $mailer;
}
/**
* Send notification
*
* @param VCalendar $vcalendar
* @param string $calendarDisplayName
* @param IUser $user
* @return void
* @throws \Exception
*/
public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void
{
if ($user->getEMailAddress() === null) {
return;
}
$lang = $this->config->getUserValue($user->getUID(), 'core', 'lang', $this->l10nFactory->findLanguage());
$this->l10n = $this->l10nFactory->get('dav', $lang);
$event = $this->extractEventDetails($vcalendar);
$fromEMail = \OCP\Util::getDefaultEmailAddress('invitations-noreply');
$message = $this->mailer->createMessage()
->setFrom([$fromEMail => 'Nextcloud'])
// TODO: Set reply to from event creator
// ->setReplyTo([$sender => $senderName])
->setTo([$user->getEMailAddress() => $user->getDisplayName()]);
$template = $this->mailer->createEMailTemplate('dav.calendarReminder', $event);
$template->addHeader();
$this->addSubjectAndHeading($template, $event['title']);
$this->addBulletList($template, $event, $calendarDisplayName);
$template->addFooter();
$message->useTemplate($template);
$attachment = $this->mailer->createAttachment(
$vcalendar->serialize(),
$event['uid'].'.ics',// TODO(leon): Make file name unique, e.g. add event id
'text/calendar'
);
$message->attach($attachment);
try {
$failed = $this->mailer->send($message);
if ($failed) {
$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
}
} catch(\Exception $ex) {
$this->logger->logException($ex, ['app' => 'dav']);
}
}
/**
* @param IEMailTemplate $template
* @param string $summary
*/
private function addSubjectAndHeading(IEMailTemplate $template, string $summary): void
{
$template->setSubject('Notification: ' . $summary);
$template->addHeading($summary);
}
/**
* @param IEMailTemplate $template
* @param array $eventData
* @param string $calendarDisplayName
*/
private function addBulletList(IEMailTemplate $template, array $eventData, string $calendarDisplayName): void
{
$template->addBodyListItem($calendarDisplayName, $this->l10n->t('Calendar:'),
$this->getAbsoluteImagePath('actions/info.svg'));
$template->addBodyListItem($eventData['when'], $this->l10n->t('Date:'),
$this->getAbsoluteImagePath('places/calendar.svg'));
if ($eventData['location']) {
$template->addBodyListItem((string) $eventData['location'], $this->l10n->t('Where:'),
$this->getAbsoluteImagePath('actions/address.svg'));
}
if ($eventData['description']) {
$template->addBodyListItem((string) $eventData['description'], $this->l10n->t('Description:'),
$this->getAbsoluteImagePath('actions/more.svg'));
}
if ($eventData['url']) {
$template->addBodyListItem((string) $eventData['url'], $this->l10n->t('Link:'),
$this->getAbsoluteImagePath('places/link.svg'));
}
}
/**
* @param string $path
* @return string
*/
private function getAbsoluteImagePath($path): string
{
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->imagePath('core', $path)
);
}
}

@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\Reminder\NotificationProvider;
class ProviderNotAvailableException extends \Exception {
/**
* ProviderNotAvailableException constructor.
*
* @since 16.0.0
*
* @param string $type ReminderType
*/
public function __construct(string $type) {
parent::__construct("No notification provider for type $type available");
}
}

@ -0,0 +1,101 @@
<?php
/**
* @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\Reminder\NotificationProvider;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IURLGenerator;
use OCP\L10N\IFactory as L10NFactory;
use OCP\Notification\IManager;
use OCP\IUser;
use OCP\Notification\INotification;
use Sabre\VObject\Component\VCalendar;
use OCP\AppFramework\Utility\ITimeFactory;
class PushProvider extends AbstractNotificationProvider
{
public const NOTIFICATION_TYPE = 'DISPLAY';
/**
* @var IManager
*/
private $manager;
/**
* @var ITimeFactory
*/
private $timeFactory;
/**
* @param IConfig $config
* @param IManager $manager
* @param ILogger $logger
* @param L10NFactory $l10nFactory
* @param IUrlGenerator $urlGenerator
* @param ITimeFactory $timeFactory
*/
public function __construct(IConfig $config, IManager $manager, ILogger $logger,
L10NFactory $l10nFactory,
IURLGenerator $urlGenerator, ITimeFactory $timeFactory) {
parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
$this->manager = $manager;
$this->timeFactory = $timeFactory;
}
/**
* Send notification
*
* @param VCalendar $vcalendar
* @param string $calendarDisplayName
* @param IUser $user
* @return void
* @throws \Exception
*/
public function send(VCalendar $vcalendar, string $calendarDisplayName, IUser $user): void
{
$lang = $this->config->getUserValue($user->getUID(), 'core', 'lang', $this->l10nFactory->findLanguage());
$this->l10n = $this->l10nFactory->get('dav', $lang);
$event = $this->extractEventDetails($vcalendar);
/** @var INotification $notification */
$notification = $this->manager->createNotification();
$notification->setApp(Application::APP_ID)
->setUser($user->getUID())
->setDateTime($this->timeFactory->getDateTime())
->setObject(Application::APP_ID, $event['uid']) // $type and $id
->setSubject('calendar_reminder', ['title' => $event['title'], 'start' => $event['start']->getTimestamp()]) // $subject and $parameters
->setMessage('calendar_reminder', [
'when' => $event['when'],
'description' => $event['description'],
'location' => $event['location'],
'calendar' => $calendarDisplayName
])
;
$this->manager->notify($notification);
}
}

@ -0,0 +1,59 @@
<?php
/**
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\CalDAV\Reminder;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException;
class NotificationProviderManager {
/** @var array */
private $providers = [];
/**
* @var string $type
* @return AbstractNotificationProvider
* @throws ProviderNotAvailableException
* @throws NotificationTypeDoesNotExistException
*/
public function getProvider(string $type): AbstractNotificationProvider
{
if (in_array($type, ReminderService::REMINDER_TYPES, true)) {
if (isset($this->providers[$type])) {
return $this->providers[$type];
}
throw new ProviderNotAvailableException($type);
}
throw new NotificationTypeDoesNotExistException($type);
}
/**
* @param string $providerClassName
* @throws \OCP\AppFramework\QueryException
*/
public function registerProvider(string $providerClassName): void
{
$provider = \OC::$server->query($providerClassName);
if (!$provider instanceof AbstractNotificationProvider) {
throw new \InvalidArgumentException('Invalid notification provider registered');
}
$this->providers[$provider::NOTIFICATION_TYPE] = $provider;
}
}

@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (c) 2018 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\Reminder;
class NotificationTypeDoesNotExistException extends \Exception {
/**
* NotificationTypeDoesNotExistException constructor.
*
* @since 16.0.0
*
* @param string $type ReminderType
*/
public function __construct(string $type) {
parent::__construct("Type $type is not an accepted type of notification");
}
}

@ -0,0 +1,143 @@
<?php
/**
* @copyright Copyright (c) 2019 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\CalDAV\Reminder;
use OCA\DAV\AppInfo\Application;
use OCP\IL10N;
use OCP\L10N\IFactory;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\IURLGenerator;
class Notifier implements INotifier {
public static $units = array(
'y' => 'year',
'm' => 'month',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second',
);
/** @var IFactory */
protected $factory;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var IL10N */
protected $l;
public function __construct(IFactory $factory, IURLGenerator $urlGenerator) {
$this->factory = $factory;
$this->urlGenerator = $urlGenerator;
}
/**
* @param INotification $notification
* @param string $languageCode The code of the language that should be used to prepare the notification
* @return INotification
* @throws \Exception
*/
public function prepare(INotification $notification, $languageCode): INotification
{
if ($notification->getApp() !== Application::APP_ID) {
throw new \InvalidArgumentException('Notification not from this app');
}
// Read the language from the notification
$this->l = $this->factory->get('dav', $languageCode);
if ($notification->getSubject() === 'calendar_reminder') {
$subjectParameters = $notification->getSubjectParameters();
$notification->setParsedSubject($this->processEventTitle($subjectParameters));
$messageParameters = $notification->getMessageParameters();
$notification->setParsedMessage($this->processEventDescription($messageParameters));
$notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'places/calendar.svg')));
return $notification;
}
// Unknown subject => Unknown notification => throw
throw new \InvalidArgumentException('Unknown subject');
}
/**
* @param array $event
* @return string
* @throws \Exception
*/
private function processEventTitle(array $event): string
{
$event_datetime = new \DateTime();
$event_datetime->setTimestamp($event['start']);
$now = new \DateTime();
$diff = $event_datetime->diff($now);
foreach (self::$units as $attribute => $unit) {
$count = $diff->$attribute;
if (0 !== $count) {
return $this->getPluralizedTitle($count, $diff->invert, $unit, $event['title']);
}
}
return '';
}
/**
*
* @param int $count
* @param int $invert
* @param string $unit
* @param string $title
* @return string
*/
private function getPluralizedTitle(int $count, int $invert, string $unit, string $title): string
{
if ($invert) {
return $this->l->n('%s (in one %s)', '%s (in %n %ss)', $count, [$title, $unit]);
}
// This should probably not show up
return $this->l->n('%s (one %s ago)', '%s (%n %ss ago)', $count, [$title, $unit]);
}
/**
* @param array $event
* @return string
*/
private function processEventDescription(array $event): string
{
$description = [
$this->l->t('Calendar: %s', $event['calendar']),
$this->l->t('Date: %s', $event['when']),
];
if ($event['description']) {
$description[] = $this->l->t('Description: %s', $event['description']);
}
if ($event['location']) {
$description[] = $this->l->t('Where: %s', $event['location']);
}
return implode('<br>', $description);
}
}

@ -0,0 +1,185 @@
<?php
/**
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\CalDAV\Reminder;
use OC\User\NoUserException;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\IUserSession;
use Sabre\VObject;
use Sabre\VObject\Component\VAlarm;
use Sabre\VObject\Reader;
class ReminderService {
/** @var Backend */
private $backend;
/** @var NotificationProviderManager */
private $notificationProviderManager;
/** @var IUserManager */
private $userManager;
/** @var IGroupManager */
private $groupManager;
/** @var IUserSession */
private $userSession;
public const REMINDER_TYPE_EMAIL = 'EMAIL';
public const REMINDER_TYPE_DISPLAY = 'DISPLAY';
public const REMINDER_TYPE_AUDIO = 'AUDIO';
public const REMINDER_TYPES = [self::REMINDER_TYPE_EMAIL, self::REMINDER_TYPE_DISPLAY, self::REMINDER_TYPE_AUDIO];
public function __construct(Backend $backend,
NotificationProviderManager $notificationProviderManager,
IUserManager $userManager,
IGroupManager $groupManager,
IUserSession $userSession) {
$this->backend = $backend;
$this->notificationProviderManager = $notificationProviderManager;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
}
/**
* Process reminders to activate
*
* @throws NoUserException
* @throws NotificationProvider\ProviderNotAvailableException
* @throws NotificationTypeDoesNotExistException
*/
public function processReminders(): void
{
$reminders = $this->backend->getRemindersToProcess();
foreach ($reminders as $reminder) {
$calendarData = Reader::read($reminder['calendardata']);
$user = $this->userManager->get($reminder['uid']);
if ($user === null) {
throw new NoUserException('User not found for calendar');
}
$notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']);
$notificationProvider->send($calendarData, $reminder['displayname'], $user);
$this->backend->removeReminder($reminder['id']);
}
}
/**
* Saves reminders when a calendar object with some alarms was created/updated/deleted
*
* @param string $action
* @param array $calendarData
* @param array $shares
* @param array $objectData
* @return void
* @throws VObject\InvalidDataException
* @throws NoUserException
*/
public function onTouchCalendarObject(string $action, array $calendarData, array $shares, array $objectData): void
{
if (!isset($calendarData['principaluri'])) {
return;
}
// Always remove existing reminders for this event
$this->backend->cleanRemindersForEvent($objectData['calendarid'], $objectData['uri']);
/**
* If we are deleting the event, no need to go further
*/
if ($action === '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') {
return;
}
$user = $this->userSession->getUser();
if ($user === null) {
throw new NoUserException('No user in session');
}
$users = $this->getUsersForShares($shares);
$users[] = $user->getUID();
$vobject = VObject\Reader::read($objectData['calendardata']);
foreach ($vobject->VEVENT->VALARM as $alarm) {
if ($alarm instanceof VAlarm) {
$type = strtoupper($alarm->ACTION->getValue());
if (in_array($type, self::REMINDER_TYPES, true)) {
$time = $alarm->getEffectiveTriggerTime();
foreach ($users as $uid) {
$this->backend->insertReminder(
$uid,
$objectData['calendarid'],
$objectData['uri'],
$type,
$time->getTimestamp(),
$vobject->VEVENT->DTSTART->getDateTime()->getTimestamp());
}
}
}
}
}
/**
* Get all users that have access to a given calendar
*
* @param array $shares
* @return string[]
*/
private function getUsersForShares(array $shares): array
{
$users = $groups = [];
foreach ($shares as $share) {
$principal = explode('/', $share['{http://owncloud.org/ns}principal']);
if ($principal[1] === 'users') {
$users[] = $principal[2];
} else if ($principal[1] === 'groups') {
$groups[] = $principal[2];
}
}
if (!empty($groups)) {
foreach ($groups as $gid) {
$group = $this->groupManager->get($gid);
if ($group instanceof IGroup) {
foreach ($group->getUsers() as $user) {
$users[] = $user->getUID();
}
}
}
}
return array_unique($users);
}
}

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace OCA\DAV\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
use Doctrine\DBAL\Types\Type;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version1007Date20181005133326 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
}
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('calendar_reminders')) {
$table = $schema->createTable('calendar_reminders');
$table->addColumn('id', Type::BIGINT, [
'autoincrement' => true,
'notnull' => true,
'length' => 11,
'unsigned' => true,
]);
$table->addColumn('uid', Type::STRING, [
'notnull' => true,
'length' => 255,
]);
$table->addColumn('calendarid', Type::BIGINT, [
'notnull' => false,
'length' => 11,
]);
$table->addColumn('objecturi', Type::STRING, [
'notnull' => true,
'length' => 255,
]);
$table->addColumn('type', Type::STRING, [
'notnull' => true,
'length' => 255,
]);
$table->addColumn('notificationdate', Type::DATETIME, [
'notnull' => false,
]);
$table->addColumn('eventstartdate', Type::DATETIME, [
'notnull' => false,
]);
$table->setPrimaryKey(['id']);
$table->addIndex(['calendarid'], 'calendar_reminder_calendars');
return $schema;
}
}
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
}
}

@ -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);

@ -72,4 +72,25 @@ script('dav', [
<em><?php p($l->t('Birthday calendars will be generated by a background job.')); ?></em><br>
<em><?php p($l->t('Hence they will not be available immediately after enabling but will show up after some time.')); ?></em>
</p>
<p>
<input type="checkbox" name="caldav_send_reminders_notifications" id="caldavSendRemindersNotifications" class="checkbox"
<?php ($_['send_reminders_notifications'] === 'yes') ? print_unescaped('checked="checked"') : null ?>/>
<label for="caldavSendRemindersNotifications"><?php p($l->t('Send notifications for events')); ?></label>
<br>
<em>
<?php print_unescaped(str_replace(
[
'{emailopen}',
'{linkclose}',
],
[
'<a href="../admin#mail_general_settings">',
'</a>',
],
$l->t('Please make sure to properly set up {emailopen}the email server{linkclose}.')
)); ?>
</em>
<br>
<em><?php p($l->t('Notifications will be send through background jobs, so these need to happen often enough.')); ?></em>
</p>
</form>

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018, Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Tests\unit\BackgroundJob;
use OCA\DAV\BackgroundJob\EventReminderJob;
use OCA\DAV\CalDAV\Reminder\ReminderService;
use OCP\IConfig;
use Test\TestCase;
class EventReminderJobTest extends TestCase {
/** @var ReminderService|\PHPUnit\Framework\MockObject\MockObject */
private $reminderService;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
private $config;
/** @var EventReminderJob|\PHPUnit\Framework\MockObject\MockObject */
private $backgroundJob;
protected function setUp() {
parent::setUp();
$this->reminderService = $this->createMock(ReminderService::class);
$this->config = $this->createMock(IConfig::class);
$this->backgroundJob = new EventReminderJob($this->reminderService, $this->config);
}
public function data(): array
{
return [[true], [false]];
}
/**
* @dataProvider data
* @param bool $sendEventReminders
*/
public function testRun(bool $sendEventReminders): void
{
$this->config->expects($this->once())->method('getAppValue')->with('dav', 'sendEventReminders', 'yes')->willReturn($sendEventReminders ? 'yes' : 'no');
$this->reminderService->expects($this->exactly($sendEventReminders ? 1 : 0))->method('processReminders');
$this->backgroundJob->run([]);
}
}

@ -0,0 +1,87 @@
<?php
/**
* @copyright Copyright (c) 2019, Thomas Citharel
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\Reminder;
use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IURLGenerator;
use OCP\L10N\IFactory as L10NFactory;
use OCP\IUser;
use Test\TestCase;
use Sabre\VObject\Component\VCalendar;
abstract class AbstractNotificationProviderTest extends TestCase {
/** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */
protected $logger;
/** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */
protected $l10nFactory;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
protected $l10n;
/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
protected $urlGenerator;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
protected $config;
/** @var AbstractNotificationProvider|\PHPUnit\Framework\MockObject\MockObject */
protected $provider;
/**
* @var VCalendar
*/
protected $vcalendar;
/**
* @var string
*/
protected $calendarDisplayName;
/**
* @var IUser|\PHPUnit\Framework\MockObject\MockObject
*/
protected $user;
public function setUp() {
parent::setUp();
$this->logger = $this->createMock(ILogger::class);
$this->l10nFactory = $this->createMock(L10NFactory::class);
$this->l10n = $this->createMock(IL10N::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->config = $this->createMock(IConfig::class);
$this->vcalendar = new VCalendar();
$this->vcalendar->add('VEVENT', [
'SUMMARY' => 'Fellowship meeting',
'DTSTART' => new \DateTime('2017-01-01 00:00:00') // 1483228800
]);
$this->calendarDisplayName = 'Personal';
$this->user = $this->createMock(IUser::class);
}
}

@ -0,0 +1,313 @@
<?php
/**
* @copyright Copyright (c) 2018, Thomas Citharel
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\Reminder;
use OCP\IDBConnection;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\AppFramework\Utility\ITimeFactory;
use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend;
use Test\TestCase;
class BackendTest extends TestCase {
/**
* Reminder Backend
*
* @var ReminderBackend|\PHPUnit\Framework\MockObject\MockObject
*/
private $reminderBackend;
/** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */
private $dbConnection;
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
private $timeFactory;
public function setUp() {
parent::setUp();
$this->dbConnection = $this->createMock(IDBConnection::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->reminderBackend = new ReminderBackend($this->dbConnection, $this->timeFactory);
}
public function testCleanRemindersForEvent(): void
{
/** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */
$queryBuilder = $this->createMock(IQueryBuilder::class);
$stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class);
$expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class);
$this->dbConnection->expects($this->once())
->method('getQueryBuilder')
->with()
->will($this->returnValue($queryBuilder));
$queryBuilder->method('expr')
->will($this->returnValue($expr));
$expr->method('eq')
->will($this->returnValueMap([
['calendarid', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'],
['objecturi', 'createNamedParameter-2', null, 'WHERE_CLAUSE_2'],
]));
$queryBuilder->method('createNamedParameter')
->will($this->returnValueMap([
[1, \PDO::PARAM_STR, null, 'createNamedParameter-1'],
['object.ics', \PDO::PARAM_STR, null, 'createNamedParameter-2'],
]));
$queryBuilder->expects($this->at(0))
->method('delete')
->with('calendar_reminders')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(3))
->method('where')
->with('WHERE_CLAUSE_1')
->will($this->returnValue($queryBuilder));
$queryBuilder->expects($this->at(6))
->method('andWhere')
->with('WHERE_CLAUSE_2')
->will($this->returnValue($queryBuilder));
$queryBuilder->expects($this->at(7))
->method('execute')
->with()
->willReturn($stmt);
$this->reminderBackend->cleanRemindersForEvent(1, 'object.ics');
}
public function testCleanRemindersForCalendar(): void
{
/** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */
$queryBuilder = $this->createMock(IQueryBuilder::class);
$stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class);
$expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class);
$this->dbConnection->expects($this->once())
->method('getQueryBuilder')
->with()
->will($this->returnValue($queryBuilder));
$queryBuilder->method('expr')
->will($this->returnValue($expr));
$expr->method('eq')
->will($this->returnValueMap([
['calendarid', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'],
]));
$queryBuilder->method('createNamedParameter')
->will($this->returnValueMap([
[1337, \PDO::PARAM_STR, null, 'createNamedParameter-1'],
]));
$queryBuilder->expects($this->at(0))
->method('delete')
->with('calendar_reminders')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(3))
->method('where')
->with('WHERE_CLAUSE_1')
->will($this->returnValue($queryBuilder));
$queryBuilder->expects($this->at(4))
->method('execute')
->with()
->willReturn($stmt);
$this->reminderBackend->cleanRemindersForCalendar(1337);
}
public function testRemoveReminder(): void
{
/** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */
$queryBuilder = $this->createMock(IQueryBuilder::class);
$stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class);
$expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class);
$this->dbConnection->expects($this->once())
->method('getQueryBuilder')
->with()
->will($this->returnValue($queryBuilder));
$queryBuilder->method('expr')
->will($this->returnValue($expr));
$expr->method('eq')
->will($this->returnValueMap([
['id', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'],
]));
$queryBuilder->method('createNamedParameter')
->will($this->returnValueMap([
[16, \PDO::PARAM_STR, null, 'createNamedParameter-1'],
]));
$queryBuilder->expects($this->at(0))
->method('delete')
->with('calendar_reminders')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(3))
->method('where')
->with('WHERE_CLAUSE_1')
->will($this->returnValue($queryBuilder));
$queryBuilder->expects($this->at(4))
->method('execute')
->with()
->willReturn($stmt);
$this->reminderBackend->removeReminder(16);
}
public function testGetRemindersToProcess(): void
{
$dbData = [[
'cr.id' => 30,
'cr.calendarid' => 3,
'cr.objecturi' => 'object.ics',
'cr.type' => 'EMAIL',
'cr.notificationdate' => 1337,
'cr.uid' => 'user1',
'co.calendardata' => 'BEGIN:VCALENDAR',
'c.displayname' => 'My Calendar'
]];
$this->timeFactory->expects($this->exactly(2))
->method('getTime')
->with()
->willReturn(1337);
/** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */
$queryBuilder = $this->createMock(IQueryBuilder::class);
$stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class);
$expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class);
$this->dbConnection->expects($this->once())
->method('getQueryBuilder')
->with()
->willReturn($queryBuilder);
$queryBuilder->method('expr')
->willReturn($expr);
$expr->method('eq')
->willReturnMap([
['cr.calendarid', 'c.id', null, 'EQ_CLAUSE_1'],
['co.uri', 'cr.objecturi', null, 'EQ_CLAUSE_2'],
]);
$expr->method('andX')
->willReturnMap([
['EQ_CLAUSE_1', 'EQ_CLAUSE_2', 'ANDX_CLAUSE'],
]);
$expr->method('lte')
->with('cr.notificationdate', 'createNamedParameter-1', null)
->willReturn('LTE_CLAUSE_1');
$expr->method('gte')
->with('cr.eventstartdate', 'createNamedParameter-1', null)
->willReturn('GTE_CLAUSE_2');
$queryBuilder->method('createNamedParameter')
->willReturnMap([
[1337, \PDO::PARAM_STR, null, 'createNamedParameter-1'],
]);
$queryBuilder->expects($this->at(0))
->method('select')
->with(['cr.id', 'cr.calendarid', 'cr.objecturi', 'cr.type', 'cr.notificationdate', 'cr.uid', 'co.calendardata', 'c.displayname'])
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(1))
->method('from')
->with('calendar_reminders', 'cr')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(4))
->method('where')
->with('LTE_CLAUSE_1')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(7))
->method('andWhere')
->with('GTE_CLAUSE_2')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(9))
->method('leftJoin')
->with('cr', 'calendars', 'c', 'EQ_CLAUSE_1')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(13))
->method('leftJoin')
->with('cr', 'calendarobjects', 'co', 'ANDX_CLAUSE')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(14))
->method('execute')
->with()
->willReturn($stmt);
$stmt->expects($this->once())
->method('fetchAll')
->with()
->willReturn($dbData);
$actual = $this->reminderBackend->getRemindersToProcess();
$this->assertEquals($dbData, $actual);
}
public function testInsertReminder(): void
{
/** @var IQueryBuilder|\PHPUnit\Framework\MockObject\MockObject $queryBuilder */
$queryBuilder = $this->createMock(IQueryBuilder::class);
$stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class);
$expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class);
$this->dbConnection->expects($this->once())
->method('getQueryBuilder')
->with()
->will($this->returnValue($queryBuilder));
$queryBuilder->method('expr')
->will($this->returnValue($expr));
$queryBuilder->method('createNamedParameter')
->will($this->returnValueMap([
['user1', \PDO::PARAM_STR, null, 'createNamedParameter-1'],
['1', \PDO::PARAM_STR, null, 'createNamedParameter-2'],
['object.ics', \PDO::PARAM_STR, null, 'createNamedParameter-3'],
['EMAIL', \PDO::PARAM_STR, null, 'createNamedParameter-4'],
[1227, \PDO::PARAM_STR, null, 'createNamedParameter-5'],
[1337, \PDO::PARAM_STR, null, 'createNamedParameter-6'],
]));
$queryBuilder->expects($this->at(0))
->method('insert')
->with('calendar_reminders')
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(7))
->method('values')
->with([
'uid' => 'createNamedParameter-1',
'calendarid' => 'createNamedParameter-2',
'objecturi' => 'createNamedParameter-3',
'type' => 'createNamedParameter-4',
'notificationdate' => 'createNamedParameter-5',
'eventstartdate' => 'createNamedParameter-6',
])
->willReturn($queryBuilder);
$queryBuilder->expects($this->at(8))
->method('execute')
->with()
->willReturn($stmt);
$actual = $this->reminderBackend->insertReminder('user1', '1', 'object.ics', 'EMAIL', 1227, 1337);
}
}

@ -0,0 +1,226 @@
<?php
/**
* @copyright Copyright (c) 2019, Thomas Citharel
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\Reminder\NotificationProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider;
use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IURLGenerator;
use OCP\L10N\IFactory as L10NFactory;
use OCP\IUser;
use OCP\Mail\IEMailTemplate;
use OCP\Mail\IMailer;
use OCP\Mail\IAttachment;
use OCP\Mail\IMessage;
use Test\TestCase;
use OCA\DAV\Tests\unit\CalDAV\Reminder\AbstractNotificationProviderTest;
class EmailProviderTest extends AbstractNotificationProviderTest {
const USER_EMAIL = 'frodo@hobb.it';
/** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */
protected $logger;
/** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */
protected $l10nFactory;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
protected $l10n;
/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
protected $urlGenerator;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
protected $config;
/** @var IMailer|\PHPUnit\Framework\MockObject\MockObject */
private $mailer;
public function setUp() {
parent::setUp();
$this->mailer = $this->createMock(IMailer::class);
$this->provider = new EmailProvider(
$this->config,
$this->mailer,
$this->logger,
$this->l10nFactory,
$this->urlGenerator
);
}
public function testSendWithNoUserEmail(): void
{
$this->user->expects($this->once())
->method('getEMailAddress')
->with()
->willReturn(null);
$this->mailer
->expects($this->never())
->method('send');
$this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user);
}
public function testSendWithFailedRecipients(): void
{
$this->user->expects($this->exactly(2))
->method('getEMailAddress')
->with()
->willReturn(self::USER_EMAIL);
$this->mailer
->expects($this->once())
->method('send')
->willReturn([self::USER_EMAIL])
;
$this->logger
->expects($this->once())
->method('error');
$l10n = $this->createMock(IL10N::class);
$this->l10nFactory
->method('get')
->willReturn($l10n);
$this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user);
}
public function testSendWithMailerFailure(): void
{
$this->user->expects($this->exactly(2))
->method('getEMailAddress')
->with()
->willReturn(self::USER_EMAIL);
$ex = new \Exception();
$this->mailer
->expects($this->once())
->method('send')
->will($this->throwException($ex))
;
$this->logger
->expects($this->once())
->method('logException')
->with($ex, ['app' => 'dav']);
$l10n = $this->createMock(IL10N::class);
$this->l10nFactory
->method('get')
->willReturn($l10n);
$this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user);
}
public function testSend(): void
{
$this->user->expects($this->exactly(2))
->method('getEMailAddress')
->with()
->willReturn(self::USER_EMAIL);
$this->user->expects($this->once())
->method('getDisplayName')
->with()
->willReturn('Frodo');
$this->urlGenerator
->expects($this->exactly(2))
->method('getAbsoluteURL');
$this->urlGenerator
->expects($this->exactly(2))
->method('imagePath');
$mailMessage = $this->createMock(IMessage::class);
$mailMessage->expects($this->once())
->method('setFrom')
->with([\OCP\Util::getDefaultEmailAddress('invitations-noreply') => 'Nextcloud'])
->willReturn($mailMessage);
$mailMessage->expects($this->once())
->method('setTo')
->with([self::USER_EMAIL => 'Frodo'])
->willReturn($mailMessage);
$mailMessage
->expects($this->never())
->method('setReplyTo')
->willReturn($mailMessage);
$emailTemplate = $this->createMock(IEMailTemplate::class);
$this->mailer
->expects($this->once())
->method('createEMailTemplate')
->willReturn($emailTemplate);
$emailTemplate->expects($this->once())
->method('setSubject')
->with('Notification: Fellowship meeting');
$emailTemplate->expects($this->once())
->method('addHeader');
$emailTemplate->expects($this->once())
->method('addHeading');
$emailTemplate->expects($this->exactly(2))
->method('addBodyListItem');
$emailTemplate->expects($this->once())
->method('addFooter');
$mailMessage->expects($this->once())
->method('useTemplate')
->with($emailTemplate);
$this->mailer
->expects($this->once())
->method('createMessage')
->willReturn($mailMessage);
$emailAttachment = $this->createMock(IAttachment::class);
$this->mailer
->expects($this->once())
->method('createAttachment')
->willReturn($emailAttachment);
$this->mailer
->expects($this->once())
->method('send');
$l10n = $this->createMock(IL10N::class);
$this->l10nFactory
->method('get')
->willReturn($l10n);
$this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user);
}
}

@ -0,0 +1,139 @@
<?php
/**
* @copyright Copyright (c) 2019, Thomas Citharel
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\Reminder\NotificationProvider;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider;
use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IURLGenerator;
use OCP\L10N\IFactory as L10NFactory;
use OCP\IUser;
use OCP\Notification\IManager;
use OCP\Notification\INotification;
use OCP\AppFramework\Utility\ITimeFactory;
use Test\TestCase;
use OCA\DAV\Tests\unit\CalDAV\Reminder\AbstractNotificationProviderTest;
class PushProviderTest extends AbstractNotificationProviderTest {
/** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */
protected $logger;
/** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */
protected $l10nFactory;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
protected $l10n;
/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
protected $urlGenerator;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
protected $config;
/** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
private $manager;
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
private $timeFactory;
public function setUp() {
parent::setUp();
$this->manager = $this->createMock(IManager::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->provider = new PushProvider(
$this->config,
$this->manager,
$this->logger,
$this->l10nFactory,
$this->urlGenerator,
$this->timeFactory
);
}
public function testSend(): void
{
$notification = $this->createMock(INotification::class);
$notification
->expects($this->once())
->method('setApp')
->with(Application::APP_ID)
->willReturn($notification);
$notification
->expects($this->once())
->method('setUser')
->willReturn($notification)
;
$notification
->expects($this->once())
->method('setDateTime')
->willReturn($notification)
;
$notification
->expects($this->once())
->method('setObject')
->willReturn($notification)
;
$notification
->expects($this->once())
->method('setSubject')
->willReturn($notification)
;
$notification
->expects($this->once())
->method('setMessage')
->willReturn($notification)
;
$this->manager
->expects($this->once())
->method('createNotification')
->willReturn($notification);
$this->manager
->expects($this->once())
->method('notify')
->with($notification);
$l10n = $this->createMock(IL10N::class);
$this->l10nFactory
->method('get')
->willReturn($l10n);
$this->timeFactory->expects($this->once())
->method('getDateTime')
->with()
->willReturn(new \DateTime());
$this->provider->send($this->vcalendar, $this->calendarDisplayName, $this->user);
}
}

@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (c) 2019, Thomas Citharel
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\Reminder;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProviderManager;
use OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException;
use OCA\DAV\Capabilities;
use Test\TestCase;
class NotificationProviderManagerTest extends TestCase {
/** @var NotificationProviderManager|\PHPUnit\Framework\MockObject\MockObject */
private $providerManager;
/**
* @throws \OCP\AppFramework\QueryException
*/
public function setUp() {
parent::setUp();
$this->providerManager = new NotificationProviderManager();
$this->providerManager->registerProvider(EmailProvider::class);
}
/**
* @expectedException OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException
* @expectedExceptionMessage Type NOT EXISTENT is not an accepted type of notification
* @throws ProviderNotAvailableException
* @throws NotificationTypeDoesNotExistException
*/
public function testGetProviderForUnknownType(): void
{
$this->providerManager->getProvider('NOT EXISTENT');
}
/**
* @expectedException OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException
* @expectedExceptionMessage No notification provider for type AUDIO available
* @throws NotificationTypeDoesNotExistException
* @throws ProviderNotAvailableException
*/
public function testGetProviderForUnRegisteredType(): void
{
$this->providerManager->getProvider('AUDIO');
}
/**
* @throws NotificationTypeDoesNotExistException
* @throws ProviderNotAvailableException
*/
public function testGetProvider(): void
{
$provider = $this->providerManager->getProvider('EMAIL');
$this->assertInstanceOf(EmailProvider::class, $provider);
}
/**
* @throws NotificationTypeDoesNotExistException
* @throws ProviderNotAvailableException
* @throws \OCP\AppFramework\QueryException
*/
public function testRegisterProvider(): void
{
$this->providerManager->registerProvider(PushProvider::class);
$provider = $this->providerManager->getProvider('DISPLAY');
$this->assertInstanceOf(PushProvider::class, $provider);
}
/**
* @expectedExceptionMessage Invalid notification provider registered
* @expectedException \InvalidArgumentException
* @throws \OCP\AppFramework\QueryException
*/
public function testRegisterBadProvider(): void
{
$this->providerManager->registerProvider(Capabilities::class);
}
}

@ -0,0 +1,184 @@
<?php
/**
* @copyright Copyright (c) 2019 Thomas Citharel <tcit@tcit.fr>
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\Reminder;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\CalDAV\Reminder\Notifier;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\L10N\IFactory;
use OCP\Notification\INotification;
use Test\TestCase;
class NotifierTest extends TestCase {
/** @var Notifier */
protected $notifier;
/** @var IFactory|\PHPUnit\Framework\MockObject\MockObject */
protected $factory;
/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
protected $urlGenerator;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
protected $l;
protected function setUp() {
parent::setUp();
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->l = $this->createMock(IL10N::class);
$this->l->expects($this->any())
->method('t')
->willReturnCallback(function($string, $args) {
return vsprintf($string, $args);
});
$this->l->expects($this->any())
->method('n')
->willReturnCallback(function($textSingular, $textPlural, $count, $args) {
$text = $count === 1 ? $textSingular : $textPlural;
$text = str_replace('%n', (string)$count, $text);
return vsprintf($text, $args);
});
$this->factory = $this->createMock(IFactory::class);
$this->factory->expects($this->any())
->method('get')
->willReturn($this->l);
$this->notifier = new Notifier(
$this->factory,
$this->urlGenerator
);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Notification not from this app
*/
public function testPrepareWrongApp(): void
{
/** @var INotification|\PHPUnit\Framework\MockObject\MockObject $notification */
$notification = $this->createMock(INotification::class);
$notification->expects($this->once())
->method('getApp')
->willReturn('notifications');
$notification->expects($this->never())
->method('getSubject');
$this->notifier->prepare($notification, 'en');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Unknown subject
*/
public function testPrepareWrongSubject() {
/** @var INotification|\PHPUnit\Framework\MockObject\MockObject $notification */
$notification = $this->createMock(INotification::class);
$notification->expects($this->once())
->method('getApp')
->willReturn(Application::APP_ID);
$notification->expects($this->once())
->method('getSubject')
->willReturn('wrong subject');
$this->notifier->prepare($notification, 'en');
}
public function dataPrepare(): array
{
return [
[
'calendar_reminder',
[
'title' => 'foo',
'start' => time() - 60 * 60 * 24
],
'foo (one day ago)',
[
'when' => 'foo',
'description' => 'bar',
'location' => 'NC Headquarters',
'calendar' => 'Personal'
],
'Calendar: Personal<br>Date: foo<br>Description: bar<br>Where: NC Headquarters'
],
];
}
/**
* @dataProvider dataPrepare
*
* @param string $subjectType
* @param array $subjectParams
* @param string $subject
* @param array $messageParams
* @param string $message
* @throws \Exception
*/
public function testPrepare(string $subjectType, array $subjectParams, string $subject, array $messageParams, string $message): void
{
/** @var INotification|\PHPUnit\Framework\MockObject\MockObject $notification */
$notification = $this->createMock(INotification::class);
$notification->expects($this->once())
->method('getApp')
->willReturn(Application::APP_ID);
$notification->expects($this->once())
->method('getSubject')
->willReturn($subjectType);
$notification->expects($this->once())
->method('getSubjectParameters')
->willReturn($subjectParams);
$notification->expects($this->once())
->method('getMessageParameters')
->willReturn($messageParams);
$notification->expects($this->once())
->method('setParsedSubject')
->with($subject)
->willReturnSelf();
$notification->expects($this->once())
->method('setParsedMessage')
->with($message)
->willReturnSelf();
$this->urlGenerator->expects($this->once())
->method('imagePath')
->with('core', 'places/calendar.svg')
->willReturn('icon-url');
$this->urlGenerator->expects($this->once())
->method('getAbsoluteURL')
->with('icon-url')
->willReturn('absolute-icon-url');
$notification->expects($this->once())
->method('setIcon')
->with('absolute-icon-url')
->willReturnSelf();
$return = $this->notifier->prepare($notification, 'en');
$this->assertEquals($notification, $return);
}
}

@ -0,0 +1,276 @@
<?php
/**
* @copyright Copyright (c) 2019, Thomas Citharel
*
* @author Thomas Citharel <tcit@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\Reminder;
use OCA\DAV\CalDAV\Reminder\AbstractNotificationProvider;
use OCA\DAV\CalDAV\Reminder\Backend;
use OCA\DAV\CalDAV\Reminder\NotificationProviderManager;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider;
use OCA\DAV\CalDAV\Reminder\ReminderService;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use Test\TestCase;
class ReminderServiceTest extends TestCase {
/** @var Backend|\PHPUnit\Framework\MockObject\MockObject */
private $backend;
/** @var NotificationProviderManager|\PHPUnit\Framework\MockObject\MockObject */
private $notificationProviderManager;
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
private $userManager;
/** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject*/
private $groupManager;
/** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
private $userSession;
public const CALENDAR_DATA = <<<EOD
BEGIN:VCALENDAR
PRODID:-//Nextcloud calendar v1.6.4
BEGIN:VEVENT
CREATED:20160602T133732
DTSTAMP:20160602T133732
LAST-MODIFIED:20160602T133732
UID:wej2z68l9h
SUMMARY:Test Event
LOCATION:Somewhere ...
DESCRIPTION:maybe ....
DTSTART;TZID=Europe/Berlin;VALUE=DATE:20160609
DTEND;TZID=Europe/Berlin;VALUE=DATE:20160610
BEGIN:VALARM
ACTION:EMAIL
TRIGGER:-PT15M
END:VALARM
END:VEVENT
END:VCALENDAR
EOD;
public function setUp() {
parent::setUp();
$this->backend = $this->createMock(Backend::class);
$this->notificationProviderManager = $this->createMock(NotificationProviderManager::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->userSession = $this->createMock(IUserSession::class);
}
public function dataTestProcessReminders(): array
{
return [
[
[], null
],
[
[
[
'calendardata' => self::CALENDAR_DATA,
'displayname' => 'Personal',
'type' => 'EMAIL',
'uid' => 1,
'id' => 1,
],
],
$this->createMock(EmailProvider::class),
],
[
[
[
'calendardata' => self::CALENDAR_DATA,
'displayname' => 'Personal',
'type' => 'DISPLAY',
'uid' => 1,
'id' => 1,
],
],
$this->createMock(PushProvider::class),
]
];
}
/**
* @dataProvider dataTestProcessReminders
* @param array $reminders
* @param AbstractNotificationProvider|null $notificationProvider
* @throws \OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException
* @throws \OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException
* @throws \OC\User\NoUserException
*/
public function testProcessReminders(array $reminders, ?AbstractNotificationProvider $notificationProvider): void
{
$user = $this->createMock(IUser::class);
$this->backend->expects($this->once())->method('getRemindersToProcess')->willReturn($reminders);
if (count($reminders) > 0) {
$this->userManager->expects($this->exactly(count($reminders)))->method('get')->willReturn($user);
$this->backend->expects($this->exactly(count($reminders)))->method('removeReminder');
$this->notificationProviderManager->expects($this->exactly(count($reminders)))->method('getProvider')->willReturn($notificationProvider);
}
$reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession);
$reminderService->processReminders();
}
/**
* @expectedException OC\User\NoUserException
*/
public function testProcessReminderWithBadUser(): void
{
$this->backend->expects($this->once())->method('getRemindersToProcess')->willReturn([
[
'calendardata' => self::CALENDAR_DATA,
'type' => 'DISPLAY',
'uid' => 1,
'id' => 1,
]
]);
$this->userManager->expects($this->once())->method('get')->with(1)->willReturn(null);
$reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession);
$reminderService->processReminders();
}
public function providesTouchCalendarObject(): array
{
return [
[
'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
[
'principaluri' => 'principals/users/personal'
],
[],
[
'calendarid' => 1,
'uri' => 'something.ics',
],
0
],
[
'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
[
'principaluri' => 'principals/users/personal'
],
[],
[
'calendarid' => 1,
'uri' => 'something.ics',
'calendardata' => self::CALENDAR_DATA
],
0
],
[
'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
[
'principaluri' => 'principals/users/someone',
'uri' => 'personal'
],
[
[
'{http://owncloud.org/ns}principal' => 'principals/users/someone'
]
],
[
'calendarid' => 1,
'uri' => 'something.ics',
'calendardata' => self::CALENDAR_DATA
],
0
],
[
'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
[
'principaluri' => 'principals/users/someone',
'uri' => 'personal'
],
[
[
'{http://owncloud.org/ns}principal' => 'principals/groups/somegroup'
]
],
[
'calendarid' => 1,
'uri' => 'something.ics',
'calendardata' => self::CALENDAR_DATA
],
1
]
];
}
/**
* @dataProvider providesTouchCalendarObject
* @param string $action
* @param array $calendarData
* @param array $shares
* @param array $objectData
* @param int $numberOfGroups
* @throws \OC\User\NoUserException
* @throws \Sabre\VObject\InvalidDataException
*/
public function testOnTouchCalendarObject(string $action, array $calendarData, array $shares, array $objectData, int $numberOfGroups): void
{
$this->backend->expects($this->once())->method('cleanRemindersForEvent')->with($objectData['calendarid'], $objectData['uri']);
if ($action !== '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') {
$user = $this->createMock(IUser::class);
$user->expects($this->once())->method('getUID')->willReturn('user');
$this->userSession->expects($this->once())->method('getUser')->willReturn($user);
if ($numberOfGroups === 0) {
$this->backend->expects($this->exactly(count($shares) + 1))->method('insertReminder');
} else {
$group = $this->createMock(IGroup::class);
$groupUser = $this->createMock(IUser::class);
$groupUser->expects($this->once())->method('getUID')->willReturn('groupuser');
$group->expects($this->once())->method('getUsers')->willReturn([$groupUser]);
$this->groupManager->expects($this->exactly($numberOfGroups))->method('get')->willReturn($group);
}
}
$reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession);
$reminderService->onTouchCalendarObject($action, $calendarData, $shares, $objectData);
}
/**
* @expectedException OC\User\NoUserException
*/
public function testOnTouchCalendarObjectWithNoSession(): void
{
$this->backend->expects($this->once())->method('cleanRemindersForEvent');
$this->userSession->expects($this->once())->method('getUser')->willReturn(null);
$reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession);
$reminderService->onTouchCalendarObject('', ['principaluri' => 'foo'], [], ['calendarid' => 1, 'uri' => 'bar']);
}
public function testOnTouchCalendarObjectWithNoCalendarURI(): void
{
$reminderService = new ReminderService($this->backend, $this->notificationProviderManager, $this->userManager, $this->groupManager, $this->userSession);
$this->assertNull($reminderService->onTouchCalendarObject('', [], [], []));
}
}
Loading…
Cancel
Save