diff options
Diffstat (limited to 'apps/dav/lib/CalDAV')
50 files changed, 269 insertions, 147 deletions
diff --git a/apps/dav/lib/CalDAV/Activity/Backend.php b/apps/dav/lib/CalDAV/Activity/Backend.php index 5ae71f4ecc6..f0c49e6e28c 100644 --- a/apps/dav/lib/CalDAV/Activity/Backend.php +++ b/apps/dav/lib/CalDAV/Activity/Backend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php b/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php index 7411202044d..78579ee84b7 100644 --- a/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php +++ b/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Activity/Filter/Todo.php b/apps/dav/lib/CalDAV/Activity/Filter/Todo.php index 6bc7bd4b308..b001f90c28d 100644 --- a/apps/dav/lib/CalDAV/Activity/Filter/Todo.php +++ b/apps/dav/lib/CalDAV/Activity/Filter/Todo.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Base.php b/apps/dav/lib/CalDAV/Activity/Provider/Base.php index 9a75acb878c..558abe0ca1a 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Base.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Base.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -40,8 +41,8 @@ abstract class Base implements IProvider { * @return array */ protected function generateCalendarParameter($data, IL10N $l) { - if ($data['uri'] === CalDavBackend::PERSONAL_CALENDAR_URI && - $data['name'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { + if ($data['uri'] === CalDavBackend::PERSONAL_CALENDAR_URI + && $data['name'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { return [ 'type' => 'calendar', 'id' => (string)$data['id'], diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php b/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php index 898eb2b5cb6..8c93ddae431 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Event.php b/apps/dav/lib/CalDAV/Activity/Provider/Event.php index 41b610542df..87551d7840b 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Event.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Event.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -78,14 +79,9 @@ class Event extends Base { // as seen from the affected user. $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $affectedUser . '/' . $calendarUri . '_shared_by_' . $linkData['owner'] . '/' . $linkData['object_uri']); } - $link = [ - 'view' => 'dayGridMonth', - 'timeRange' => 'now', - 'mode' => 'sidebar', + $params['link'] = $this->url->linkToRouteAbsolute('calendar.view.indexdirect.edit', [ 'objectId' => $objectId, - 'recurrenceId' => 'next' - ]; - $params['link'] = $this->url->linkToRouteAbsolute('calendar.view.indexview.timerange.edit', $link); + ]); } catch (\Exception $error) { // Do nothing } diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Todo.php b/apps/dav/lib/CalDAV/Activity/Provider/Todo.php index 1e817663439..fc0625ec970 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Todo.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Todo.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Activity/Setting/Calendar.php b/apps/dav/lib/CalDAV/Activity/Setting/Calendar.php index a201213f784..0ad86a919bc 100644 --- a/apps/dav/lib/CalDAV/Activity/Setting/Calendar.php +++ b/apps/dav/lib/CalDAV/Activity/Setting/Calendar.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Activity/Setting/Event.php b/apps/dav/lib/CalDAV/Activity/Setting/Event.php index ea049738251..ea9476d6f08 100644 --- a/apps/dav/lib/CalDAV/Activity/Setting/Event.php +++ b/apps/dav/lib/CalDAV/Activity/Setting/Event.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Activity/Setting/Todo.php b/apps/dav/lib/CalDAV/Activity/Setting/Todo.php index 7ac3b1e0f76..ed8377b0ffa 100644 --- a/apps/dav/lib/CalDAV/Activity/Setting/Todo.php +++ b/apps/dav/lib/CalDAV/Activity/Setting/Todo.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php b/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php index cd0b58c53d2..681709cdb6f 100644 --- a/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php +++ b/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/BirthdayService.php b/apps/dav/lib/CalDAV/BirthdayService.php index e1e46316d74..680b228766f 100644 --- a/apps/dav/lib/CalDAV/BirthdayService.php +++ b/apps/dav/lib/CalDAV/BirthdayService.php @@ -295,8 +295,8 @@ class BirthdayService { } return ( - $newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() || - $newCalendarData->VEVENT->SUMMARY->getValue() !== $existingBirthday->VEVENT->SUMMARY->getValue() + $newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() + || $newCalendarData->VEVENT->SUMMARY->getValue() !== $existingBirthday->VEVENT->SUMMARY->getValue() ); } diff --git a/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php b/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php index 4d25f5bb501..cc1bab6d4fc 100644 --- a/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php +++ b/apps/dav/lib/CalDAV/CachedSubscriptionImpl.php @@ -9,11 +9,12 @@ declare(strict_types=1); namespace OCA\DAV\CalDAV; use OCP\Calendar\ICalendar; +use OCP\Calendar\ICalendarIsEnabled; use OCP\Calendar\ICalendarIsShared; use OCP\Calendar\ICalendarIsWritable; use OCP\Constants; -class CachedSubscriptionImpl implements ICalendar, ICalendarIsShared, ICalendarIsWritable { +class CachedSubscriptionImpl implements ICalendar, ICalendarIsEnabled, ICalendarIsShared, ICalendarIsWritable { public function __construct( private CachedSubscription $calendar, @@ -54,16 +55,6 @@ class CachedSubscriptionImpl implements ICalendar, ICalendarIsShared, ICalendarI return $this->calendarInfo['{http://apple.com/ns/ical/}calendar-color']; } - /** - * @param string $pattern which should match within the $searchProperties - * @param array $searchProperties defines the properties within the query pattern should match - * @param array $options - optional parameters: - * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] - * @param int|null $limit - limit number of search results - * @param int|null $offset - offset for paging of search results - * @return array an array of events/journals/todos which are arrays of key-value-pairs - * @since 13.0.0 - */ public function search(string $pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null): array { return $this->backend->search($this->calendarInfo, $pattern, $searchProperties, $options, $limit, $offset); } @@ -86,6 +77,13 @@ class CachedSubscriptionImpl implements ICalendar, ICalendarIsShared, ICalendarI return $result; } + /** + * @since 32.0.0 + */ + public function isEnabled(): bool { + return $this->calendarInfo['{http://owncloud.org/ns}calendar-enabled'] ?? true; + } + public function isWritable(): bool { return false; } diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index e69fe9ed3f0..d5b0d875ede 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -90,6 +91,19 @@ use function time; * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php * * @package OCA\DAV\CalDAV + * + * @psalm-type CalendarInfo = array{ + * id: int, + * uri: string, + * principaluri: string, + * '{http://calendarserver.org/ns/}getctag': string, + * '{http://sabredav.org/ns}sync-token': int, + * '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet, + * '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': \Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp, + * '{DAV:}displayname': string, + * '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string, + * '{http://nextcloud.com/ns}owner-displayname': string, + * } */ class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport { use TTransactional; @@ -199,15 +213,13 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription } /** - * Return the number of calendars for a principal + * Return the number of calendars owned by the given principal. * - * By default this excludes the automatically generated birthday calendar + * Calendars shared with the given principal are not counted! * - * @param $principalUri - * @param bool $excludeBirthday - * @return int + * By default, this excludes the automatically generated birthday calendar. */ - public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) { + public function getCalendarsForUserCount(string $principalUri, bool $excludeBirthday = true): int { $principalUri = $this->convertPrincipal($principalUri, true); $query = $this->db->getQueryBuilder(); $query->select($query->func()->count('*')) @@ -398,8 +410,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription // New share can not have more permissions than the old one. continue; } - if (isset($calendars[$row['id']][$readOnlyPropertyName]) && - $calendars[$row['id']][$readOnlyPropertyName] === 0) { + if (isset($calendars[$row['id']][$readOnlyPropertyName]) + && $calendars[$row['id']][$readOnlyPropertyName] === 0) { // Old share is already read-write, no more permissions can be gained continue; } @@ -651,7 +663,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription } /** - * @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp, '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string }|null + * @psalm-return CalendarInfo|null + * @return array|null */ public function getCalendarById(int $calendarId): ?array { $fields = array_column($this->propertyMap, 0); @@ -1026,7 +1039,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription $rs->closeCursor(); } } - + /** * Returns all calendar objects with limited metadata for a calendar * @@ -1524,25 +1537,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription }, $this->db); } - - /** - * @param int $calendarObjectId - * @param int $classification - */ - public function setClassification($calendarObjectId, $classification) { - $this->cachedObjects = []; - if (!in_array($classification, [ - self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL - ])) { - throw new \InvalidArgumentException(); - } - $query = $this->db->getQueryBuilder(); - $query->update('calendarobjects') - ->set('classification', $query->createNamedParameter($classification)) - ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId))) - ->executeStatement(); - } - /** * Deletes an existing calendar object. * @@ -2016,8 +2010,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription if ($pattern !== '') { $innerQuery->andWhere($innerQuery->expr()->iLike('op.value', - $outerQuery->createNamedParameter('%' . - $this->db->escapeLikeParameter($pattern) . '%'))); + $outerQuery->createNamedParameter('%' + . $this->db->escapeLikeParameter($pattern) . '%'))); } $start = null; @@ -2993,7 +2987,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'calendarid' => $query->createNamedParameter($calendarId), 'operation' => $query->createNamedParameter($operation), 'calendartype' => $query->createNamedParameter($calendarType), - 'created_at' => time(), + 'created_at' => $query->createNamedParameter(time()), ]); foreach ($objectUris as $uri) { $query->setParameter('uri', $uri); @@ -3671,4 +3665,26 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription ->where($cmd->expr()->eq('uid', $cmd->createNamedParameter($eventId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR)); $cmd->executeStatement(); } + + public function unshare(IShareable $shareable, string $principal): void { + $this->atomic(function () use ($shareable, $principal): void { + $calendarData = $this->getCalendarById($shareable->getResourceId()); + if ($calendarData === null) { + throw new \RuntimeException('Trying to update shares for non-existing calendar: ' . $shareable->getResourceId()); + } + + $oldShares = $this->getShares($shareable->getResourceId()); + $unshare = $this->calendarSharingBackend->unshare($shareable, $principal); + + if ($unshare) { + $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent( + $shareable->getResourceId(), + $calendarData, + $oldShares, + [], + [$principal] + )); + } + }, $this->db); + } } diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index 789fe4b55c0..dd3a4cf3f69 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -53,8 +53,8 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI && strcasecmp($this->calendarInfo['{DAV:}displayname'], 'Contact birthdays') === 0) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Contact birthdays'); } - if ($this->getName() === CalDavBackend::PERSONAL_CALENDAR_URI && - $this->calendarInfo['{DAV:}displayname'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { + if ($this->getName() === CalDavBackend::PERSONAL_CALENDAR_URI + && $this->calendarInfo['{DAV:}displayname'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Personal'); } $this->l10n = $l10n; @@ -214,12 +214,8 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable } public function delete() { - if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']) && - $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']) { - $principal = 'principal:' . parent::getOwner(); - $this->caldavBackend->updateShares($this, [], [ - $principal - ]); + if ($this->isShared()) { + $this->caldavBackend->unshare($this, 'principal:' . $this->getPrincipalURI()); return; } diff --git a/apps/dav/lib/CalDAV/CalendarImpl.php b/apps/dav/lib/CalDAV/CalendarImpl.php index 46f1b9aef9d..b79bf7ea2d0 100644 --- a/apps/dav/lib/CalDAV/CalendarImpl.php +++ b/apps/dav/lib/CalDAV/CalendarImpl.php @@ -14,6 +14,7 @@ use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer; use OCP\Calendar\CalendarExportOptions; use OCP\Calendar\Exceptions\CalendarException; use OCP\Calendar\ICalendarExport; +use OCP\Calendar\ICalendarIsEnabled; use OCP\Calendar\ICalendarIsShared; use OCP\Calendar\ICalendarIsWritable; use OCP\Calendar\ICreateFromString; @@ -29,7 +30,7 @@ use Sabre\VObject\Property; use Sabre\VObject\Reader; use function Sabre\Uri\split as uriSplit; -class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIsWritable, ICalendarIsShared, ICalendarExport { +class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIsWritable, ICalendarIsShared, ICalendarExport, ICalendarIsEnabled { public function __construct( private Calendar $calendar, /** @var array<string, mixed> */ @@ -92,16 +93,6 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs return $vtimezone; } - /** - * @param string $pattern which should match within the $searchProperties - * @param array $searchProperties defines the properties within the query pattern should match - * @param array $options - optional parameters: - * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] - * @param int|null $limit - limit number of search results - * @param int|null $offset - offset for paging of search results - * @return array an array of events/journals/todos which are arrays of key-value-pairs - * @since 13.0.0 - */ public function search(string $pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null): array { return $this->backend->search($this->calendarInfo, $pattern, $searchProperties, $options, $limit, $offset); @@ -137,6 +128,13 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs } /** + * @since 32.0.0 + */ + public function isEnabled(): bool { + return $this->calendarInfo['{http://owncloud.org/ns}calendar-enabled'] ?? true; + } + + /** * @since 31.0.0 */ public function isWritable(): bool { diff --git a/apps/dav/lib/CalDAV/CalendarManager.php b/apps/dav/lib/CalDAV/CalendarManager.php index 1baf53ee457..a2d2f1cda8a 100644 --- a/apps/dav/lib/CalDAV/CalendarManager.php +++ b/apps/dav/lib/CalDAV/CalendarManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/CalendarObject.php b/apps/dav/lib/CalDAV/CalendarObject.php index 90a1e97ec4d..02178b4236f 100644 --- a/apps/dav/lib/CalDAV/CalendarObject.php +++ b/apps/dav/lib/CalDAV/CalendarObject.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/apps/dav/lib/CalDAV/CalendarProvider.php b/apps/dav/lib/CalDAV/CalendarProvider.php index a31322b2b49..3cc4039ed36 100644 --- a/apps/dav/lib/CalDAV/CalendarProvider.php +++ b/apps/dav/lib/CalDAV/CalendarProvider.php @@ -8,6 +8,8 @@ declare(strict_types=1); */ namespace OCA\DAV\CalDAV; +use OCA\DAV\Db\Property; +use OCA\DAV\Db\PropertyMapper; use OCP\Calendar\ICalendarProvider; use OCP\IConfig; use OCP\IL10N; @@ -20,6 +22,7 @@ class CalendarProvider implements ICalendarProvider { private IL10N $l10n, private IConfig $config, private LoggerInterface $logger, + private PropertyMapper $propertyMapper, ) { } @@ -35,6 +38,7 @@ class CalendarProvider implements ICalendarProvider { $iCalendars = []; foreach ($calendarInfos as $calendarInfo) { + $calendarInfo = array_merge($calendarInfo, $this->getAdditionalProperties($calendarInfo['principaluri'], $calendarInfo['uri'])); $calendar = new Calendar($this->calDavBackend, $calendarInfo, $this->l10n, $this->config, $this->logger); $iCalendars[] = new CalendarImpl( $calendar, @@ -44,4 +48,23 @@ class CalendarProvider implements ICalendarProvider { } return $iCalendars; } + + public function getAdditionalProperties(string $principalUri, string $calendarUri): array { + $user = str_replace('principals/users/', '', $principalUri); + $path = 'calendars/' . $user . '/' . $calendarUri; + + $properties = $this->propertyMapper->findPropertiesByPath($user, $path); + + $list = []; + foreach ($properties as $property) { + if ($property instanceof Property) { + $list[$property->getPropertyname()] = match ($property->getPropertyname()) { + '{http://owncloud.org/ns}calendar-enabled' => (bool)$property->getPropertyvalue(), + default => $property->getPropertyvalue() + }; + } + } + + return $list; + } } diff --git a/apps/dav/lib/CalDAV/CalendarRoot.php b/apps/dav/lib/CalDAV/CalendarRoot.php index bfe5f84ce31..c0a313955bb 100644 --- a/apps/dav/lib/CalDAV/CalendarRoot.php +++ b/apps/dav/lib/CalDAV/CalendarRoot.php @@ -33,8 +33,8 @@ class CalendarRoot extends \Sabre\CalDAV\CalendarRoot { } public function getName() { - if ($this->principalPrefix === 'principals/calendar-resources' || - $this->principalPrefix === 'principals/calendar-rooms') { + if ($this->principalPrefix === 'principals/calendar-resources' + || $this->principalPrefix === 'principals/calendar-rooms') { $parts = explode('/', $this->principalPrefix); return $parts[1]; diff --git a/apps/dav/lib/CalDAV/EventReader.php b/apps/dav/lib/CalDAV/EventReader.php index 7e337f3108a..b7dd2889956 100644 --- a/apps/dav/lib/CalDAV/EventReader.php +++ b/apps/dav/lib/CalDAV/EventReader.php @@ -169,9 +169,9 @@ class EventReader { if (isset($this->baseEvent->DTEND)) { $this->baseEventEndDate = $this->baseEvent->DTEND->getDateTime($this->baseEventEndTimeZone); $this->baseEventEndDateFloating = $this->baseEvent->DTEND->isFloating(); - $this->baseEventDuration = - $this->baseEvent->DTEND->getDateTime($this->baseEventEndTimeZone)->getTimeStamp() - - $this->baseEventStartDate->getTimeStamp(); + $this->baseEventDuration + = $this->baseEvent->DTEND->getDateTime($this->baseEventEndTimeZone)->getTimeStamp() + - $this->baseEventStartDate->getTimeStamp(); } // evaluate if duration exists // extract duration and calculate end date @@ -362,8 +362,8 @@ class EventReader { public function recurringConcludes(): bool { // retrieve rrule conclusions - if ($this->rruleIterator?->concludesOn() !== null || - $this->rruleIterator?->concludesAfter() !== null) { + if ($this->rruleIterator?->concludesOn() !== null + || $this->rruleIterator?->concludesAfter() !== null) { return true; } // retrieve rdate conclusions diff --git a/apps/dav/lib/CalDAV/Export/ExportService.php b/apps/dav/lib/CalDAV/Export/ExportService.php index 393c53b92e4..552b9e2b675 100644 --- a/apps/dav/lib/CalDAV/Export/ExportService.php +++ b/apps/dav/lib/CalDAV/Export/ExportService.php @@ -18,7 +18,7 @@ use Sabre\VObject\Writer; * Calendar Export Service */ class ExportService { - + public const FORMATS = ['ical', 'jcal', 'xcal']; private string $systemVersion; @@ -92,7 +92,7 @@ class ExportService { default => Writer::write($vobject) }; } - + /** * Generates serialized content for a component in xml format */ diff --git a/apps/dav/lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php b/apps/dav/lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php index 3f71b1db24c..08dc10f7bf4 100644 --- a/apps/dav/lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php +++ b/apps/dav/lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Integration/ExternalCalendar.php b/apps/dav/lib/CalDAV/Integration/ExternalCalendar.php index 6e755716397..acf81638679 100644 --- a/apps/dav/lib/CalDAV/Integration/ExternalCalendar.php +++ b/apps/dav/lib/CalDAV/Integration/ExternalCalendar.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Integration/ICalendarProvider.php b/apps/dav/lib/CalDAV/Integration/ICalendarProvider.php index bbee4cbda7c..40a8860dcb4 100644 --- a/apps/dav/lib/CalDAV/Integration/ICalendarProvider.php +++ b/apps/dav/lib/CalDAV/Integration/ICalendarProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php index 3d650a4a059..c8a7109abde 100644 --- a/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php +++ b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Outbox.php b/apps/dav/lib/CalDAV/Outbox.php index fc9dc87a574..608114d8093 100644 --- a/apps/dav/lib/CalDAV/Outbox.php +++ b/apps/dav/lib/CalDAV/Outbox.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Principal/Collection.php b/apps/dav/lib/CalDAV/Principal/Collection.php index f2cea0b5136..b76fde66464 100644 --- a/apps/dav/lib/CalDAV/Principal/Collection.php +++ b/apps/dav/lib/CalDAV/Principal/Collection.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Principal/User.php b/apps/dav/lib/CalDAV/Principal/User.php index 60b7953ea62..047d83827ed 100644 --- a/apps/dav/lib/CalDAV/Principal/User.php +++ b/apps/dav/lib/CalDAV/Principal/User.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/PublicCalendar.php b/apps/dav/lib/CalDAV/PublicCalendar.php index 4ee811efeae..9af6e544165 100644 --- a/apps/dav/lib/CalDAV/PublicCalendar.php +++ b/apps/dav/lib/CalDAV/PublicCalendar.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/PublicCalendarObject.php b/apps/dav/lib/CalDAV/PublicCalendarObject.php index c3dc5ab1843..2ab40b94347 100644 --- a/apps/dav/lib/CalDAV/PublicCalendarObject.php +++ b/apps/dav/lib/CalDAV/PublicCalendarObject.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php index db73a761b0d..76378e7a1c5 100644 --- a/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php +++ b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php b/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php index 2fd55a12643..fb9b7298f9b 100644 --- a/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php +++ b/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php index e2d44535ce0..c75090e1560 100644 --- a/apps/dav/lib/CalDAV/Reminder/ReminderService.php +++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php @@ -441,10 +441,10 @@ class ReminderService { */ private function deleteOrProcessNext(array $reminder, VObject\Component\VEvent $vevent):void { - if ($reminder['is_repeat_based'] || - !$reminder['is_recurring'] || - !$reminder['is_relative'] || - $reminder['is_recurrence_exception']) { + if ($reminder['is_repeat_based'] + || !$reminder['is_recurring'] + || !$reminder['is_relative'] + || $reminder['is_recurrence_exception']) { $this->backend->removeReminder($reminder['id']); return; } diff --git a/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php index e1bd2f5be29..68bb3373346 100644 --- a/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php +++ b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -25,12 +26,6 @@ use function array_values; abstract class AbstractPrincipalBackend implements BackendInterface { - /** @var ProxyMapper */ - private $proxyMapper; - - /** @var string */ - private $principalPrefix; - /** @var string */ private $dbTableName; @@ -45,13 +40,11 @@ abstract class AbstractPrincipalBackend implements BackendInterface { private IUserSession $userSession, private IGroupManager $groupManager, private LoggerInterface $logger, - ProxyMapper $proxyMapper, - string $principalPrefix, + private ProxyMapper $proxyMapper, + private string $principalPrefix, string $dbPrefix, private string $cuType, ) { - $this->proxyMapper = $proxyMapper; - $this->principalPrefix = $principalPrefix; $this->dbTableName = 'calendar_' . $dbPrefix . 's'; $this->dbMetaDataTableName = $this->dbTableName . '_md'; $this->dbForeignKeyName = $dbPrefix . '_id'; @@ -93,8 +86,8 @@ abstract class AbstractPrincipalBackend implements BackendInterface { $metaDataById[$metaDataRow[$this->dbForeignKeyName]] = []; } - $metaDataById[$metaDataRow[$this->dbForeignKeyName]][$metaDataRow['key']] = - $metaDataRow['value']; + $metaDataById[$metaDataRow[$this->dbForeignKeyName]][$metaDataRow['key']] + = $metaDataRow['value']; } while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { @@ -478,9 +471,9 @@ abstract class AbstractPrincipalBackend implements BackendInterface { * @return bool */ private function isAllowedToAccessResource(array $row, array $userGroups): bool { - if (!isset($row['group_restrictions']) || - $row['group_restrictions'] === null || - $row['group_restrictions'] === '') { + if (!isset($row['group_restrictions']) + || $row['group_restrictions'] === null + || $row['group_restrictions'] === '') { return true; } diff --git a/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php index 40396f67ce9..c70d93daf52 100644 --- a/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php +++ b/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php index 91cf78c296f..5704b23ae14 100644 --- a/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php +++ b/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 7e79388c53a..2af6b162d8d 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -45,7 +45,7 @@ use Sabre\VObject\Reader; * @license http://sabre.io/license/ Modified BSD License */ class IMipPlugin extends SabreIMipPlugin { - + private ?VCalendar $vCalendar = null; public const MAX_DATE = '2038-01-01'; public const METHOD_REQUEST = 'request'; @@ -156,9 +156,10 @@ class IMipPlugin extends SabreIMipPlugin { $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; return; } - // Don't send emails to things - if ($this->imipService->isRoomOrResource($attendee)) { - $this->logger->debug('No invitation sent as recipient is room or resource', [ + // Don't send emails to rooms, resources and circles + if ($this->imipService->isRoomOrResource($attendee) + || $this->imipService->isCircle($attendee)) { + $this->logger->debug('No invitation sent as recipient is room, resource or circle', [ 'attendee' => $recipient, ]); $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; @@ -185,7 +186,7 @@ class IMipPlugin extends SabreIMipPlugin { switch (strtolower($iTipMessage->method)) { case self::METHOD_REPLY: $method = self::METHOD_REPLY; - $data = $this->imipService->buildBodyData($vEvent, $oldVevent); + $data = $this->imipService->buildReplyBodyData($vEvent); $replyingAttendee = $this->imipService->getReplyingAttendee($iTipMessage); break; case self::METHOD_CANCEL: @@ -248,7 +249,6 @@ class IMipPlugin extends SabreIMipPlugin { // convert iTip Message to string $itip_msg = $iTipMessage->message->serialize(); - $user = null; $mailService = null; try { @@ -260,8 +260,14 @@ class IMipPlugin extends SabreIMipPlugin { $mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender); } } + + // The display name in Nextcloud can use utf-8. + // As the default charset for text/* is us-ascii, it's important to explicitly define it. + // See https://www.rfc-editor.org/rfc/rfc6047.html#section-2.4. + $contentType = 'text/calendar; method=' . $iTipMessage->method . '; charset="utf-8"'; + // evaluate if a mail service was found and has sending capabilities - if ($mailService !== null && $mailService instanceof IMessageSend) { + if ($mailService instanceof IMessageSend) { // construct mail message and set required parameters $message = $mailService->initiateMessage(); $message->setFrom( @@ -273,10 +279,12 @@ class IMipPlugin extends SabreIMipPlugin { $message->setSubject($template->renderSubject()); $message->setBodyPlain($template->renderText()); $message->setBodyHtml($template->renderHtml()); + // Adding name=event.ics is a trick to make the invitation also appear + // as a file attachment in mail clients like Thunderbird or Evolution. $message->setAttachments((new Attachment( $itip_msg, null, - 'text/calendar; name=event.ics; method=' . $iTipMessage->method, + $contentType . '; name=event.ics', true ))); // send message @@ -292,10 +300,12 @@ class IMipPlugin extends SabreIMipPlugin { (($senderName !== null) ? [$sender => $senderName] : [$sender]) ); $message->useTemplate($template); + // Using a different content type because Symfony Mailer/Mime will append the name to + // the content type header and attachInline does not allow null. $message->attachInline( $itip_msg, 'event.ics', - 'text/calendar; method=' . $iTipMessage->method + $contentType, ); $failed = $this->mailer->send($message); } diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php index e2844960a23..f7054eb2d34 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipService.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php @@ -159,7 +159,35 @@ class IMipService { if ($eventReaderCurrent->recurs()) { $data['meeting_occurring'] = $this->generateOccurringString($eventReaderCurrent); } - + return $data; + } + + /** + * @param VEvent $vEvent + * @return array + */ + public function buildReplyBodyData(VEvent $vEvent): array { + // construct event reader + $eventReader = new EventReader($vEvent); + $defaultVal = ''; + $data = []; + $data['meeting_when'] = $this->generateWhenString($eventReader); + + foreach (self::STRING_DIFF as $key => $property) { + $data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal); + } + + if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) { + $data['meeting_location_html'] = $locationHtml; + } + + $data['meeting_url_html'] = $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $data['meeting_url']) : ''; + + // generate occurring next string + if ($eventReader->recurs()) { + $data['meeting_occurring'] = $this->generateOccurringString($eventReader); + } + return $data; } @@ -324,7 +352,7 @@ class IMipService { * @return string */ public function generateWhenStringRecurringDaily(EventReader $er): string { - + // initialize $interval = (int)$er->recurringInterval(); $startTime = null; @@ -375,7 +403,7 @@ class IMipService { * @return string */ public function generateWhenStringRecurringWeekly(EventReader $er): string { - + // initialize $interval = (int)$er->recurringInterval(); $startTime = null; @@ -428,15 +456,15 @@ class IMipService { * @return string */ public function generateWhenStringRecurringMonthly(EventReader $er): string { - + // initialize $interval = (int)$er->recurringInterval(); $startTime = null; $conclusion = null; // days of month if ($er->recurringPattern() === 'R') { - $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' . - implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed())); + $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' + . implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed())); } else { $days = implode(', ', $er->recurringDaysOfMonth()); } @@ -493,7 +521,7 @@ class IMipService { * @return string */ public function generateWhenStringRecurringYearly(EventReader $er): string { - + // initialize $interval = (int)$er->recurringInterval(); $startTime = null; @@ -502,8 +530,8 @@ class IMipService { $months = implode(', ', array_map(function ($value) { return $this->localizeMonthName($value); }, $er->recurringMonthsOfYearNamed())); // days of month if ($er->recurringPattern() === 'R') { - $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' . - implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed())); + $days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' ' + . implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed())); } else { $days = $er->startDateTime()->format('jS'); } @@ -582,7 +610,7 @@ class IMipService { true => $this->l10n->t('On specific dates between %1$s - %2$s until %3$s', [$startTime, $endTime, $conclusion]), }; } - + /** * generates a occurring next string for a recurring event * @@ -1080,8 +1108,8 @@ class IMipService { $attendee = $iTipMessage->recipient; $organizer = $iTipMessage->sender; $sequence = $iTipMessage->sequence; - $recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ? - $vevent->{'RECURRENCE-ID'}->serialize() : null; + $recurrenceId = isset($vevent->{'RECURRENCE-ID'}) + ? $vevent->{'RECURRENCE-ID'}->serialize() : null; $uid = $vevent->{'UID'}; $query = $this->db->getQueryBuilder(); @@ -1155,6 +1183,21 @@ class IMipService { return false; } + public function isCircle(Property $attendee): bool { + $cuType = $attendee->offsetGet('CUTYPE'); + if (!$cuType instanceof Parameter) { + return false; + } + + $uri = $attendee->getValue(); + if (!$uri) { + return false; + } + + $cuTypeValue = $cuType->getValue(); + return $cuTypeValue === 'GROUP' && str_starts_with($uri, 'mailto:circle+'); + } + public function minimizeInterval(\DateInterval $dateInterval): array { // evaluate if time interval is in the past if ($dateInterval->invert == 1) { diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php index da0f9ea5637..a001df8b2a8 100644 --- a/apps/dav/lib/CalDAV/Schedule/Plugin.php +++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php @@ -372,8 +372,8 @@ EOF; return null; } - $isResourceOrRoom = str_starts_with($principalUrl, 'principals/calendar-resources') || - str_starts_with($principalUrl, 'principals/calendar-rooms'); + $isResourceOrRoom = str_starts_with($principalUrl, 'principals/calendar-resources') + || str_starts_with($principalUrl, 'principals/calendar-rooms'); if (str_starts_with($principalUrl, 'principals/users')) { [, $userId] = split($principalUrl); diff --git a/apps/dav/lib/CalDAV/Search/SearchPlugin.php b/apps/dav/lib/CalDAV/Search/SearchPlugin.php index 602ce151e12..27e39a76305 100644 --- a/apps/dav/lib/CalDAV/Search/SearchPlugin.php +++ b/apps/dav/lib/CalDAV/Search/SearchPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -61,8 +62,8 @@ class SearchPlugin extends ServerPlugin { $server->on('report', [$this, 'report']); - $server->xml->elementMap['{' . self::NS_Nextcloud . '}calendar-search'] = - CalendarSearchReport::class; + $server->xml->elementMap['{' . self::NS_Nextcloud . '}calendar-search'] + = CalendarSearchReport::class; } /** diff --git a/apps/dav/lib/CalDAV/Search/Xml/Filter/CompFilter.php b/apps/dav/lib/CalDAV/Search/Xml/Filter/CompFilter.php index 8a130865842..21a4fff1caf 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Filter/CompFilter.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Filter/CompFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Search/Xml/Filter/LimitFilter.php b/apps/dav/lib/CalDAV/Search/Xml/Filter/LimitFilter.php index 943e657903e..a98b325397b 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Filter/LimitFilter.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Filter/LimitFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Search/Xml/Filter/OffsetFilter.php b/apps/dav/lib/CalDAV/Search/Xml/Filter/OffsetFilter.php index 439a795dde9..ef438aa0258 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Filter/OffsetFilter.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Filter/OffsetFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php b/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php index 3b03b63e909..0c31f32348a 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Filter/ParamFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Search/Xml/Filter/PropFilter.php b/apps/dav/lib/CalDAV/Search/Xml/Filter/PropFilter.php index 42ecf630f44..251120e35cc 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Filter/PropFilter.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Filter/PropFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php b/apps/dav/lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php index b10cf3140cf..6d6bf958496 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php b/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php index 639d0b32655..6ece88fa87b 100644 --- a/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php +++ b/apps/dav/lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/lib/CalDAV/TipBroker.php b/apps/dav/lib/CalDAV/TipBroker.php index 43eff124f0b..16e68fde1f0 100644 --- a/apps/dav/lib/CalDAV/TipBroker.php +++ b/apps/dav/lib/CalDAV/TipBroker.php @@ -97,7 +97,7 @@ class TipBroker extends Broker { // Also If the meeting STATUS property was changed to CANCELLED // we need to send the attendee a CANCEL message. if (!$attendee['newInstances'] || $eventInfo['status'] === 'CANCELLED') { - + $message->method = $icalMsg->METHOD = 'CANCEL'; $message->significantChange = true; // clone base event @@ -108,7 +108,7 @@ class TipBroker extends Broker { $event->DTSTAMP = gmdate('Ymd\\THis\\Z'); $event->SEQUENCE = $message->sequence; $icalMsg->add($event); - + } else { // The attendee gets the updated event body $message->method = $icalMsg->METHOD = 'REQUEST'; @@ -124,11 +124,11 @@ class TipBroker extends Broker { $oldAttendeeInstances = array_keys($attendee['oldInstances']); $newAttendeeInstances = array_keys($attendee['newInstances']); - $message->significantChange = - $attendee['forceSend'] === 'REQUEST' || - count($oldAttendeeInstances) !== count($newAttendeeInstances) || - count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0 || - $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash']; + $message->significantChange + = $attendee['forceSend'] === 'REQUEST' + || count($oldAttendeeInstances) !== count($newAttendeeInstances) + || count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0 + || $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash']; foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) { $currentEvent = clone $eventInfo['instances'][$instanceId]; diff --git a/apps/dav/lib/CalDAV/UpcomingEventsService.php b/apps/dav/lib/CalDAV/UpcomingEventsService.php index 9c054deb4e0..6614d937ff7 100644 --- a/apps/dav/lib/CalDAV/UpcomingEventsService.php +++ b/apps/dav/lib/CalDAV/UpcomingEventsService.php @@ -47,20 +47,36 @@ class UpcomingEventsService { $this->userManager->get($userId), ); - return array_map(fn (array $event) => new UpcomingEvent( - $event['uri'], - ($event['objects'][0]['RECURRENCE-ID'][0] ?? null)?->getTimeStamp(), - $event['calendar-uri'], - $event['objects'][0]['DTSTART'][0]?->getTimestamp(), - $event['objects'][0]['SUMMARY'][0] ?? null, - $event['objects'][0]['LOCATION'][0] ?? null, - match ($calendarAppEnabled) { - // TODO: create a named, deep route in calendar - // TODO: it's a code smell to just assume this route exists, find an abstraction - true => $this->urlGenerator->linkToRouteAbsolute('calendar.view.index'), - false => null, - }, - ), $events); + return array_map(function (array $event) use ($userId, $calendarAppEnabled) { + $calendarAppUrl = null; + + if ($calendarAppEnabled) { + $arguments = [ + 'objectId' => base64_encode($this->urlGenerator->getWebroot() . '/remote.php/dav/calendars/' . $userId . '/' . $event['calendar-uri'] . '/' . $event['uri']), + ]; + + if (isset($event['RECURRENCE-ID'])) { + $arguments['recurrenceId'] = $event['RECURRENCE-ID'][0]; + } + /** + * TODO: create a named, deep route in calendar (it's a code smell to just assume this route exists, find an abstraction) + * When changing, also adjust for: + * - spreed/lib/Service/CalendarIntegrationService.php#getDashboardEvents + * - spreed/lib/Service/CalendarIntegrationService.php#getMutualEvents + */ + $calendarAppUrl = $this->urlGenerator->linkToRouteAbsolute('calendar.view.indexdirect.edit', $arguments); + } + + return new UpcomingEvent( + $event['uri'], + ($event['objects'][0]['RECURRENCE-ID'][0] ?? null)?->getTimeStamp(), + $event['calendar-uri'], + $event['objects'][0]['DTSTART'][0]?->getTimestamp(), + $event['objects'][0]['SUMMARY'][0] ?? null, + $event['objects'][0]['LOCATION'][0] ?? null, + $calendarAppUrl, + ); + }, $events); } } |