From bcce568c6d892ccacc598c5d5bfa6b24c3284741 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 9 Aug 2019 20:25:21 +0200 Subject: Support recurring events + repeating alarms Signed-off-by: Georg Ehrke --- apps/dav/lib/CalDAV/Reminder/Notifier.php | 292 +++++++++++++++++++++++------- 1 file changed, 226 insertions(+), 66 deletions(-) (limited to 'apps/dav/lib/CalDAV/Reminder/Notifier.php') diff --git a/apps/dav/lib/CalDAV/Reminder/Notifier.php b/apps/dav/lib/CalDAV/Reminder/Notifier.php index 3718d5b29a6..4bad9841787 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; + + /** @var ITimeFactory */ + private $timeFactory; - public function __construct(IFactory $factory, IURLGenerator $urlGenerator) { - $this->factory = $factory; + /** + * 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 - * @return string - * @throws \Exception + * @param INotification $notification + * @return INotification */ - 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 ''; + 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 int $count - * @param int $invert - * @param string $unit - * @param string $title - * @return string + * @param INotification $notification */ - 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]); + 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]); } - // This should probably not show up - return $this->l->n('%s (one %s ago)', '%s (%n %ss ago)', $count, [$title, $unit]); + + $notification->setParsedSubject($title); } /** - * @param array $event - * @return string + * Sets the notification message based on the parameters set in PushProvider + * + * @param INotification $notification */ - private function processEventDescription(array $event):string { + private function prepareNotificationMessage(INotification $notification): void { + $parameters = $notification->getMessageParameters(); + $description = [ - $this->l->t('Calendar: %s', $event['calendar']), - $this->l->t('Date: %s', $event['when']), + $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'); + } - if ($event['description']) { - $description[] = $this->l->t('Description: %s', $event['description']); + /** + * @param array $parameters + * @return string + * @throws \Exception + */ + 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); + + if ($isAllDay) { + // One day event + if ($diff->days === 1) { + return $this->getDateString($startDateTime); + } + + return implode(' - ', [ + $this->getDateString($startDateTime), + $this->getDateString($endDateTime), + ]); } - if ($event['location']) { - $description[] = $this->l->t('Where: %s', $event['location']); + + $startTimezone = $endTimezone = null; + if (!$parameters['start_is_floating']) { + $startTimezone = $parameters['start_timezone']; + $endTimezone = $parameters['end_timezone']; } - return implode('
', $description); + + $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 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 DateTime $dt + * @return string + */ + private function getWeekDayName(DateTime $dt):string { + return $this->l10n->l('weekdayName', $dt, ['width' => 'abbreviated']); + } + + /** + * @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']); } } -- cgit v1.2.3